playlist;
private CountDownLatch onCurrentMediaItemChangedLatch;
diff --git a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/TestUtils.java b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/TestUtils.java
index 5a8e87de22..a7eb058ee6 100644
--- a/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/TestUtils.java
+++ b/extensions/media2/src/androidTest/java/com/google/android/exoplayer2/ext/media2/TestUtils.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java
index 19b1130c13..c23bdd5669 100644
--- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java
+++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/DefaultMediaItemConverter.java
@@ -13,79 +13,125 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE;
+
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media2.common.CallbackMediaItem;
import androidx.media2.common.FileMediaItem;
-import androidx.media2.common.MediaMetadata;
import androidx.media2.common.UriMediaItem;
+import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.util.Assertions;
-/** Default implementation of {@link MediaItemConverter}. */
-public final class DefaultMediaItemConverter implements MediaItemConverter {
+/**
+ * Default implementation of {@link MediaItemConverter}.
+ *
+ * Note that {@link #getMetadata} can be overridden to fill in additional metadata when
+ * converting {@link MediaItem ExoPlayer MediaItems} to their AndroidX equivalents.
+ */
+public class DefaultMediaItemConverter implements MediaItemConverter {
@Override
- public MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
- if (androidXMediaItem instanceof FileMediaItem) {
+ public MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem media2MediaItem) {
+ if (media2MediaItem instanceof FileMediaItem) {
throw new IllegalStateException("FileMediaItem isn't supported");
}
- if (androidXMediaItem instanceof CallbackMediaItem) {
+ if (media2MediaItem instanceof CallbackMediaItem) {
throw new IllegalStateException("CallbackMediaItem isn't supported");
}
-
- MediaItem.Builder exoplayerMediaItemBuilder = new MediaItem.Builder();
-
- // Set mediaItem as tag for creating MediaSource via MediaSourceFactory methods.
- exoplayerMediaItemBuilder.setTag(androidXMediaItem);
-
- // Media ID or URI must be present. Get it from androidx.MediaItem if possible.
+
@Nullable Uri uri = null;
@Nullable String mediaId = null;
- if (androidXMediaItem instanceof UriMediaItem) {
- UriMediaItem uriMediaItem = (UriMediaItem) androidXMediaItem;
+ @Nullable String title = null;
+ if (media2MediaItem instanceof UriMediaItem) {
+ UriMediaItem uriMediaItem = (UriMediaItem) media2MediaItem;
uri = uriMediaItem.getUri();
}
- @Nullable MediaMetadata metadata = androidXMediaItem.getMetadata();
+ @Nullable androidx.media2.common.MediaMetadata metadata = media2MediaItem.getMetadata();
if (metadata != null) {
- mediaId = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
- @Nullable String uriString = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_URI);
- if (uri == null && uriString != null) {
- uri = Uri.parse(uriString);
+ @Nullable String uriString = metadata.getString(METADATA_KEY_MEDIA_URI);
+ mediaId = metadata.getString(METADATA_KEY_MEDIA_ID);
+ if (uri == null) {
+ if (uriString != null) {
+ uri = Uri.parse(uriString);
+ } else if (mediaId != null) {
+ uri = Uri.parse("media2:///" + mediaId);
+ }
+ }
+ title = metadata.getString(METADATA_KEY_DISPLAY_TITLE);
+ if (title == null) {
+ title = metadata.getString(METADATA_KEY_TITLE);
}
}
if (uri == null) {
- // Generate a Uri to make it non-null. If not, tag will be ignored.
- uri = Uri.parse("exoplayer://" + androidXMediaItem.hashCode());
+ // Generate a URI to make it non-null. If not, then the tag passed to setTag will be ignored.
+ uri = Uri.parse("media2:///");
}
- exoplayerMediaItemBuilder.setUri(uri);
- exoplayerMediaItemBuilder.setMediaId(mediaId);
-
- if (androidXMediaItem.getStartPosition() != androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
- exoplayerMediaItemBuilder.setClipStartPositionMs(androidXMediaItem.getStartPosition());
- exoplayerMediaItemBuilder.setClipRelativeToDefaultPosition(true);
+ long startPositionMs = media2MediaItem.getStartPosition();
+ if (startPositionMs == androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
+ startPositionMs = 0;
}
- if (androidXMediaItem.getEndPosition() != androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
- exoplayerMediaItemBuilder.setClipEndPositionMs(androidXMediaItem.getEndPosition());
- exoplayerMediaItemBuilder.setClipRelativeToDefaultPosition(true);
+ long endPositionMs = media2MediaItem.getEndPosition();
+ if (endPositionMs == androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
+ endPositionMs = C.TIME_END_OF_SOURCE;
}
- return exoplayerMediaItemBuilder.build();
+ return new MediaItem.Builder()
+ .setUri(uri)
+ .setMediaId(mediaId)
+ .setMediaMetadata(
+ new com.google.android.exoplayer2.MediaMetadata.Builder().setTitle(title).build())
+ .setTag(media2MediaItem)
+ .setClipStartPositionMs(startPositionMs)
+ .setClipEndPositionMs(endPositionMs)
+ .build();
}
@Override
- public androidx.media2.common.MediaItem convertToAndroidXMediaItem(MediaItem exoplayerMediaItem) {
- Assertions.checkNotNull(exoplayerMediaItem);
+ public androidx.media2.common.MediaItem convertToMedia2MediaItem(MediaItem exoPlayerMediaItem) {
+ Assertions.checkNotNull(exoPlayerMediaItem);
MediaItem.PlaybackProperties playbackProperties =
- Assertions.checkNotNull(exoplayerMediaItem.playbackProperties);
+ Assertions.checkNotNull(exoPlayerMediaItem.playbackProperties);
+
@Nullable Object tag = playbackProperties.tag;
- if (!(tag instanceof androidx.media2.common.MediaItem)) {
- throw new IllegalStateException(
- "MediaItem tag must be an instance of androidx.media2.common.MediaItem");
+ if (tag instanceof androidx.media2.common.MediaItem) {
+ return (androidx.media2.common.MediaItem) tag;
}
- return (androidx.media2.common.MediaItem) tag;
+
+ androidx.media2.common.MediaMetadata metadata = getMetadata(exoPlayerMediaItem);
+ long startPositionMs = exoPlayerMediaItem.clippingProperties.startPositionMs;
+ long endPositionMs = exoPlayerMediaItem.clippingProperties.endPositionMs;
+ if (endPositionMs == C.TIME_END_OF_SOURCE) {
+ endPositionMs = androidx.media2.common.MediaItem.POSITION_UNKNOWN;
+ }
+
+ return new androidx.media2.common.MediaItem.Builder()
+ .setMetadata(metadata)
+ .setStartPosition(startPositionMs)
+ .setEndPosition(endPositionMs)
+ .build();
+ }
+
+ /**
+ * Returns a {@link androidx.media2.common.MediaMetadata} corresponding to the given {@link
+ * MediaItem ExoPlayer MediaItem}.
+ */
+ protected androidx.media2.common.MediaMetadata getMetadata(MediaItem exoPlayerMediaItem) {
+ @Nullable String title = exoPlayerMediaItem.mediaMetadata.title;
+
+ androidx.media2.common.MediaMetadata.Builder metadataBuilder =
+ new androidx.media2.common.MediaMetadata.Builder()
+ .putString(METADATA_KEY_MEDIA_ID, exoPlayerMediaItem.mediaId);
+ if (title != null) {
+ metadataBuilder.putString(METADATA_KEY_TITLE, title);
+ metadataBuilder.putString(METADATA_KEY_DISPLAY_TITLE, title);
+ }
+ return metadataBuilder.build();
}
}
diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaItemConverter.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaItemConverter.java
index 34a3d1c314..218c2a737e 100644
--- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaItemConverter.java
+++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/MediaItemConverter.java
@@ -13,25 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
import com.google.android.exoplayer2.MediaItem;
/**
- * Converter for between {@link MediaItem AndroidX MediaItem} and {@link
- * com.google.android.exoplayer2.MediaItem ExoPlayer MediaItem}.
+ * Converts between {@link androidx.media2.common.MediaItem Media2 MediaItem} and {@link MediaItem
+ * ExoPlayer MediaItem}.
*/
public interface MediaItemConverter {
/**
- * Converts {@link androidx.media2.common.MediaItem AndroidX MediaItem} to {@link MediaItem
+ * Converts an {@link androidx.media2.common.MediaItem Media2 MediaItem} to an {@link MediaItem
* ExoPlayer MediaItem}.
*/
- MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem androidXMediaItem);
+ MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem media2MediaItem);
/**
- * Converts {@link MediaItem ExoPlayer MediaItem} to {@link androidx.media2.common.MediaItem
- * AndroidX MediaItem}.
+ * Converts an {@link MediaItem ExoPlayer MediaItem} to an {@link androidx.media2.common.MediaItem
+ * Media2 MediaItem}.
*/
- androidx.media2.common.MediaItem convertToAndroidXMediaItem(MediaItem exoplayerMediaItem);
+ androidx.media2.common.MediaItem convertToMedia2MediaItem(MediaItem exoPlayerMediaItem);
}
diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java
index 6089d2c5d4..453a7b6d55 100644
--- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java
+++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/PlayerWrapper.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
import androidx.annotation.IntRange;
@@ -27,6 +26,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
@@ -56,44 +56,44 @@ import java.util.List;
void onPlayerStateChanged(/* @SessionPlayer.PlayerState */ int playerState);
/** Called when the player is prepared. */
- void onPrepared(androidx.media2.common.MediaItem androidXMediaItem, int bufferingPercentage);
+ void onPrepared(androidx.media2.common.MediaItem media2MediaItem, int bufferingPercentage);
/** Called when a seek request has completed. */
void onSeekCompleted();
/** Called when the player rebuffers. */
- void onBufferingStarted(androidx.media2.common.MediaItem androidXMediaItem);
+ void onBufferingStarted(androidx.media2.common.MediaItem media2MediaItem);
/** Called when the player becomes ready again after rebuffering. */
void onBufferingEnded(
- androidx.media2.common.MediaItem androidXMediaItem, int bufferingPercentage);
+ androidx.media2.common.MediaItem media2MediaItem, int bufferingPercentage);
/** Called periodically with the player's buffered position as a percentage. */
void onBufferingUpdate(
- androidx.media2.common.MediaItem androidXMediaItem, int bufferingPercentage);
+ androidx.media2.common.MediaItem media2MediaItem, int bufferingPercentage);
/** Called when current media item is changed. */
- void onCurrentMediaItemChanged(androidx.media2.common.MediaItem androidXMediaItem);
+ void onCurrentMediaItemChanged(androidx.media2.common.MediaItem media2MediaItem);
/** Called when playback of the item list has ended. */
void onPlaybackEnded();
/** Called when the player encounters an error. */
- void onError(@Nullable androidx.media2.common.MediaItem androidXMediaItem);
+ void onError(@Nullable androidx.media2.common.MediaItem media2MediaItem);
- /** Called when the playlist is changed */
+ /** Called when the playlist is changed. */
void onPlaylistChanged();
- /** Called when the shuffle mode is changed */
+ /** Called when the shuffle mode is changed. */
void onShuffleModeChanged(int shuffleMode);
- /** Called when the repeat mode is changed */
+ /** Called when the repeat mode is changed. */
void onRepeatModeChanged(int repeatMode);
- /** Called when the audio attributes is changed */
+ /** Called when the audio attributes is changed. */
void onAudioAttributesChanged(AudioAttributesCompat audioAttributes);
- /** Called when the playback speed is changed */
+ /** Called when the playback speed is changed. */
void onPlaybackSpeedChanged(float playbackSpeed);
}
@@ -108,14 +108,15 @@ import java.util.List;
private final ControlDispatcher controlDispatcher;
private final ComponentListener componentListener;
- private final List cachedPlaylist;
@Nullable private MediaMetadata playlistMetadata;
- private final List cachedMediaItems;
+
+ // These should be only updated in TimelineChanges.
+ private final List media2Playlist;
+ private final List exoPlayerPlaylist;
private boolean prepared;
private boolean rebuffering;
private int currentWindowIndex;
- private boolean loggedUnexpectedTimelineChanges;
private boolean ignoreTimelineUpdates;
/**
@@ -146,79 +147,69 @@ import java.util.List;
handler = new PlayerHandler(player.getApplicationLooper());
pollBufferRunnable = new PollBufferRunnable();
- cachedPlaylist = new ArrayList<>();
- cachedMediaItems = new ArrayList<>();
+ media2Playlist = new ArrayList<>();
+ exoPlayerPlaylist = new ArrayList<>();
currentWindowIndex = C.INDEX_UNSET;
+
+ prepared = player.getPlaybackState() != Player.STATE_IDLE;
+ rebuffering = player.getPlaybackState() == Player.STATE_BUFFERING;
+
+ updatePlaylist(player.getCurrentTimeline());
}
- public boolean setMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
- return setPlaylist(Collections.singletonList(androidXMediaItem), /* metadata= */ null);
+ public boolean setMediaItem(androidx.media2.common.MediaItem media2MediaItem) {
+ return setPlaylist(Collections.singletonList(media2MediaItem), /* metadata= */ null);
}
public boolean setPlaylist(
List playlist, @Nullable MediaMetadata metadata) {
// Check for duplication.
for (int i = 0; i < playlist.size(); i++) {
- androidx.media2.common.MediaItem androidXMediaItem = playlist.get(i);
- Assertions.checkArgument(playlist.indexOf(androidXMediaItem) == i);
+ androidx.media2.common.MediaItem media2MediaItem = playlist.get(i);
+ Assertions.checkArgument(playlist.indexOf(media2MediaItem) == i);
}
- this.cachedPlaylist.clear();
- this.cachedPlaylist.addAll(playlist);
this.playlistMetadata = metadata;
- this.cachedMediaItems.clear();
- List exoplayerMediaItems = new ArrayList<>();
+ List exoPlayerMediaItems = new ArrayList<>();
for (int i = 0; i < playlist.size(); i++) {
- androidx.media2.common.MediaItem androidXMediaItem = playlist.get(i);
- MediaItem exoplayerMediaItem =
- Assertions.checkNotNull(
- mediaItemConverter.convertToExoPlayerMediaItem(androidXMediaItem));
- exoplayerMediaItems.add(exoplayerMediaItem);
+ androidx.media2.common.MediaItem media2MediaItem = playlist.get(i);
+ MediaItem exoPlayerMediaItem =
+ Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(media2MediaItem));
+ exoPlayerMediaItems.add(exoPlayerMediaItem);
}
- this.cachedMediaItems.addAll(exoplayerMediaItems);
- player.setMediaItems(exoplayerMediaItems, /* resetPosition= */ true);
+ player.setMediaItems(exoPlayerMediaItems, /* resetPosition= */ true);
currentWindowIndex = getCurrentMediaItemIndex();
return true;
}
- public boolean addPlaylistItem(int index, androidx.media2.common.MediaItem androidXMediaItem) {
- Assertions.checkArgument(!cachedPlaylist.contains(androidXMediaItem));
- index = Util.constrainValue(index, 0, cachedPlaylist.size());
+ public boolean addPlaylistItem(int index, androidx.media2.common.MediaItem media2MediaItem) {
+ Assertions.checkArgument(!media2Playlist.contains(media2MediaItem));
+ index = Util.constrainValue(index, 0, media2Playlist.size());
- cachedPlaylist.add(index, androidXMediaItem);
- MediaItem exoplayerMediaItem =
- Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(androidXMediaItem));
- cachedMediaItems.add(index, exoplayerMediaItem);
- player.addMediaItem(index, exoplayerMediaItem);
+ MediaItem exoPlayerMediaItem =
+ Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(media2MediaItem));
+ player.addMediaItem(index, exoPlayerMediaItem);
return true;
}
public boolean removePlaylistItem(@IntRange(from = 0) int index) {
- androidx.media2.common.MediaItem androidXMediaItemToRemove = cachedPlaylist.remove(index);
- releaseMediaItem(androidXMediaItemToRemove);
- cachedMediaItems.remove(index);
player.removeMediaItem(index);
return true;
}
- public boolean replacePlaylistItem(
- int index, androidx.media2.common.MediaItem androidXMediaItem) {
- Assertions.checkArgument(!cachedPlaylist.contains(androidXMediaItem));
- index = Util.constrainValue(index, 0, cachedPlaylist.size());
+ public boolean replacePlaylistItem(int index, androidx.media2.common.MediaItem media2MediaItem) {
+ Assertions.checkArgument(!media2Playlist.contains(media2MediaItem));
+ index = Util.constrainValue(index, 0, media2Playlist.size());
- androidx.media2.common.MediaItem androidXMediaItemToRemove = cachedPlaylist.get(index);
- cachedPlaylist.set(index, androidXMediaItem);
- releaseMediaItem(androidXMediaItemToRemove);
- MediaItem exoplayerMediaItemToAdd =
- Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(androidXMediaItem));
- cachedMediaItems.set(index, exoplayerMediaItemToAdd);
+ MediaItem exoPlayerMediaItemToAdd =
+ Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(media2MediaItem));
ignoreTimelineUpdates = true;
player.removeMediaItem(index);
ignoreTimelineUpdates = false;
- player.addMediaItem(index, exoplayerMediaItemToAdd);
+ player.addMediaItem(index, exoPlayerMediaItemToAdd);
return true;
}
@@ -272,8 +263,8 @@ import java.util.List;
}
@Nullable
- public List getCachedPlaylist() {
- return new ArrayList<>(cachedPlaylist);
+ public List getPlaylist() {
+ return new ArrayList<>(media2Playlist);
}
@Nullable
@@ -290,7 +281,7 @@ import java.util.List;
}
public int getCurrentMediaItemIndex() {
- return cachedPlaylist.isEmpty() ? C.INDEX_UNSET : player.getCurrentWindowIndex();
+ return media2Playlist.isEmpty() ? C.INDEX_UNSET : player.getCurrentWindowIndex();
}
public int getPreviousMediaItemIndex() {
@@ -304,7 +295,7 @@ import java.util.List;
@Nullable
public androidx.media2.common.MediaItem getCurrentMediaItem() {
int index = getCurrentMediaItemIndex();
- return (index != C.INDEX_UNSET) ? cachedPlaylist.get(index) : null;
+ return index == C.INDEX_UNSET ? null : media2Playlist.get(index);
}
public boolean prepare() {
@@ -317,9 +308,9 @@ import java.util.List;
public boolean play() {
if (player.getPlaybackState() == Player.STATE_ENDED) {
- int currentWindowIndex = getCurrentMediaItemIndex();
boolean seekHandled =
- controlDispatcher.dispatchSeekTo(player, currentWindowIndex, /* positionMs= */ 0);
+ controlDispatcher.dispatchSeekTo(
+ player, player.getCurrentWindowIndex(), /* positionMs= */ 0);
if (!seekHandled) {
return false;
}
@@ -342,23 +333,19 @@ import java.util.List;
}
public boolean seekTo(long position) {
- int currentWindowIndex = getCurrentMediaItemIndex();
- return controlDispatcher.dispatchSeekTo(player, currentWindowIndex, position);
+ return controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), position);
}
public long getCurrentPosition() {
- Assertions.checkState(getState() != SessionPlayer.PLAYER_STATE_IDLE);
- return Math.max(0, player.getCurrentPosition());
+ return player.getCurrentPosition();
}
public long getDuration() {
- Assertions.checkState(getState() != SessionPlayer.PLAYER_STATE_IDLE);
long duration = player.getDuration();
- return duration == C.TIME_UNSET ? -1 : duration;
+ return duration == C.TIME_UNSET ? SessionPlayer.UNKNOWN_TIME : duration;
}
public long getBufferedPosition() {
- Assertions.checkState(getState() != SessionPlayer.PLAYER_STATE_IDLE);
return player.getBufferedPosition();
}
@@ -397,11 +384,11 @@ import java.util.List;
}
public void setPlaybackSpeed(float playbackSpeed) {
- player.setPlaybackSpeed(playbackSpeed);
+ player.setPlaybackParameters(new PlaybackParameters(playbackSpeed));
}
public float getPlaybackSpeed() {
- return player.getPlaybackSpeed();
+ return player.getPlaybackParameters().speed;
}
public void reset() {
@@ -427,7 +414,7 @@ import java.util.List;
}
public boolean canSkipToPlaylistItem() {
- @Nullable List playlist = getCachedPlaylist();
+ @Nullable List playlist = getPlaylist();
return playlist != null && playlist.size() > 1;
}
@@ -497,57 +484,57 @@ import java.util.List;
listener.onShuffleModeChanged(Utils.getShuffleMode(shuffleModeEnabled));
}
- private void handlePlaybackSpeedChanged(float playbackSpeed) {
- listener.onPlaybackSpeedChanged(playbackSpeed);
+ private void handlePlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ listener.onPlaybackSpeedChanged(playbackParameters.speed);
}
private void handleTimelineChanged(Timeline timeline) {
if (ignoreTimelineUpdates) {
return;
}
- updateCachedPlaylistAndMediaItems(timeline);
+ if (!isExoPlayerMediaItemsChanged(timeline)) {
+ return;
+ }
+ updatePlaylist(timeline);
listener.onPlaylistChanged();
}
- // Update cached playlist, if the ExoPlayer Player's Timeline is unexpectedly changed without
- // using SessionPlayer interface.
- private void updateCachedPlaylistAndMediaItems(Timeline currentTimeline) {
- // Check whether ExoPlayer media items are the same as expected.
+ // Check whether Timeline is changed by media item changes or not
+ private boolean isExoPlayerMediaItemsChanged(Timeline timeline) {
+ if (exoPlayerPlaylist.size() != timeline.getWindowCount()) {
+ return true;
+ }
Timeline.Window window = new Timeline.Window();
- int windowCount = currentTimeline.getWindowCount();
+ int windowCount = timeline.getWindowCount();
for (int i = 0; i < windowCount; i++) {
- currentTimeline.getWindow(i, window);
- if (i >= cachedMediaItems.size()
- || !ObjectsCompat.equals(cachedMediaItems.get(i), window.mediaItem)) {
- if (!loggedUnexpectedTimelineChanges) {
- Log.w(TAG, "Timeline was unexpectedly changed. Playlist will be rebuilt.");
- loggedUnexpectedTimelineChanges = true;
- }
-
- androidx.media2.common.MediaItem oldAndroidXMediaItem = cachedPlaylist.get(i);
- releaseMediaItem(oldAndroidXMediaItem);
-
- androidx.media2.common.MediaItem androidXMediaItem =
- Assertions.checkNotNull(
- mediaItemConverter.convertToAndroidXMediaItem(window.mediaItem));
- if (i < cachedMediaItems.size()) {
- cachedMediaItems.set(i, window.mediaItem);
- cachedPlaylist.set(i, androidXMediaItem);
- } else {
- cachedMediaItems.add(window.mediaItem);
- cachedPlaylist.add(androidXMediaItem);
- }
+ timeline.getWindow(i, window);
+ if (!ObjectsCompat.equals(exoPlayerPlaylist.get(i), window.mediaItem)) {
+ return true;
}
}
- if (cachedMediaItems.size() > windowCount) {
- if (!loggedUnexpectedTimelineChanges) {
- Log.w(TAG, "Timeline was unexpectedly changed. Playlist will be rebuilt.");
- loggedUnexpectedTimelineChanges = true;
- }
- while (cachedMediaItems.size() > windowCount) {
- cachedMediaItems.remove(windowCount);
- cachedPlaylist.remove(windowCount);
- }
+ return false;
+ }
+
+ private void updatePlaylist(Timeline timeline) {
+ List media2MediaItemToBeRemoved =
+ new ArrayList<>(media2Playlist);
+ media2Playlist.clear();
+ exoPlayerPlaylist.clear();
+
+ Timeline.Window window = new Timeline.Window();
+ int windowCount = timeline.getWindowCount();
+ for (int i = 0; i < windowCount; i++) {
+ timeline.getWindow(i, window);
+ MediaItem exoPlayerMediaItem = window.mediaItem;
+ androidx.media2.common.MediaItem media2MediaItem =
+ Assertions.checkNotNull(mediaItemConverter.convertToMedia2MediaItem(exoPlayerMediaItem));
+ exoPlayerPlaylist.add(exoPlayerMediaItem);
+ media2Playlist.add(media2MediaItem);
+ media2MediaItemToBeRemoved.remove(media2MediaItem);
+ }
+
+ for (androidx.media2.common.MediaItem item : media2MediaItemToBeRemoved) {
+ releaseMediaItem(item);
}
}
@@ -556,35 +543,35 @@ import java.util.List;
}
private void updateBufferingAndScheduleNextPollBuffer() {
- androidx.media2.common.MediaItem androidXMediaItem =
+ androidx.media2.common.MediaItem media2MediaItem =
Assertions.checkNotNull(getCurrentMediaItem());
- listener.onBufferingUpdate(androidXMediaItem, player.getBufferedPercentage());
+ listener.onBufferingUpdate(media2MediaItem, player.getBufferedPercentage());
handler.removeCallbacks(pollBufferRunnable);
handler.postDelayed(pollBufferRunnable, POLL_BUFFER_INTERVAL_MS);
}
private void maybeNotifyBufferingEvents() {
- androidx.media2.common.MediaItem androidXMediaItem =
+ androidx.media2.common.MediaItem media2MediaItem =
Assertions.checkNotNull(getCurrentMediaItem());
if (prepared && !rebuffering) {
rebuffering = true;
- listener.onBufferingStarted(androidXMediaItem);
+ listener.onBufferingStarted(media2MediaItem);
}
}
private void maybeNotifyReadyEvents() {
- androidx.media2.common.MediaItem androidXMediaItem =
+ androidx.media2.common.MediaItem media2MediaItem =
Assertions.checkNotNull(getCurrentMediaItem());
boolean prepareComplete = !prepared;
if (prepareComplete) {
prepared = true;
handlePositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
listener.onPlayerStateChanged(SessionPlayer.PLAYER_STATE_PAUSED);
- listener.onPrepared(androidXMediaItem, player.getBufferedPercentage());
+ listener.onPrepared(media2MediaItem, player.getBufferedPercentage());
}
if (rebuffering) {
rebuffering = false;
- listener.onBufferingEnded(androidXMediaItem, player.getBufferedPercentage());
+ listener.onBufferingEnded(media2MediaItem, player.getBufferedPercentage());
}
}
@@ -596,13 +583,13 @@ import java.util.List;
}
}
- private void releaseMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
+ private void releaseMediaItem(androidx.media2.common.MediaItem media2MediaItem) {
try {
- if (androidXMediaItem instanceof CallbackMediaItem) {
- ((CallbackMediaItem) androidXMediaItem).getDataSourceCallback().close();
+ if (media2MediaItem instanceof CallbackMediaItem) {
+ ((CallbackMediaItem) media2MediaItem).getDataSourceCallback().close();
}
} catch (IOException e) {
- Log.w(TAG, "Error releasing media item " + androidXMediaItem, e);
+ Log.w(TAG, "Error releasing media item " + media2MediaItem, e);
}
}
@@ -641,8 +628,8 @@ import java.util.List;
}
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
- handlePlaybackSpeedChanged(playbackSpeed);
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ handlePlaybackParametersChanged(playbackParameters);
}
@Override
diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java
index 986478f1a9..1f60db947e 100644
--- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java
+++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallback.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallbackBuilder.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallbackBuilder.java
index e334dbd0ad..516ec20b3b 100644
--- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallbackBuilder.java
+++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionCallbackBuilder.java
@@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -31,7 +29,6 @@ import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.Rating;
import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.UriMediaItem;
import androidx.media2.session.MediaController;
import androidx.media2.session.MediaSession;
import androidx.media2.session.MediaSession.ControllerInfo;
@@ -39,13 +36,11 @@ import androidx.media2.session.SessionCommand;
import androidx.media2.session.SessionCommandGroup;
import androidx.media2.session.SessionResult;
import com.google.android.exoplayer2.util.Assertions;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
/**
- * Builds {@link MediaSession.SessionCallback} with various collaborators.
+ * Builds a {@link MediaSession.SessionCallback} with various collaborators.
*
* @see MediaSession.SessionCallback
*/
@@ -351,10 +346,8 @@ public final class SessionCallbackBuilder {
}
}
- /**
- * Default implementation of {@link MediaItemProvider} that assumes the media id is a URI string.
- */
- public static final class DefaultMediaItemProvider implements MediaItemProvider {
+ /** A {@link MediaItemProvider} that creates media items containing only a media ID. */
+ public static final class MediaIdMediaItemProvider implements MediaItemProvider {
@Override
@Nullable
public MediaItem onCreateMediaItem(
@@ -362,17 +355,11 @@ public final class SessionCallbackBuilder {
if (TextUtils.isEmpty(mediaId)) {
return null;
}
- try {
- new URI(mediaId);
- } catch (URISyntaxException e) {
- // Ignore if mediaId isn't a URI.
- return null;
- }
MediaMetadata metadata =
new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
.build();
- return new UriMediaItem.Builder(Uri.parse(mediaId)).setMetadata(metadata).build();
+ return new MediaItem.Builder().setMetadata(metadata).build();
}
}
diff --git a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java
index 99a5f1bcfc..d4aa888a1a 100644
--- a/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java
+++ b/extensions/media2/src/main/java/com/google/android/exoplayer2/ext/media2/SessionPlayerConnector.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.ext.media2;
import androidx.annotation.FloatRange;
@@ -23,6 +22,7 @@ import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Pair;
import androidx.media.AudioAttributesCompat;
+import androidx.media2.common.CallbackMediaItem;
import androidx.media2.common.FileMediaItem;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
@@ -41,25 +41,11 @@ import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* An implementation of {@link SessionPlayer} that wraps a given ExoPlayer {@link Player} instance.
*
- * Ownership
- *
- * {@code SessionPlayerConnector} takes ownership of the provided ExoPlayer {@link Player}
- * instance between when it's constructed and when it's {@link #close() closed}. No other components
- * should interact with the wrapped player (otherwise, unexpected event callbacks from the wrapped
- * player may put the session player in an inconsistent state).
- *
- *
Call {@link SessionPlayer#close()} when the {@code SessionPlayerConnector} is no longer needed
- * to regain ownership of the wrapped player. It is the caller's responsibility to release the
- * wrapped player via {@link Player#release()}.
- *
- *
Threading model
- *
* Internally this implementation posts operations to and receives callbacks on the thread
* associated with {@link Player#getApplicationLooper()}, so it is important not to block this
* thread. In particular, when awaiting the result of an asynchronous session player operation, apps
@@ -95,16 +81,15 @@ public final class SessionPlayerConnector extends SessionPlayer {
// Should be only accessed on the executor, which is currently single-threaded.
@Nullable private MediaItem currentMediaItem;
- @Nullable private List currentPlaylist;
/**
- * Creates an instance using {@link DefaultControlDispatcher} to dispatch player commands.
+ * Creates an instance using {@link DefaultMediaItemConverter} to convert between ExoPlayer and
+ * media2 MediaItems and {@link DefaultControlDispatcher} to dispatch player commands.
*
* @param player The player to wrap.
- * @param mediaItemConverter The {@link MediaItemConverter}.
*/
- public SessionPlayerConnector(Player player, MediaItemConverter mediaItemConverter) {
- this(player, mediaItemConverter, new DefaultControlDispatcher());
+ public SessionPlayerConnector(Player player) {
+ this(player, new DefaultMediaItemConverter(), new DefaultControlDispatcher());
}
/**
@@ -124,19 +109,8 @@ public final class SessionPlayerConnector extends SessionPlayer {
taskHandler = new PlayerHandler(player.getApplicationLooper());
taskHandlerExecutor = taskHandler::postOrRun;
ExoPlayerWrapperListener playerListener = new ExoPlayerWrapperListener();
- PlayerWrapper playerWrapper =
- new PlayerWrapper(playerListener, player, mediaItemConverter, controlDispatcher);
- this.player = playerWrapper;
+ this.player = new PlayerWrapper(playerListener, player, mediaItemConverter, controlDispatcher);
playerCommandQueue = new PlayerCommandQueue(this.player, taskHandler);
-
- @SuppressWarnings("assignment.type.incompatible")
- @Initialized
- SessionPlayerConnector initializedThis = this;
- initializedThis.runPlayerCallableBlocking(
- /* callable= */ () -> {
- playerWrapper.reset();
- return null;
- });
}
@Override
@@ -251,17 +225,27 @@ public final class SessionPlayerConnector extends SessionPlayer {
return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getAudioAttributes);
}
+ /**
+ * {@inheritDoc}
+ *
+ * {@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
+ */
@Override
public ListenableFuture setMediaItem(MediaItem item) {
Assertions.checkNotNull(item);
Assertions.checkArgument(!(item instanceof FileMediaItem));
+ Assertions.checkArgument(!(item instanceof CallbackMediaItem));
ListenableFuture result =
playerCommandQueue.addCommand(
PlayerCommandQueue.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, () -> player.setMediaItem(item));
- result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
return result;
}
+ /**
+ * {@inheritDoc}
+ *
+ * {@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
+ */
@Override
public ListenableFuture setPlaylist(
final List playlist, @Nullable MediaMetadata metadata) {
@@ -271,6 +255,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
MediaItem item = playlist.get(i);
Assertions.checkNotNull(item);
Assertions.checkArgument(!(item instanceof FileMediaItem));
+ Assertions.checkArgument(!(item instanceof CallbackMediaItem));
for (int j = 0; j < i; j++) {
Assertions.checkArgument(
item != playlist.get(j),
@@ -281,20 +266,24 @@ public final class SessionPlayerConnector extends SessionPlayer {
playerCommandQueue.addCommand(
PlayerCommandQueue.COMMAND_CODE_PLAYER_SET_PLAYLIST,
/* command= */ () -> player.setPlaylist(playlist, metadata));
- result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
return result;
}
+ /**
+ * {@inheritDoc}
+ *
+ * {@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
+ */
@Override
public ListenableFuture addPlaylistItem(int index, MediaItem item) {
Assertions.checkArgument(index >= 0);
Assertions.checkNotNull(item);
Assertions.checkArgument(!(item instanceof FileMediaItem));
+ Assertions.checkArgument(!(item instanceof CallbackMediaItem));
ListenableFuture result =
playerCommandQueue.addCommand(
PlayerCommandQueue.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
/* command= */ () -> player.addPlaylistItem(index, item));
- result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
return result;
}
@@ -305,20 +294,24 @@ public final class SessionPlayerConnector extends SessionPlayer {
playerCommandQueue.addCommand(
PlayerCommandQueue.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
/* command= */ () -> player.removePlaylistItem(index));
- result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
return result;
}
+ /**
+ * {@inheritDoc}
+ *
+ * {@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
+ */
@Override
public ListenableFuture replacePlaylistItem(int index, MediaItem item) {
Assertions.checkArgument(index >= 0);
Assertions.checkNotNull(item);
Assertions.checkArgument(!(item instanceof FileMediaItem));
+ Assertions.checkArgument(!(item instanceof CallbackMediaItem));
ListenableFuture result =
playerCommandQueue.addCommand(
PlayerCommandQueue.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
/* command= */ () -> player.replacePlaylistItem(index, item));
- result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
return result;
}
@@ -385,7 +378,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
@Override
@Nullable
public List getPlaylist() {
- return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getCachedPlaylist);
+ return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getPlaylist);
}
@Override
@@ -447,7 +440,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
}
reset();
- this.runPlayerCallableBlockingInternal(
+ this.runPlayerCallableBlocking(
/* callable= */ () -> {
player.close();
return null;
@@ -511,7 +504,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
state = PLAYER_STATE_IDLE;
mediaItemToBuffState.clear();
}
- this.runPlayerCallableBlockingInternal(
+ this.runPlayerCallableBlocking(
/* callable= */ () -> {
player.reset();
return null;
@@ -558,25 +551,18 @@ public final class SessionPlayerConnector extends SessionPlayer {
}
private void handlePlaylistChangedOnHandler() {
- List currentPlaylist = player.getCachedPlaylist();
- boolean notifyCurrentPlaylist = !ObjectsCompat.equals(this.currentPlaylist, currentPlaylist);
- this.currentPlaylist = currentPlaylist;
+ List currentPlaylist = player.getPlaylist();
MediaMetadata playlistMetadata = player.getPlaylistMetadata();
MediaItem currentMediaItem = player.getCurrentMediaItem();
boolean notifyCurrentMediaItem = !ObjectsCompat.equals(this.currentMediaItem, currentMediaItem);
this.currentMediaItem = currentMediaItem;
- if (!notifyCurrentMediaItem && !notifyCurrentPlaylist) {
- return;
- }
long currentPosition = getCurrentPosition();
notifySessionPlayerCallback(
callback -> {
- if (notifyCurrentPlaylist) {
- callback.onPlaylistChanged(
- SessionPlayerConnector.this, currentPlaylist, playlistMetadata);
- }
+ callback.onPlaylistChanged(
+ SessionPlayerConnector.this, currentPlaylist, playlistMetadata);
if (notifyCurrentMediaItem) {
Assertions.checkNotNull(
currentMediaItem, "PlaylistManager#currentMediaItem() cannot be changed to null");
@@ -610,13 +596,6 @@ public final class SessionPlayerConnector extends SessionPlayer {
}
private T runPlayerCallableBlocking(Callable callable) {
- synchronized (stateLock) {
- Assertions.checkState(!closed);
- }
- return runPlayerCallableBlockingInternal(callable);
- }
-
- private T runPlayerCallableBlockingInternal(Callable callable) {
SettableFuture future = SettableFuture.create();
boolean success =
taskHandler.postOrRun(
diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
index f3edfa3545..85d0155bd7 100644
--- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
+++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
@@ -127,8 +128,8 @@ public final class MediaSessionConnector {
@PlaybackActions public static final long DEFAULT_PLAYBACK_ACTIONS = ALL_PLAYBACK_ACTIONS;
/**
- * The name of the {@link PlaybackStateCompat} float extra with the value of {@link
- * Player#getPlaybackSpeed()}.
+ * The name of the {@link PlaybackStateCompat} float extra with the value of {@code
+ * Player.getPlaybackParameters().speed}.
*/
public static final String EXTRAS_SPEED = "EXO_SPEED";
@@ -765,7 +766,7 @@ public final class MediaSessionConnector {
queueNavigator != null
? queueNavigator.getActiveQueueItemId(player)
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
- float playbackSpeed = player.getPlaybackSpeed();
+ float playbackSpeed = player.getPlaybackParameters().speed;
extras.putFloat(EXTRAS_SPEED, playbackSpeed);
float sessionPlaybackSpeed = player.isPlaying() ? playbackSpeed : 0f;
builder
@@ -1134,7 +1135,7 @@ public final class MediaSessionConnector {
}
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
invalidateMediaSessionPlaybackState();
}
diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Format.java b/library/common/src/main/java/com/google/android/exoplayer2/Format.java
index fa11f0c2a9..05062727c3 100644
--- a/library/common/src/main/java/com/google/android/exoplayer2/Format.java
+++ b/library/common/src/main/java/com/google/android/exoplayer2/Format.java
@@ -592,37 +592,7 @@ public final class Format implements Parcelable {
// Build.
public Format build() {
- return new Format(
- id,
- label,
- language,
- selectionFlags,
- roleFlags,
- averageBitrate,
- peakBitrate,
- codecs,
- metadata,
- containerMimeType,
- sampleMimeType,
- maxInputSize,
- initializationData,
- drmInitData,
- subsampleOffsetUs,
- width,
- height,
- frameRate,
- rotationDegrees,
- pixelWidthHeightRatio,
- projectionData,
- stereoMode,
- colorInfo,
- channelCount,
- sampleRate,
- pcmEncoding,
- encoderDelay,
- encoderPadding,
- accessibilityChannel,
- exoMediaCryptoType);
+ return new Format(/* builder= */ this);
}
}
@@ -1211,86 +1181,51 @@ public final class Format implements Parcelable {
return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
}
- // Some fields are deprecated but they're still assigned below.
- /* package */ Format(
- @Nullable String id,
- @Nullable String label,
- @Nullable String language,
- @C.SelectionFlags int selectionFlags,
- @C.RoleFlags int roleFlags,
- int averageBitrate,
- int peakBitrate,
- @Nullable String codecs,
- @Nullable Metadata metadata,
- // Container specific.
- @Nullable String containerMimeType,
- // Sample specific.
- @Nullable String sampleMimeType,
- int maxInputSize,
- @Nullable List initializationData,
- @Nullable DrmInitData drmInitData,
- long subsampleOffsetUs,
- // Video specific.
- int width,
- int height,
- float frameRate,
- int rotationDegrees,
- float pixelWidthHeightRatio,
- @Nullable byte[] projectionData,
- @C.StereoMode int stereoMode,
- @Nullable ColorInfo colorInfo,
- // Audio specific.
- int channelCount,
- int sampleRate,
- @C.PcmEncoding int pcmEncoding,
- int encoderDelay,
- int encoderPadding,
- // Text specific.
- int accessibilityChannel,
- // Provided by source.
- @Nullable Class extends ExoMediaCrypto> exoMediaCryptoType) {
- this.id = id;
- this.label = label;
- this.language = Util.normalizeLanguageCode(language);
- this.selectionFlags = selectionFlags;
- this.roleFlags = roleFlags;
- this.averageBitrate = averageBitrate;
- this.peakBitrate = peakBitrate;
- this.bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
- this.codecs = codecs;
- this.metadata = metadata;
+ private Format(Builder builder) {
+ id = builder.id;
+ label = builder.label;
+ language = Util.normalizeLanguageCode(builder.language);
+ selectionFlags = builder.selectionFlags;
+ roleFlags = builder.roleFlags;
+ averageBitrate = builder.averageBitrate;
+ peakBitrate = builder.peakBitrate;
+ bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
+ codecs = builder.codecs;
+ metadata = builder.metadata;
// Container specific.
- this.containerMimeType = containerMimeType;
+ containerMimeType = builder.containerMimeType;
// Sample specific.
- this.sampleMimeType = sampleMimeType;
- this.maxInputSize = maxInputSize;
- this.initializationData =
- initializationData == null ? Collections.emptyList() : initializationData;
- this.drmInitData = drmInitData;
- this.subsampleOffsetUs = subsampleOffsetUs;
+ sampleMimeType = builder.sampleMimeType;
+ maxInputSize = builder.maxInputSize;
+ initializationData =
+ builder.initializationData == null ? Collections.emptyList() : builder.initializationData;
+ drmInitData = builder.drmInitData;
+ subsampleOffsetUs = builder.subsampleOffsetUs;
// Video specific.
- this.width = width;
- this.height = height;
- this.frameRate = frameRate;
- this.rotationDegrees = rotationDegrees == NO_VALUE ? 0 : rotationDegrees;
- this.pixelWidthHeightRatio = pixelWidthHeightRatio == NO_VALUE ? 1 : pixelWidthHeightRatio;
- this.projectionData = projectionData;
- this.stereoMode = stereoMode;
- this.colorInfo = colorInfo;
+ width = builder.width;
+ height = builder.height;
+ frameRate = builder.frameRate;
+ rotationDegrees = builder.rotationDegrees == NO_VALUE ? 0 : builder.rotationDegrees;
+ pixelWidthHeightRatio =
+ builder.pixelWidthHeightRatio == NO_VALUE ? 1 : builder.pixelWidthHeightRatio;
+ projectionData = builder.projectionData;
+ stereoMode = builder.stereoMode;
+ colorInfo = builder.colorInfo;
// Audio specific.
- this.channelCount = channelCount;
- this.sampleRate = sampleRate;
- this.pcmEncoding = pcmEncoding;
- this.encoderDelay = encoderDelay == NO_VALUE ? 0 : encoderDelay;
- this.encoderPadding = encoderPadding == NO_VALUE ? 0 : encoderPadding;
+ channelCount = builder.channelCount;
+ sampleRate = builder.sampleRate;
+ pcmEncoding = builder.pcmEncoding;
+ encoderDelay = builder.encoderDelay == NO_VALUE ? 0 : builder.encoderDelay;
+ encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding;
// Text specific.
- this.accessibilityChannel = accessibilityChannel;
+ accessibilityChannel = builder.accessibilityChannel;
// Provided by source.
- if (exoMediaCryptoType == null && drmInitData != null) {
+ if (builder.exoMediaCryptoType == null && drmInitData != null) {
// Encrypted content must always have a non-null exoMediaCryptoType.
exoMediaCryptoType = UnsupportedMediaCrypto.class;
+ } else {
+ exoMediaCryptoType = builder.exoMediaCryptoType;
}
- this.exoMediaCryptoType = exoMediaCryptoType;
}
// Some fields are deprecated but they're still assigned below.
diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java
index 6edbecf1ea..4831ec59e2 100644
--- a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java
+++ b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.util;
+import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -219,11 +220,12 @@ public final class NalUnitUtil {
* Returns whether the NAL unit with the specified header contains supplemental enhancement
* information.
*
- * @param mimeType The sample MIME type.
+ * @param mimeType The sample MIME type, or {@code null} if unknown.
* @param nalUnitHeaderFirstByte The first byte of nal_unit().
- * @return Whether the NAL unit with the specified header is an SEI NAL unit.
+ * @return Whether the NAL unit with the specified header is an SEI NAL unit. False is returned if
+ * the {@code MimeType} is {@code null}.
*/
- public static boolean isNalUnitSei(String mimeType, byte nalUnitHeaderFirstByte) {
+ public static boolean isNalUnitSei(@Nullable String mimeType, byte nalUnitHeaderFirstByte) {
return (MimeTypes.VIDEO_H264.equals(mimeType)
&& (nalUnitHeaderFirstByte & 0x1F) == H264_NAL_UNIT_TYPE_SEI)
|| (MimeTypes.VIDEO_H265.equals(mimeType)
diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
index 213745f93d..7fa26a94f4 100644
--- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
+++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
@@ -405,6 +405,21 @@ public final class Util {
return concatenation;
}
+ /**
+ * Copies the contents of {@code list} into {@code array}.
+ *
+ * {@code list.size()} must be the same as {@code array.length} to ensure the contents can be
+ * copied into {@code array} without leaving any nulls at the end.
+ *
+ * @param list The list to copy items from.
+ * @param array The array to copy items to.
+ */
+ @SuppressWarnings("nullness:toArray.nullable.elements.not.newarray")
+ public static void nullSafeListToArray(List list, T[] array) {
+ Assertions.checkState(list.size() == array.length);
+ list.toArray(array);
+ }
+
/**
* Creates a {@link Handler} on the current {@link Looper} thread.
*
@@ -2362,7 +2377,7 @@ public final class Util {
case TelephonyManager.NETWORK_TYPE_LTE:
return C.NETWORK_TYPE_4G;
case TelephonyManager.NETWORK_TYPE_NR:
- return C.NETWORK_TYPE_5G;
+ return SDK_INT >= 29 ? C.NETWORK_TYPE_5G : C.NETWORK_TYPE_UNKNOWN;
case TelephonyManager.NETWORK_TYPE_IWLAN:
return C.NETWORK_TYPE_WIFI;
case TelephonyManager.NETWORK_TYPE_GSM:
diff --git a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java
index 608d1bb104..1ad888c868 100644
--- a/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java
+++ b/library/common/src/test/java/com/google/android/exoplayer2/FormatTest.java
@@ -90,37 +90,38 @@ public final class FormatTest {
C.COLOR_TRANSFER_SDR,
new byte[] {1, 2, 3, 4, 5, 6, 7});
- return new Format(
- "id",
- "label",
- "language",
- C.SELECTION_FLAG_DEFAULT,
- C.ROLE_FLAG_MAIN,
- /* averageBitrate= */ 1024,
- /* peakBitrate= */ 2048,
- "codec",
- metadata,
- /* containerMimeType= */ MimeTypes.VIDEO_MP4,
- /* sampleMimeType= */ MimeTypes.VIDEO_H264,
- /* maxInputSize= */ 5000,
- initializationData,
- drmInitData,
- Format.OFFSET_SAMPLE_RELATIVE,
- /* width= */ 1920,
- /* height= */ 1080,
- /* frameRate= */ 24,
- /* rotationDegrees= */ 90,
- /* pixelWidthHeightRatio= */ 4,
- projectionData,
- C.STEREO_MODE_TOP_BOTTOM,
- colorInfo,
- /* channelCount= */ 6,
- /* sampleRate= */ 44100,
- C.ENCODING_PCM_24BIT,
- /* encoderDelay= */ 1001,
- /* encoderPadding= */ 1002,
- /* accessibilityChannel= */ 2,
- /* exoMediaCryptoType= */ ExoMediaCrypto.class);
+ return new Format.Builder()
+ .setId("id")
+ .setLabel("label")
+ .setLanguage("language")
+ .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
+ .setRoleFlags(C.ROLE_FLAG_MAIN)
+ .setAverageBitrate(1024)
+ .setPeakBitrate(2048)
+ .setCodecs("codec")
+ .setMetadata(metadata)
+ .setContainerMimeType(MimeTypes.VIDEO_MP4)
+ .setSampleMimeType(MimeTypes.VIDEO_H264)
+ .setMaxInputSize(5000)
+ .setInitializationData(initializationData)
+ .setDrmInitData(drmInitData)
+ .setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE)
+ .setWidth(1920)
+ .setHeight(1080)
+ .setFrameRate(24)
+ .setRotationDegrees(90)
+ .setPixelWidthHeightRatio(4)
+ .setProjectionData(projectionData)
+ .setStereoMode(C.STEREO_MODE_TOP_BOTTOM)
+ .setColorInfo(colorInfo)
+ .setChannelCount(6)
+ .setSampleRate(44100)
+ .setPcmEncoding(C.ENCODING_PCM_24BIT)
+ .setEncoderDelay(1001)
+ .setEncoderPadding(1002)
+ .setAccessibilityChannel(2)
+ .setExoMediaCryptoType(ExoMediaCrypto.class)
+ .build();
}
/** Generates an array of random bytes with the specified length. */
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java
index 6276f3bca8..9ee1846fc1 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultMediaClock.java
@@ -27,20 +27,20 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
*/
/* package */ final class DefaultMediaClock implements MediaClock {
- /** Listener interface to be notified of changes to the active playback speed. */
- public interface PlaybackSpeedListener {
+ /** Listener interface to be notified of changes to the active playback parameters. */
+ public interface PlaybackParametersListener {
/**
- * Called when the active playback speed changed. Will not be called for {@link
- * #setPlaybackSpeed(float)}.
+ * Called when the active playback parameters changed. Will not be called for {@link
+ * #setPlaybackParameters(PlaybackParameters)}.
*
- * @param newPlaybackSpeed The newly active playback speed.
+ * @param newPlaybackParameters The newly active playback parameters.
*/
- void onPlaybackSpeedChanged(float newPlaybackSpeed);
+ void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters);
}
private final StandaloneMediaClock standaloneClock;
- private final PlaybackSpeedListener listener;
+ private final PlaybackParametersListener listener;
@Nullable private Renderer rendererClockSource;
@Nullable private MediaClock rendererClock;
@@ -48,13 +48,13 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
private boolean standaloneClockIsStarted;
/**
- * Creates a new instance with listener for playback speed changes and a {@link Clock} to use for
- * the standalone clock implementation.
+ * Creates a new instance with a listener for playback parameters changes and a {@link Clock} to
+ * use for the standalone clock implementation.
*
- * @param listener A {@link PlaybackSpeedListener} to listen for playback speed changes.
+ * @param listener A {@link PlaybackParametersListener} to listen for playback parameters changes.
* @param clock A {@link Clock}.
*/
- public DefaultMediaClock(PlaybackSpeedListener listener, Clock clock) {
+ public DefaultMediaClock(PlaybackParametersListener listener, Clock clock) {
this.listener = listener;
this.standaloneClock = new StandaloneMediaClock(clock);
isUsingStandaloneClock = true;
@@ -102,7 +102,7 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
}
this.rendererClock = rendererMediaClock;
this.rendererClockSource = renderer;
- rendererClock.setPlaybackSpeed(standaloneClock.getPlaybackSpeed());
+ rendererClock.setPlaybackParameters(standaloneClock.getPlaybackParameters());
}
}
@@ -140,19 +140,19 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
if (rendererClock != null) {
- rendererClock.setPlaybackSpeed(playbackSpeed);
- playbackSpeed = rendererClock.getPlaybackSpeed();
+ rendererClock.setPlaybackParameters(playbackParameters);
+ playbackParameters = rendererClock.getPlaybackParameters();
}
- standaloneClock.setPlaybackSpeed(playbackSpeed);
+ standaloneClock.setPlaybackParameters(playbackParameters);
}
@Override
- public float getPlaybackSpeed() {
+ public PlaybackParameters getPlaybackParameters() {
return rendererClock != null
- ? rendererClock.getPlaybackSpeed()
- : standaloneClock.getPlaybackSpeed();
+ ? rendererClock.getPlaybackParameters()
+ : standaloneClock.getPlaybackParameters();
}
private void syncClocks(boolean isReadingAhead) {
@@ -180,10 +180,10 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
}
// Continuously sync stand-alone clock to renderer clock so that it can take over if needed.
standaloneClock.resetPosition(rendererClockPositionUs);
- float playbackSpeed = rendererClock.getPlaybackSpeed();
- if (playbackSpeed != standaloneClock.getPlaybackSpeed()) {
- standaloneClock.setPlaybackSpeed(playbackSpeed);
- listener.onPlaybackSpeedChanged(playbackSpeed);
+ PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters();
+ if (!playbackParameters.equals(standaloneClock.getPlaybackParameters())) {
+ standaloneClock.setPlaybackParameters(playbackParameters);
+ listener.onPlaybackParametersChanged(playbackParameters);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
index 76be46eb40..b1f5736465 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
@@ -619,32 +619,17 @@ import java.util.concurrent.TimeoutException;
/* seekProcessed= */ true);
}
- /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
- @SuppressWarnings("deprecation")
- @Deprecated
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
- setPlaybackSpeed(
- playbackParameters != null ? playbackParameters.speed : Player.DEFAULT_PLAYBACK_SPEED);
- }
-
- /** @deprecated Use {@link #getPlaybackSpeed()} instead. */
- @SuppressWarnings("deprecation")
- @Deprecated
- @Override
- public PlaybackParameters getPlaybackParameters() {
- return new PlaybackParameters(playbackInfo.playbackSpeed);
- }
-
- @Override
- public void setPlaybackSpeed(float playbackSpeed) {
- checkState(playbackSpeed > 0);
- if (playbackInfo.playbackSpeed == playbackSpeed) {
+ if (playbackParameters == null) {
+ playbackParameters = PlaybackParameters.DEFAULT;
+ }
+ if (playbackInfo.playbackParameters.equals(playbackParameters)) {
return;
}
- PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackSpeed(playbackSpeed);
+ PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
pendingOperationAcks++;
- internalPlayer.setPlaybackSpeed(playbackSpeed);
+ internalPlayer.setPlaybackParameters(playbackParameters);
updatePlaybackInfo(
newPlaybackInfo,
/* positionDiscontinuity= */ false,
@@ -655,8 +640,8 @@ import java.util.concurrent.TimeoutException;
}
@Override
- public float getPlaybackSpeed() {
- return playbackInfo.playbackSpeed;
+ public PlaybackParameters getPlaybackParameters() {
+ return playbackInfo.playbackParameters;
}
@Override
@@ -1366,7 +1351,7 @@ import java.util.concurrent.TimeoutException;
private final boolean playWhenReadyChanged;
private final boolean playbackSuppressionReasonChanged;
private final boolean isPlayingChanged;
- private final boolean playbackSpeedChanged;
+ private final boolean playbackParametersChanged;
private final boolean offloadSchedulingEnabledChanged;
public PlaybackInfoUpdate(
@@ -1405,7 +1390,8 @@ import java.util.concurrent.TimeoutException;
playbackSuppressionReasonChanged =
previousPlaybackInfo.playbackSuppressionReason != playbackInfo.playbackSuppressionReason;
isPlayingChanged = isPlaying(previousPlaybackInfo) != isPlaying(playbackInfo);
- playbackSpeedChanged = previousPlaybackInfo.playbackSpeed != playbackInfo.playbackSpeed;
+ playbackParametersChanged =
+ !previousPlaybackInfo.playbackParameters.equals(playbackInfo.playbackParameters);
offloadSchedulingEnabledChanged =
previousPlaybackInfo.offloadSchedulingEnabled != playbackInfo.offloadSchedulingEnabled;
}
@@ -1473,13 +1459,11 @@ import java.util.concurrent.TimeoutException;
invokeAll(
listenerSnapshot, listener -> listener.onIsPlayingChanged(isPlaying(playbackInfo)));
}
- if (playbackSpeedChanged) {
- PlaybackParameters playbackParameters = new PlaybackParameters(playbackInfo.playbackSpeed);
+ if (playbackParametersChanged) {
invokeAll(
listenerSnapshot,
listener -> {
- listener.onPlaybackSpeedChanged(playbackInfo.playbackSpeed);
- listener.onPlaybackParametersChanged(playbackParameters);
+ listener.onPlaybackParametersChanged(playbackInfo.playbackParameters);
});
}
if (seekProcessed) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
index 77637f1ef0..9739680e79 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
@@ -27,7 +27,7 @@ import android.os.SystemClock;
import android.util.Pair;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.DefaultMediaClock.PlaybackSpeedListener;
+import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParametersListener;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason;
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
@@ -61,7 +61,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriod.Callback,
TrackSelector.InvalidationListener,
MediaSourceList.MediaSourceListInfoRefreshListener,
- PlaybackSpeedListener,
+ PlaybackParametersListener,
PlayerMessage.Sender {
private static final String TAG = "ExoPlayerImplInternal";
@@ -121,7 +121,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_SET_PLAY_WHEN_READY = 1;
private static final int MSG_DO_SOME_WORK = 2;
private static final int MSG_SEEK_TO = 3;
- private static final int MSG_SET_PLAYBACK_SPEED = 4;
+ private static final int MSG_SET_PLAYBACK_PARAMETERS = 4;
private static final int MSG_SET_SEEK_PARAMETERS = 5;
private static final int MSG_STOP = 6;
private static final int MSG_RELEASE = 7;
@@ -133,7 +133,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_SET_FOREGROUND_MODE = 13;
private static final int MSG_SEND_MESSAGE = 14;
private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15;
- private static final int MSG_PLAYBACK_SPEED_CHANGED_INTERNAL = 16;
+ private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16;
private static final int MSG_SET_MEDIA_SOURCES = 17;
private static final int MSG_ADD_MEDIA_SOURCES = 18;
private static final int MSG_MOVE_MEDIA_SOURCES = 19;
@@ -163,6 +163,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final BandwidthMeter bandwidthMeter;
private final HandlerWrapper handler;
private final HandlerThread internalPlaybackThread;
+ private final Looper playbackLooper;
private final Timeline.Window window;
private final Timeline.Period period;
private final long backBufferDurationUs;
@@ -252,7 +253,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
// not normally change to this priority" is incorrect.
internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
- handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
+ playbackLooper = internalPlaybackThread.getLooper();
+ handler = clock.createHandler(playbackLooper, this);
}
public void experimentalSetReleaseTimeoutMs(long releaseTimeoutMs) {
@@ -301,8 +303,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
.sendToTarget();
}
- public void setPlaybackSpeed(float playbackSpeed) {
- handler.obtainMessage(MSG_SET_PLAYBACK_SPEED, playbackSpeed).sendToTarget();
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget();
}
public void setSeekParameters(SeekParameters seekParameters) {
@@ -403,7 +405,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
public Looper getPlaybackLooper() {
- return internalPlaybackThread.getLooper();
+ return playbackLooper;
}
// Playlist.PlaylistInfoRefreshListener implementation.
@@ -432,11 +434,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
}
- // DefaultMediaClock.PlaybackSpeedListener implementation.
+ // DefaultMediaClock.PlaybackParametersListener implementation.
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
- sendPlaybackSpeedChangedInternal(playbackSpeed, /* acknowledgeCommand= */ false);
+ public void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters) {
+ sendPlaybackParametersChangedInternal(newPlaybackParameters, /* acknowledgeCommand= */ false);
}
// Handler.Callback implementation.
@@ -467,8 +469,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_SEEK_TO:
seekToInternal((SeekPosition) msg.obj);
break;
- case MSG_SET_PLAYBACK_SPEED:
- setPlaybackSpeedInternal((Float) msg.obj);
+ case MSG_SET_PLAYBACK_PARAMETERS:
+ setPlaybackParametersInternal((PlaybackParameters) msg.obj);
break;
case MSG_SET_SEEK_PARAMETERS:
setSeekParametersInternal((SeekParameters) msg.obj);
@@ -489,8 +491,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_TRACK_SELECTION_INVALIDATED:
reselectTracksInternal();
break;
- case MSG_PLAYBACK_SPEED_CHANGED_INTERNAL:
- handlePlaybackSpeed((Float) msg.obj, /* acknowledgeCommand= */ msg.arg1 != 0);
+ case MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL:
+ handlePlaybackParameters(
+ (PlaybackParameters) msg.obj, /* acknowledgeCommand= */ msg.arg1 != 0);
break;
case MSG_SEND_MESSAGE:
sendMessageInternal((PlayerMessage) msg.obj);
@@ -733,11 +736,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void setPauseAtEndOfWindowInternal(boolean pauseAtEndOfWindow)
throws ExoPlaybackException {
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
- if (queue.getReadingPeriod() != queue.getPlayingPeriod()) {
- seekToCurrentPosition(/* sendDiscontinuity= */ true);
- }
resetPendingPauseAtEndOfPeriod();
- handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
+ if (pendingPauseAtEndOfPeriod && queue.getReadingPeriod() != queue.getPlayingPeriod()) {
+ // When pausing is required, we need to set the streams of the playing period final. If we
+ // already started reading the next period, we need to flush the renderers.
+ seekToCurrentPosition(/* sendDiscontinuity= */ true);
+ handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
+ }
}
private void setOffloadSchedulingEnabledInternal(boolean offloadSchedulingEnabled) {
@@ -1182,9 +1187,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
notifyTrackSelectionDiscontinuity();
}
- private void setPlaybackSpeedInternal(float playbackSpeed) {
- mediaClock.setPlaybackSpeed(playbackSpeed);
- sendPlaybackSpeedChangedInternal(mediaClock.getPlaybackSpeed(), /* acknowledgeCommand= */ true);
+ private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
+ mediaClock.setPlaybackParameters(playbackParameters);
+ sendPlaybackParametersChangedInternal(
+ mediaClock.getPlaybackParameters(), /* acknowledgeCommand= */ true);
}
private void setSeekParametersInternal(SeekParameters seekParameters) {
@@ -1301,7 +1307,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mediaPeriodId,
playbackInfo.playWhenReady,
playbackInfo.playbackSuppressionReason,
- playbackInfo.playbackSpeed,
+ playbackInfo.playbackParameters,
startPositionUs,
/* totalBufferedDurationUs= */ 0,
startPositionUs,
@@ -1361,7 +1367,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
- if (message.getHandler().getLooper() == handler.getLooper()) {
+ if (message.getHandler().getLooper() == playbackLooper) {
deliverMessage(message);
if (playbackInfo.playbackState == Player.STATE_READY
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
@@ -1506,7 +1512,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void reselectTracksInternal() throws ExoPlaybackException {
- float playbackSpeed = mediaClock.getPlaybackSpeed();
+ float playbackSpeed = mediaClock.getPlaybackParameters().speed;
// Reselect tracks on each period in turn, until the selection changes.
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
@@ -1624,7 +1630,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd
|| loadControl.shouldStartPlayback(
- getTotalBufferedDurationUs(), mediaClock.getPlaybackSpeed(), rebuffering);
+ getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
}
private boolean isTimelineReady() {
@@ -1960,7 +1966,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
return;
}
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
- loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackSpeed(), playbackInfo.timeline);
+ loadingPeriodHolder.handlePrepared(
+ mediaClock.getPlaybackParameters().speed, playbackInfo.timeline);
updateLoadControlTrackSelection(
loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult());
if (loadingPeriodHolder == queue.getPlayingPeriod()) {
@@ -1985,14 +1992,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
maybeContinueLoading();
}
- private void handlePlaybackSpeed(float playbackSpeed, boolean acknowledgeCommand)
+ private void handlePlaybackParameters(
+ PlaybackParameters playbackParameters, boolean acknowledgeCommand)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(acknowledgeCommand ? 1 : 0);
- playbackInfo = playbackInfo.copyWithPlaybackSpeed(playbackSpeed);
- updateTrackSelectionPlaybackSpeed(playbackSpeed);
+ playbackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
+ updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
for (Renderer renderer : renderers) {
if (renderer != null) {
- renderer.setOperatingRate(playbackSpeed);
+ renderer.setOperatingRate(playbackParameters.speed);
}
}
}
@@ -2018,7 +2026,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
: loadingPeriodHolder.toPeriodTime(rendererPositionUs)
- loadingPeriodHolder.info.startPositionUs;
return loadControl.shouldContinueLoading(
- playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackSpeed());
+ playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
}
private boolean isLoadingPossible() {
@@ -2194,10 +2202,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
}
- private void sendPlaybackSpeedChangedInternal(float playbackSpeed, boolean acknowledgeCommand) {
+ private void sendPlaybackParametersChangedInternal(
+ PlaybackParameters playbackParameters, boolean acknowledgeCommand) {
handler
.obtainMessage(
- MSG_PLAYBACK_SPEED_CHANGED_INTERNAL, acknowledgeCommand ? 1 : 0, 0, playbackSpeed)
+ MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL,
+ acknowledgeCommand ? 1 : 0,
+ 0,
+ playbackParameters)
.sendToTarget();
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java
index 57295c54fc..9fb6563005 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java
@@ -63,8 +63,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public final boolean playWhenReady;
/** Reason why playback is suppressed even though {@link #playWhenReady} is {@code true}. */
@PlaybackSuppressionReason public final int playbackSuppressionReason;
- /** The playback speed. */
- public final float playbackSpeed;
+ /** The playback parameters. */
+ public final PlaybackParameters playbackParameters;
/** Whether offload scheduling is enabled for the main player loop. */
public final boolean offloadSchedulingEnabled;
@@ -105,7 +105,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
PLACEHOLDER_MEDIA_PERIOD_ID,
/* playWhenReady= */ false,
Player.PLAYBACK_SUPPRESSION_REASON_NONE,
- Player.DEFAULT_PLAYBACK_SPEED,
+ PlaybackParameters.DEFAULT,
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0,
@@ -119,10 +119,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
* @param periodId See {@link #periodId}.
* @param requestedContentPositionUs See {@link #requestedContentPositionUs}.
* @param playbackState See {@link #playbackState}.
+ * @param playbackError See {@link #playbackError}.
* @param isLoading See {@link #isLoading}.
* @param trackGroups See {@link #trackGroups}.
* @param trackSelectorResult See {@link #trackSelectorResult}.
* @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}.
+ * @param playWhenReady See {@link #playWhenReady}.
+ * @param playbackSuppressionReason See {@link #playbackSuppressionReason}.
+ * @param playbackParameters See {@link #playbackParameters}.
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
* @param positionUs See {@link #positionUs}.
@@ -140,7 +144,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
MediaPeriodId loadingMediaPeriodId,
boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason,
- float playbackSpeed,
+ PlaybackParameters playbackParameters,
long bufferedPositionUs,
long totalBufferedDurationUs,
long positionUs,
@@ -156,7 +160,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
this.loadingMediaPeriodId = loadingMediaPeriodId;
this.playWhenReady = playWhenReady;
this.playbackSuppressionReason = playbackSuppressionReason;
- this.playbackSpeed = playbackSpeed;
+ this.playbackParameters = playbackParameters;
this.bufferedPositionUs = bufferedPositionUs;
this.totalBufferedDurationUs = totalBufferedDurationUs;
this.positionUs = positionUs;
@@ -201,7 +205,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -228,7 +232,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -255,7 +259,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -282,7 +286,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -309,7 +313,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -336,7 +340,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -367,7 +371,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -375,13 +379,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
}
/**
- * Copies playback info with new playback speed.
+ * Copies playback info with new playback parameters.
*
- * @param playbackSpeed New playback speed. See {@link #playbackSpeed}.
- * @return Copied playback info with new playback speed.
+ * @param playbackParameters New playback parameters. See {@link #playbackParameters}.
+ * @return Copied playback info with new playback parameters.
*/
@CheckResult
- public PlaybackInfo copyWithPlaybackSpeed(float playbackSpeed) {
+ public PlaybackInfo copyWithPlaybackParameters(PlaybackParameters playbackParameters) {
return new PlaybackInfo(
timeline,
periodId,
@@ -394,7 +398,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
@@ -422,7 +426,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
loadingMediaPeriodId,
playWhenReady,
playbackSuppressionReason,
- playbackSpeed,
+ playbackParameters,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs,
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
index afa0a7ebc4..7dcd6f80aa 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java
@@ -17,13 +17,9 @@ package com.google.android.exoplayer2;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
-/**
- * @deprecated Use {@link Player#setPlaybackSpeed(float)} and {@link
- * Player.AudioComponent#setSkipSilenceEnabled(boolean)} instead.
- */
-@SuppressWarnings("deprecation")
-@Deprecated
+/** Parameters that apply to playback, including speed setting. */
public final class PlaybackParameters {
/** The default playback parameters: real-time playback with no silence skipping. */
@@ -32,16 +28,34 @@ public final class PlaybackParameters {
/** The factor by which playback will be sped up. */
public final float speed;
+ /** The factor by which pitch will be shifted. */
+ public final float pitch;
+
private final int scaledUsPerMs;
/**
- * Creates new playback parameters that set the playback speed.
+ * Creates new playback parameters that set the playback speed. The pitch of audio will not be
+ * adjusted, so the effect is to time-stretch the audio.
*
* @param speed The factor by which playback will be sped up. Must be greater than zero.
*/
public PlaybackParameters(float speed) {
+ this(speed, /* pitch= */ 1f);
+ }
+
+ /**
+ * Creates new playback parameters that set the playback speed/pitch.
+ *
+ * @param speed The factor by which playback will be sped up. Must be greater than zero.
+ * @param pitch The factor by which the pitch of audio will be adjusted. Must be greater than
+ * zero. Useful values are {@code 1} (to time-stretch audio) and the same value as passed in
+ * as the {@code speed} (to resample audio, which is useful for slow-motion videos).
+ */
+ public PlaybackParameters(float speed, float pitch) {
Assertions.checkArgument(speed > 0);
+ Assertions.checkArgument(pitch > 0);
this.speed = speed;
+ this.pitch = pitch;
scaledUsPerMs = Math.round(speed * 1000f);
}
@@ -65,11 +79,19 @@ public final class PlaybackParameters {
return false;
}
PlaybackParameters other = (PlaybackParameters) obj;
- return this.speed == other.speed;
+ return this.speed == other.speed && this.pitch == other.pitch;
}
@Override
public int hashCode() {
- return Float.floatToRawIntBits(speed);
+ int result = 17;
+ result = 31 * result + Float.floatToRawIntBits(speed);
+ result = 31 * result + Float.floatToRawIntBits(pitch);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return Util.formatInvariant("PlaybackParameters(speed=%.2f, pitch=%.2f)", speed, pitch);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java
index 9344345375..490022de93 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java
@@ -583,21 +583,15 @@ public interface Player {
default void onPositionDiscontinuity(@DiscontinuityReason int reason) {}
/**
- * @deprecated Use {@link #onPlaybackSpeedChanged(float)} and {@link
- * AudioListener#onSkipSilenceEnabledChanged(boolean)} instead.
+ * Called when the current playback parameters change. The playback parameters may change due to
+ * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change
+ * them (for example, if audio playback switches to passthrough or offload mode, where speed
+ * adjustment is no longer possible).
+ *
+ * @param playbackParameters The playback parameters.
*/
- @SuppressWarnings("deprecation")
- @Deprecated
default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}
- /**
- * Called when the current playback speed changes. The normal playback speed is 1. The speed may
- * change due to a call to {@link #setPlaybackSpeed(float)}, or the player itself may change it
- * (for example, if audio playback switches to passthrough mode, where speed adjustment is no
- * longer possible).
- */
- default void onPlaybackSpeedChanged(float playbackSpeed) {}
-
/**
* @deprecated Seeks are processed without delay. Listen to {@link
* #onPositionDiscontinuity(int)} with reason {@link #DISCONTINUITY_REASON_SEEK} instead.
@@ -810,9 +804,6 @@ public interface Player {
*/
int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3;
- /** The default playback speed. */
- float DEFAULT_PLAYBACK_SPEED = 1.0f;
-
/** Returns the component of this player for audio output, or null if audio is not supported. */
@Nullable
AudioComponent getAudioComponent();
@@ -1161,39 +1152,24 @@ public interface Player {
void next();
/**
- * @deprecated Use {@link #setPlaybackSpeed(float)} or {@link
- * AudioComponent#setSkipSilenceEnabled(boolean)} instead.
+ * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the
+ * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment.
+ *
+ * Playback parameters changes may cause the player to buffer. {@link
+ * EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the
+ * currently active playback parameters change.
+ *
+ * @param playbackParameters The playback parameters, or {@code null} to use the defaults.
*/
- @SuppressWarnings("deprecation")
- @Deprecated
void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters);
/**
- * @deprecated Use {@link #getPlaybackSpeed()} or {@link AudioComponent#getSkipSilenceEnabled()}
- * instead.
+ * Returns the currently active playback parameters.
+ *
+ * @see EventListener#onPlaybackParametersChanged(PlaybackParameters)
*/
- @SuppressWarnings("deprecation")
- @Deprecated
PlaybackParameters getPlaybackParameters();
- /**
- * Attempts to set the playback speed.
- *
- *
Playback speed changes may cause the player to buffer. {@link
- * EventListener#onPlaybackSpeedChanged(float)} will be called whenever the currently active
- * playback speed change.
- *
- * @param playbackSpeed The playback speed.
- */
- void setPlaybackSpeed(float playbackSpeed);
-
- /**
- * Returns the currently active playback speed.
- *
- * @see EventListener#onPlaybackSpeedChanged(float)
- */
- float getPlaybackSpeed();
-
/**
* Stops playback without resetting the player. Use {@link #pause()} rather than this method if
* the intention is to pause playback.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
index 00c1e0bcc5..a43973b31c 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
@@ -1030,18 +1030,23 @@ public class SimpleExoPlayer extends BasePlayer
this.priorityTaskManager = priorityTaskManager;
}
- /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
+ /**
+ * Sets the {@link PlaybackParams} governing audio playback.
+ *
+ * @param params The {@link PlaybackParams}, or null to clear any previously set parameters.
+ * @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}.
+ */
@Deprecated
@RequiresApi(23)
public void setPlaybackParams(@Nullable PlaybackParams params) {
- float playbackSpeed;
+ PlaybackParameters playbackParameters;
if (params != null) {
params.allowDefaults();
- playbackSpeed = params.getSpeed();
+ playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch());
} else {
- playbackSpeed = 1.0f;
+ playbackParameters = null;
}
- setPlaybackSpeed(playbackSpeed);
+ setPlaybackParameters(playbackParameters);
}
/** Returns the video format currently being played, or null if no video is being played. */
@@ -1623,39 +1628,18 @@ public class SimpleExoPlayer extends BasePlayer
player.seekTo(windowIndex, positionMs);
}
- /**
- * @deprecated Use {@link #setPlaybackSpeed(float)} and {@link #setSkipSilenceEnabled(boolean)}
- * instead.
- */
- @SuppressWarnings("deprecation")
- @Deprecated
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
verifyApplicationThread();
player.setPlaybackParameters(playbackParameters);
}
- /** @deprecated Use {@link #getPlaybackSpeed()} and {@link #getSkipSilenceEnabled()} instead. */
- @SuppressWarnings("deprecation")
- @Deprecated
@Override
public PlaybackParameters getPlaybackParameters() {
verifyApplicationThread();
return player.getPlaybackParameters();
}
- @Override
- public void setPlaybackSpeed(float playbackSpeed) {
- verifyApplicationThread();
- player.setPlaybackSpeed(playbackSpeed);
- }
-
- @Override
- public float getPlaybackSpeed() {
- verifyApplicationThread();
- return player.getPlaybackSpeed();
- }
-
@Override
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
verifyApplicationThread();
@@ -2231,6 +2215,13 @@ public class SimpleExoPlayer extends BasePlayer
}
}
+ @Override
+ public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
+ for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
+ audioDebugListener.onAudioPositionAdvancing(playoutStartSystemTimeMs);
+ }
+ }
+
@Override
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
index 7c170742d7..35f3099dc9 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java
@@ -205,6 +205,14 @@ public class AnalyticsCollector
}
}
+ @Override
+ public final void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
+ EventTime eventTime = generateReadingMediaPeriodEventTime();
+ for (AnalyticsListener listener : listeners) {
+ listener.onAudioPositionAdvancing(eventTime, playoutStartSystemTimeMs);
+ }
+ }
+
@Override
public final void onAudioUnderrun(
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
@@ -544,12 +552,6 @@ public class AnalyticsCollector
}
}
- /**
- * @deprecated Use {@link #onPlaybackSpeedChanged(float)} and {@link
- * #onSkipSilenceEnabledChanged(boolean)} instead.
- */
- @SuppressWarnings("deprecation")
- @Deprecated
@Override
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
@@ -558,14 +560,6 @@ public class AnalyticsCollector
}
}
- @Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
- EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
- for (AnalyticsListener listener : listeners) {
- listener.onPlaybackSpeedChanged(eventTime, playbackSpeed);
- }
- }
-
@SuppressWarnings("deprecation")
@Override
public final void onSeekProcessed() {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
index f01d11ec25..2e26019541 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java
@@ -279,21 +279,13 @@ public interface AnalyticsListener {
default void onSeekProcessed(EventTime eventTime) {}
/**
- * @deprecated Use {@link #onPlaybackSpeedChanged(EventTime, float)} and {@link
- * #onSkipSilenceEnabledChanged(EventTime, boolean)} instead.
- */
- @SuppressWarnings("deprecation")
- @Deprecated
- default void onPlaybackParametersChanged(
- EventTime eventTime, PlaybackParameters playbackParameters) {}
-
- /**
- * Called when the playback speed changes.
+ * Called when the playback parameters changed.
*
* @param eventTime The event time.
- * @param playbackSpeed The playback speed.
+ * @param playbackParameters The new playback parameters.
*/
- default void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {}
+ default void onPlaybackParametersChanged(
+ EventTime eventTime, PlaybackParameters playbackParameters) {}
/**
* Called when the repeat mode changed.
@@ -479,6 +471,16 @@ public interface AnalyticsListener {
*/
default void onAudioInputFormatChanged(EventTime eventTime, Format format) {}
+ /**
+ * Called when the audio position has increased for the first time since the last pause or
+ * position reset.
+ *
+ * @param eventTime The event time.
+ * @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
+ * which playout started.
+ */
+ default void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {}
+
/**
* Called when an audio underrun occurs.
*
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java
index 1efb072ef0..ab137f98e1 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java
@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
@@ -334,8 +335,9 @@ public final class PlaybackStatsListener
}
@Override
- public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
- this.playbackSpeed = playbackSpeed;
+ public void onPlaybackParametersChanged(
+ EventTime eventTime, PlaybackParameters playbackParameters) {
+ playbackSpeed = playbackParameters.speed;
maybeAddSession(eventTime);
for (PlaybackStatsTracker tracker : playbackStatsTrackers.values()) {
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
index c366f27f81..f921141f24 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java
@@ -65,6 +65,15 @@ public interface AudioRendererEventListener {
*/
default void onAudioInputFormatChanged(Format format) {}
+ /**
+ * Called when the audio position has increased for the first time since the last pause or
+ * position reset.
+ *
+ * @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
+ * which playout started.
+ */
+ default void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {}
+
/**
* Called when an audio underrun occurs.
*
@@ -89,7 +98,7 @@ public interface AudioRendererEventListener {
*/
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
- /** Dispatches events to a {@link AudioRendererEventListener}. */
+ /** Dispatches events to an {@link AudioRendererEventListener}. */
final class EventDispatcher {
@Nullable private final Handler handler;
@@ -106,20 +115,16 @@ public interface AudioRendererEventListener {
this.listener = listener;
}
- /**
- * Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}.
- */
- public void enabled(final DecoderCounters decoderCounters) {
+ /** Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}. */
+ public void enabled(DecoderCounters decoderCounters) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioEnabled(decoderCounters));
}
}
- /**
- * Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}.
- */
- public void decoderInitialized(final String decoderName,
- final long initializedTimestampMs, final long initializationDurationMs) {
+ /** Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}. */
+ public void decoderInitialized(
+ String decoderName, long initializedTimestampMs, long initializationDurationMs) {
if (handler != null) {
handler.post(
() ->
@@ -129,18 +134,23 @@ public interface AudioRendererEventListener {
}
}
- /**
- * Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}.
- */
- public void inputFormatChanged(final Format format) {
+ /** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
+ public void inputFormatChanged(Format format) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format));
}
}
+ /** Invokes {@link AudioRendererEventListener#onAudioPositionAdvancing(long)}. */
+ public void positionAdvancing(long playoutStartSystemTimeMs) {
+ if (handler != null) {
+ handler.post(
+ () -> castNonNull(listener).onAudioPositionAdvancing(playoutStartSystemTimeMs));
+ }
+ }
+
/** Invokes {@link AudioRendererEventListener#onAudioUnderrun(int, long, long)}. */
- public void underrun(
- final int bufferSize, final long bufferSizeMs, final long elapsedSinceLastFeedMs) {
+ public void underrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
if (handler != null) {
handler.post(
() ->
@@ -149,10 +159,8 @@ public interface AudioRendererEventListener {
}
}
- /**
- * Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}.
- */
- public void disabled(final DecoderCounters counters) {
+ /** Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}. */
+ public void disabled(DecoderCounters counters) {
counters.ensureUpdated();
if (handler != null) {
handler.post(
@@ -163,17 +171,15 @@ public interface AudioRendererEventListener {
}
}
- /**
- * Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}.
- */
- public void audioSessionId(final int audioSessionId) {
+ /** Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}. */
+ public void audioSessionId(int audioSessionId) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioSessionId(audioSessionId));
}
}
/** Invokes {@link AudioRendererEventListener#onSkipSilenceEnabledChanged(boolean)}. */
- public void skipSilenceEnabledChanged(final boolean skipSilenceEnabled) {
+ public void skipSilenceEnabledChanged(boolean skipSilenceEnabled) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onSkipSilenceEnabledChanged(skipSilenceEnabled));
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java
index b0f76c0afb..b7d375fd9d 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java
@@ -20,6 +20,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -72,10 +73,19 @@ public interface AudioSink {
*/
void onPositionDiscontinuity();
+ /**
+ * Called when the audio sink's position has increased for the first time since it was last
+ * paused or flushed.
+ *
+ * @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
+ * which playout started. Only valid if the audio track has not underrun.
+ */
+ default void onPositionAdvancing(long playoutStartSystemTimeMs) {}
+
/**
* Called when the audio sink runs out of data.
- *
- * An audio sink implementation may never call this method (for example, if audio data is
+ *
+ *
An audio sink implementation may never call this method (for example, if audio data is
* consumed in batches rather than based on the sink's own clock).
*
* @param bufferSize The size of the sink's buffer, in bytes.
@@ -297,16 +307,21 @@ public interface AudioSink {
*/
boolean hasPendingData();
- /** Sets the playback speed. */
- void setPlaybackSpeed(float playbackSpeed);
+ /**
+ * Attempts to set the playback parameters. The audio sink may override these parameters if they
+ * are not supported.
+ *
+ * @param playbackParameters The new playback parameters to attempt to set.
+ */
+ void setPlaybackParameters(PlaybackParameters playbackParameters);
- /** Gets the playback speed. */
- float getPlaybackSpeed();
+ /** Returns the active {@link PlaybackParameters}. */
+ PlaybackParameters getPlaybackParameters();
/** Sets whether silences should be skipped in the audio stream. */
void setSkipSilenceEnabled(boolean skipSilenceEnabled);
- /** Gets whether silences are skipped in the audio stream. */
+ /** Returns whether silences are skipped in the audio stream. */
boolean getSkipSilenceEnabled();
/**
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java
index c1d8df5c75..540ee098ee 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java
@@ -48,6 +48,15 @@ import java.lang.reflect.Method;
/** Listener for position tracker events. */
public interface Listener {
+ /**
+ * Called when the position tracker's position has increased for the first time since it was
+ * last paused or reset.
+ *
+ * @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
+ * which playout started.
+ */
+ void onPositionAdvancing(long playoutStartSystemTimeMs);
+
/**
* Called when the frame position is too far from the expected frame position.
*
@@ -145,6 +154,7 @@ import java.lang.reflect.Method;
private boolean needsPassthroughWorkarounds;
private long bufferSizeUs;
private float audioTrackPlaybackSpeed;
+ private boolean notifiedPositionIncreasing;
private long smoothedPlayheadOffsetUs;
private long lastPlayheadSampleTimeUs;
@@ -287,9 +297,21 @@ import java.lang.reflect.Method;
positionUs /= 1000;
}
+ if (!notifiedPositionIncreasing && positionUs > lastPositionUs) {
+ notifiedPositionIncreasing = true;
+ long mediaDurationSinceLastPositionUs = C.usToMs(positionUs - lastPositionUs);
+ long playoutDurationSinceLastPositionUs =
+ Util.getPlayoutDurationForMediaDuration(
+ mediaDurationSinceLastPositionUs, audioTrackPlaybackSpeed);
+ long playoutStartSystemTimeMs =
+ System.currentTimeMillis() - C.usToMs(playoutDurationSinceLastPositionUs);
+ listener.onPositionAdvancing(playoutStartSystemTimeMs);
+ }
+
lastSystemTimeUs = systemTimeUs;
lastPositionUs = positionUs;
lastSampleUsedGetTimestampMode = useGetTimestampMode;
+
return positionUs;
}
@@ -512,6 +534,7 @@ import java.lang.reflect.Method;
lastPlayheadSampleTimeUs = 0;
lastSystemTimeUs = 0;
previousModeSystemTimeUs = 0;
+ notifiedPositionIncreasing = false;
}
/**
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
index bc8237c911..1c1e593e22 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java
@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
@@ -498,13 +499,13 @@ public abstract class DecoderAudioRenderer<
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
- audioSink.setPlaybackSpeed(playbackSpeed);
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ audioSink.setPlaybackParameters(playbackParameters);
}
@Override
- public float getPlaybackSpeed() {
- return audioSink.getPlaybackSpeed();
+ public PlaybackParameters getPlaybackParameters() {
+ return audioSink.getPlaybackParameters();
}
@Override
@@ -708,6 +709,11 @@ public abstract class DecoderAudioRenderer<
DecoderAudioRenderer.this.onPositionDiscontinuity();
}
+ @Override
+ public void onPositionAdvancing(long playoutStartSystemTimeMs) {
+ eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
+ }
+
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
index 2da648b303..1e04b1e8d7 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
@@ -32,6 +32,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
@@ -92,14 +93,14 @@ public final class DefaultAudioSink implements AudioSink {
AudioProcessor[] getAudioProcessors();
/**
- * Configures audio processors to apply the specified playback speed immediately, returning the
- * new playback speed, which may differ from the speed passed in. Only called when processors
- * have no input pending.
+ * Configures audio processors to apply the specified playback parameters immediately, returning
+ * the new playback parameters, which may differ from those passed in. Only called when
+ * processors have no input pending.
*
- * @param playbackSpeed The playback speed to try to apply.
- * @return The playback speed that was actually applied.
+ * @param playbackParameters The playback parameters to try to apply.
+ * @return The playback parameters that were actually applied.
*/
- float applyPlaybackSpeed(float playbackSpeed);
+ PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters);
/**
* Configures audio processors to apply whether to skip silences immediately, returning the new
@@ -170,8 +171,10 @@ public final class DefaultAudioSink implements AudioSink {
}
@Override
- public float applyPlaybackSpeed(float playbackSpeed) {
- return sonicAudioProcessor.setSpeed(playbackSpeed);
+ public PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters) {
+ float speed = sonicAudioProcessor.setSpeed(playbackParameters.speed);
+ float pitch = sonicAudioProcessor.setPitch(playbackParameters.pitch);
+ return new PlaybackParameters(speed, pitch);
}
@Override
@@ -197,6 +200,10 @@ public final class DefaultAudioSink implements AudioSink {
public static final float MIN_PLAYBACK_SPEED = 0.1f;
/** The maximum allowed playback speed. Higher values will be constrained to fall in range. */
public static final float MAX_PLAYBACK_SPEED = 8f;
+ /** The minimum allowed pitch factor. Lower values will be constrained to fall in range. */
+ public static final float MIN_PITCH = 0.1f;
+ /** The maximum allowed pitch factor. Higher values will be constrained to fall in range. */
+ public static final float MAX_PITCH = 8f;
/** The default skip silence flag. */
private static final boolean DEFAULT_SKIP_SILENCE = false;
@@ -296,7 +303,7 @@ public final class DefaultAudioSink implements AudioSink {
private AudioAttributes audioAttributes;
@Nullable private MediaPositionParameters afterDrainParameters;
private MediaPositionParameters mediaPositionParameters;
- private float audioTrackPlaybackSpeed;
+ private PlaybackParameters audioTrackPlaybackParameters;
@Nullable private ByteBuffer avSyncHeader;
private int bytesUntilNextAvSync;
@@ -418,11 +425,11 @@ public final class DefaultAudioSink implements AudioSink {
auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f);
mediaPositionParameters =
new MediaPositionParameters(
- DEFAULT_PLAYBACK_SPEED,
+ PlaybackParameters.DEFAULT,
DEFAULT_SKIP_SILENCE,
/* mediaTimeUs= */ 0,
/* audioTrackPositionUs= */ 0);
- audioTrackPlaybackSpeed = 1f;
+ audioTrackPlaybackParameters = PlaybackParameters.DEFAULT;
drainingAudioProcessorIndex = C.INDEX_UNSET;
activeAudioProcessors = new AudioProcessor[0];
outputBuffers = new ByteBuffer[0];
@@ -707,7 +714,7 @@ public final class DefaultAudioSink implements AudioSink {
}
}
// Re-apply playback parameters.
- applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
+ applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
}
if (!isAudioTrackInitialized()) {
@@ -720,9 +727,9 @@ public final class DefaultAudioSink implements AudioSink {
startMediaTimeUsNeedsInit = false;
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
- setAudioTrackPlaybackSpeedV23(audioTrackPlaybackSpeed);
+ setAudioTrackPlaybackParametersV23(audioTrackPlaybackParameters);
}
- applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
+ applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
if (playing) {
play();
@@ -758,7 +765,7 @@ public final class DefaultAudioSink implements AudioSink {
// Don't process any more input until draining completes.
return false;
}
- applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
+ applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
afterDrainParameters = null;
}
@@ -789,7 +796,7 @@ public final class DefaultAudioSink implements AudioSink {
startMediaTimeUs += adjustmentUs;
startMediaTimeUsNeedsSync = false;
// Re-apply playback parameters because the startMediaTimeUs changed.
- applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
+ applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
if (listener != null && adjustmentUs != 0) {
listener.onPositionDiscontinuity();
}
@@ -1011,26 +1018,30 @@ public final class DefaultAudioSink implements AudioSink {
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
- playbackSpeed = Util.constrainValue(playbackSpeed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED);
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ playbackParameters =
+ new PlaybackParameters(
+ Util.constrainValue(playbackParameters.speed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED),
+ Util.constrainValue(playbackParameters.pitch, MIN_PITCH, MAX_PITCH));
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
- setAudioTrackPlaybackSpeedV23(playbackSpeed);
+ setAudioTrackPlaybackParametersV23(playbackParameters);
} else {
- setAudioProcessorPlaybackSpeedAndSkipSilence(playbackSpeed, getSkipSilenceEnabled());
+ setAudioProcessorPlaybackParametersAndSkipSilence(
+ playbackParameters, getSkipSilenceEnabled());
}
}
@Override
- public float getPlaybackSpeed() {
- // We use either audio processor speed adjustment or AudioTrack playback parameters, so one of
- // the operands is always 1f.
- return getAudioProcessorPlaybackSpeed() * audioTrackPlaybackSpeed;
+ public PlaybackParameters getPlaybackParameters() {
+ return enableAudioTrackPlaybackParams
+ ? audioTrackPlaybackParameters
+ : getAudioProcessorPlaybackParameters();
}
@Override
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
- setAudioProcessorPlaybackSpeedAndSkipSilence(
- getAudioProcessorPlaybackSpeed(), skipSilenceEnabled);
+ setAudioProcessorPlaybackParametersAndSkipSilence(
+ getAudioProcessorPlaybackParameters(), skipSilenceEnabled);
}
@Override
@@ -1212,7 +1223,7 @@ public final class DefaultAudioSink implements AudioSink {
framesPerEncodedSample = 0;
mediaPositionParameters =
new MediaPositionParameters(
- getAudioProcessorPlaybackSpeed(),
+ getAudioProcessorPlaybackParameters(),
getSkipSilenceEnabled(),
/* mediaTimeUs= */ 0,
/* audioTrackPositionUs= */ 0);
@@ -1249,12 +1260,13 @@ public final class DefaultAudioSink implements AudioSink {
}
@RequiresApi(23)
- private void setAudioTrackPlaybackSpeedV23(float audioTrackPlaybackSpeed) {
+ private void setAudioTrackPlaybackParametersV23(PlaybackParameters audioTrackPlaybackParameters) {
if (isAudioTrackInitialized()) {
PlaybackParams playbackParams =
new PlaybackParams()
.allowDefaults()
- .setSpeed(audioTrackPlaybackSpeed)
+ .setSpeed(audioTrackPlaybackParameters.speed)
+ .setPitch(audioTrackPlaybackParameters.pitch)
.setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
try {
audioTrack.setPlaybackParams(playbackParams);
@@ -1262,20 +1274,22 @@ public final class DefaultAudioSink implements AudioSink {
Log.w(TAG, "Failed to set playback params", e);
}
// Update the speed using the actual effective speed from the audio track.
- audioTrackPlaybackSpeed = audioTrack.getPlaybackParams().getSpeed();
- audioTrackPositionTracker.setAudioTrackPlaybackSpeed(audioTrackPlaybackSpeed);
+ audioTrackPlaybackParameters =
+ new PlaybackParameters(
+ audioTrack.getPlaybackParams().getSpeed(), audioTrack.getPlaybackParams().getPitch());
+ audioTrackPositionTracker.setAudioTrackPlaybackSpeed(audioTrackPlaybackParameters.speed);
}
- this.audioTrackPlaybackSpeed = audioTrackPlaybackSpeed;
+ this.audioTrackPlaybackParameters = audioTrackPlaybackParameters;
}
- private void setAudioProcessorPlaybackSpeedAndSkipSilence(
- float playbackSpeed, boolean skipSilence) {
+ private void setAudioProcessorPlaybackParametersAndSkipSilence(
+ PlaybackParameters playbackParameters, boolean skipSilence) {
MediaPositionParameters currentMediaPositionParameters = getMediaPositionParameters();
- if (playbackSpeed != currentMediaPositionParameters.playbackSpeed
+ if (!playbackParameters.equals(currentMediaPositionParameters.playbackParameters)
|| skipSilence != currentMediaPositionParameters.skipSilence) {
MediaPositionParameters mediaPositionParameters =
new MediaPositionParameters(
- playbackSpeed,
+ playbackParameters,
skipSilence,
/* mediaTimeUs= */ C.TIME_UNSET,
/* audioTrackPositionUs= */ C.TIME_UNSET);
@@ -1291,8 +1305,8 @@ public final class DefaultAudioSink implements AudioSink {
}
}
- private float getAudioProcessorPlaybackSpeed() {
- return getMediaPositionParameters().playbackSpeed;
+ private PlaybackParameters getAudioProcessorPlaybackParameters() {
+ return getMediaPositionParameters().playbackParameters;
}
private MediaPositionParameters getMediaPositionParameters() {
@@ -1304,18 +1318,18 @@ public final class DefaultAudioSink implements AudioSink {
: mediaPositionParameters;
}
- private void applyAudioProcessorPlaybackSpeedAndSkipSilence(long presentationTimeUs) {
- float playbackSpeed =
+ private void applyAudioProcessorPlaybackParametersAndSkipSilence(long presentationTimeUs) {
+ PlaybackParameters playbackParameters =
configuration.canApplyPlaybackParameters
- ? audioProcessorChain.applyPlaybackSpeed(getAudioProcessorPlaybackSpeed())
- : DEFAULT_PLAYBACK_SPEED;
+ ? audioProcessorChain.applyPlaybackParameters(getAudioProcessorPlaybackParameters())
+ : PlaybackParameters.DEFAULT;
boolean skipSilenceEnabled =
configuration.canApplyPlaybackParameters
? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled())
: DEFAULT_SKIP_SILENCE;
mediaPositionParametersCheckpoints.add(
new MediaPositionParameters(
- playbackSpeed,
+ playbackParameters,
skipSilenceEnabled,
/* mediaTimeUs= */ max(0, presentationTimeUs),
/* audioTrackPositionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
@@ -1340,7 +1354,7 @@ public final class DefaultAudioSink implements AudioSink {
long playoutDurationSinceLastCheckpoint =
positionUs - mediaPositionParameters.audioTrackPositionUs;
- if (mediaPositionParameters.playbackSpeed != 1f) {
+ if (!mediaPositionParameters.playbackParameters.equals(PlaybackParameters.DEFAULT)) {
if (mediaPositionParametersCheckpoints.isEmpty()) {
playoutDurationSinceLastCheckpoint =
audioProcessorChain.getMediaDuration(playoutDurationSinceLastCheckpoint);
@@ -1348,7 +1362,8 @@ public final class DefaultAudioSink implements AudioSink {
// Playing data at a previous playback speed, so fall back to multiplying by the speed.
playoutDurationSinceLastCheckpoint =
Util.getMediaDurationForPlayoutDuration(
- playoutDurationSinceLastCheckpoint, mediaPositionParameters.playbackSpeed);
+ playoutDurationSinceLastCheckpoint,
+ mediaPositionParameters.playbackParameters.speed);
}
}
return mediaPositionParameters.mediaTimeUs + playoutDurationSinceLastCheckpoint;
@@ -1692,8 +1707,8 @@ public final class DefaultAudioSink implements AudioSink {
/** Stores parameters used to calculate the current media position. */
private static final class MediaPositionParameters {
- /** The playback speed. */
- public final float playbackSpeed;
+ /** The playback parameters. */
+ public final PlaybackParameters playbackParameters;
/** Whether to skip silences. */
public final boolean skipSilence;
/** The media time from which the playback parameters apply, in microseconds. */
@@ -1702,8 +1717,11 @@ public final class DefaultAudioSink implements AudioSink {
public final long audioTrackPositionUs;
private MediaPositionParameters(
- float playbackSpeed, boolean skipSilence, long mediaTimeUs, long audioTrackPositionUs) {
- this.playbackSpeed = playbackSpeed;
+ PlaybackParameters playbackParameters,
+ boolean skipSilence,
+ long mediaTimeUs,
+ long audioTrackPositionUs) {
+ this.playbackParameters = playbackParameters;
this.skipSilence = skipSilence;
this.mediaTimeUs = mediaTimeUs;
this.audioTrackPositionUs = audioTrackPositionUs;
@@ -1776,6 +1794,13 @@ public final class DefaultAudioSink implements AudioSink {
Log.w(TAG, "Ignoring impossibly large audio latency: " + latencyUs);
}
+ @Override
+ public void onPositionAdvancing(long playoutStartSystemTimeMs) {
+ if (listener != null) {
+ listener.onPositionAdvancing(playoutStartSystemTimeMs);
+ }
+ }
+
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs) {
if (listener != null) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java
index 3f755a7130..7460d12457 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java
@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import java.nio.ByteBuffer;
/** An overridable {@link AudioSink} implementation forwarding all methods to another sink. */
@@ -88,13 +89,13 @@ public class ForwardingAudioSink implements AudioSink {
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
- sink.setPlaybackSpeed(playbackSpeed);
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ sink.setPlaybackParameters(playbackParameters);
}
@Override
- public float getPlaybackSpeed() {
- return sink.getPlaybackSpeed();
+ public PlaybackParameters getPlaybackParameters() {
+ return sink.getPlaybackParameters();
}
@Override
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
index 91c0f946ce..2d034335c8 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
@@ -545,13 +546,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
- audioSink.setPlaybackSpeed(playbackSpeed);
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ audioSink.setPlaybackParameters(playbackParameters);
}
@Override
- public float getPlaybackSpeed() {
- return audioSink.getPlaybackSpeed();
+ public PlaybackParameters getPlaybackParameters() {
+ return audioSink.getPlaybackParameters();
}
@Override
@@ -828,6 +829,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
MediaCodecAudioRenderer.this.onPositionDiscontinuity();
}
+ @Override
+ public void onPositionAdvancing(long playoutStartSystemTimeMs) {
+ eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
+ }
+
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java
index a2cdaa8b74..ae65eacd13 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java
@@ -37,6 +37,7 @@ import java.util.Arrays;
private final int inputSampleRateHz;
private final int channelCount;
private final float speed;
+ private final float pitch;
private final float rate;
private final int minPeriod;
private final int maxPeriod;
@@ -63,12 +64,15 @@ import java.util.Arrays;
* @param inputSampleRateHz The sample rate of input audio, in hertz.
* @param channelCount The number of channels in the input audio.
* @param speed The speedup factor for output audio.
+ * @param pitch The pitch factor for output audio.
* @param outputSampleRateHz The sample rate for output audio, in hertz.
*/
- public Sonic(int inputSampleRateHz, int channelCount, float speed, int outputSampleRateHz) {
+ public Sonic(
+ int inputSampleRateHz, int channelCount, float speed, float pitch, int outputSampleRateHz) {
this.inputSampleRateHz = inputSampleRateHz;
this.channelCount = channelCount;
this.speed = speed;
+ this.pitch = pitch;
rate = (float) inputSampleRateHz / outputSampleRateHz;
minPeriod = inputSampleRateHz / MAXIMUM_PITCH;
maxPeriod = inputSampleRateHz / MINIMUM_PITCH;
@@ -118,8 +122,10 @@ import java.util.Arrays;
*/
public void queueEndOfStream() {
int remainingFrameCount = inputFrameCount;
+ float s = speed / pitch;
+ float r = rate * pitch;
int expectedOutputFrames =
- outputFrameCount + (int) ((remainingFrameCount / speed + pitchFrameCount) / rate + 0.5f);
+ outputFrameCount + (int) ((remainingFrameCount / s + pitchFrameCount) / r + 0.5f);
// Add enough silence to flush both input and pitch buffers.
inputBuffer =
@@ -464,14 +470,16 @@ import java.util.Arrays;
private void processStreamInput() {
// Resample as many pitch periods as we have buffered on the input.
int originalOutputFrameCount = outputFrameCount;
- if (speed > 1.00001 || speed < 0.99999) {
- changeSpeed(speed);
+ float s = speed / pitch;
+ float r = rate * pitch;
+ if (s > 1.00001 || s < 0.99999) {
+ changeSpeed(s);
} else {
copyToOutput(inputBuffer, 0, inputFrameCount);
inputFrameCount = 0;
}
- if (rate != 1.0f) {
- adjustRate(rate, originalOutputFrameCount);
+ if (r != 1.0f) {
+ adjustRate(r, originalOutputFrameCount);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java
index d582461c81..5c3c1db0c7 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java
@@ -43,6 +43,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
private int pendingOutputSampleRate;
private float speed;
+ private float pitch;
private AudioFormat pendingInputAudioFormat;
private AudioFormat pendingOutputAudioFormat;
@@ -61,6 +62,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
/** Creates a new Sonic audio processor. */
public SonicAudioProcessor() {
speed = 1f;
+ pitch = 1f;
pendingInputAudioFormat = AudioFormat.NOT_SET;
pendingOutputAudioFormat = AudioFormat.NOT_SET;
inputAudioFormat = AudioFormat.NOT_SET;
@@ -87,6 +89,22 @@ public final class SonicAudioProcessor implements AudioProcessor {
return speed;
}
+ /**
+ * Sets the playback pitch. This method may only be called after draining data through the
+ * processor. The value returned by {@link #isActive()} may change, and the processor must be
+ * {@link #flush() flushed} before queueing more data.
+ *
+ * @param pitch The requested new pitch.
+ * @return The actual new pitch.
+ */
+ public float setPitch(float pitch) {
+ if (this.pitch != pitch) {
+ this.pitch = pitch;
+ pendingSonicRecreation = true;
+ }
+ return pitch;
+ }
+
/**
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
* audio at the same sample rate as the input. After calling this method, call {@link
@@ -140,6 +158,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
public boolean isActive() {
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
+ || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
}
@@ -200,6 +219,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
inputAudioFormat.sampleRate,
inputAudioFormat.channelCount,
speed,
+ pitch,
outputAudioFormat.sampleRate);
} else if (sonic != null) {
sonic.flush();
@@ -214,6 +234,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
@Override
public void reset() {
speed = 1f;
+ pitch = 1f;
pendingInputAudioFormat = AudioFormat.NOT_SET;
pendingOutputAudioFormat = AudioFormat.NOT_SET;
inputAudioFormat = AudioFormat.NOT_SET;
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
index a593943e57..be02faeba8 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
@@ -409,7 +409,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
/**
* Sets the mode, which determines the role of sessions acquired from the instance. This must be
* called before {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}
- * or {@link #acquirePlaceholderSession} is called.
+ * is called.
*
*
By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
* required.
@@ -469,34 +469,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
@Override
@Nullable
- public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
- initPlaybackLooper(playbackLooper);
- ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
- boolean avoidPlaceholderDrmSessions =
- FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
- && FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
- // Avoid attaching a session to sparse formats.
- if (avoidPlaceholderDrmSessions
- || Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
- || exoMediaDrm.getExoMediaCryptoType() == null) {
- return null;
- }
- maybeCreateMediaDrmHandler(playbackLooper);
- if (placeholderDrmSession == null) {
- DefaultDrmSession placeholderDrmSession =
- createAndAcquireSessionWithRetry(
- /* schemeDatas= */ ImmutableList.of(),
- /* isPlaceholderSession= */ true,
- /* eventDispatcher= */ null);
- sessions.add(placeholderDrmSession);
- this.placeholderDrmSession = placeholderDrmSession;
- } else {
- placeholderDrmSession.acquire(/* eventDispatcher= */ null);
- }
- return placeholderDrmSession;
- }
-
- @Override
public DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
@@ -504,6 +476,11 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
initPlaybackLooper(playbackLooper);
maybeCreateMediaDrmHandler(playbackLooper);
+ if (format.drmInitData == null) {
+ // Content is not encrypted.
+ return maybeAcquirePlaceholderSession(MimeTypes.getTrackType(format.sampleMimeType));
+ }
+
@Nullable List schemeDatas = null;
if (offlineLicenseKeySetId == null) {
schemeDatas = getSchemeDatas(Assertions.checkNotNull(format.drmInitData), uuid, false);
@@ -565,6 +542,32 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
// Internal methods.
+ @Nullable
+ private DrmSession maybeAcquirePlaceholderSession(int trackType) {
+ ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
+ boolean avoidPlaceholderDrmSessions =
+ FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
+ && FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
+ // Avoid attaching a session to sparse formats.
+ if (avoidPlaceholderDrmSessions
+ || Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
+ || UnsupportedMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())) {
+ return null;
+ }
+ if (placeholderDrmSession == null) {
+ DefaultDrmSession placeholderDrmSession =
+ createAndAcquireSessionWithRetry(
+ /* schemeDatas= */ ImmutableList.of(),
+ /* isPlaceholderSession= */ true,
+ /* eventDispatcher= */ null);
+ sessions.add(placeholderDrmSession);
+ this.placeholderDrmSession = placeholderDrmSession;
+ } else {
+ placeholderDrmSession.acquire(/* eventDispatcher= */ null);
+ }
+ return placeholderDrmSession;
+ }
+
private boolean canAcquireSession(DrmInitData drmInitData) {
if (offlineLicenseKeySetId != null) {
// An offline license can be restored so a session can always be acquired.
@@ -585,12 +588,16 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) {
// If there is no scheme information, assume patternless AES-CTR.
return true;
- } else if (C.CENC_TYPE_cbc1.equals(schemeType)
- || C.CENC_TYPE_cbcs.equals(schemeType)
- || C.CENC_TYPE_cens.equals(schemeType)) {
- // API support for AES-CBC and pattern encryption was added in API 24. However, the
+ } else if (C.CENC_TYPE_cbcs.equals(schemeType)) {
+ // Support for cbcs (AES-CBC with pattern encryption) was added in API 24. However, the
// implementation was not stable until API 25.
return Util.SDK_INT >= 25;
+ } else if (C.CENC_TYPE_cbc1.equals(schemeType) || C.CENC_TYPE_cens.equals(schemeType)) {
+ // Support for cbc1 (AES-CTR with pattern encryption) and cens (AES-CBC without pattern
+ // encryption) was also added in API 24 and made stable from API 25, however support was
+ // removed from API 30. Since the range of API levels for which these modes are usable is too
+ // small to be useful, we don't indicate support on any API level.
+ return false;
}
// Unknown schemes, assume one of them is supported.
return true;
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java
index 7c26142216..1168884d76 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java
@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.drm;
import android.os.Looper;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
/** Manages a DRM session. */
@@ -33,13 +32,19 @@ public interface DrmSessionManager {
new DrmSessionManager() {
@Override
+ @Nullable
public DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
Format format) {
- return new ErrorStateDrmSession(
- new DrmSession.DrmSessionException(
- new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
+ if (format.drmInitData == null) {
+ return null;
+ } else {
+ return new ErrorStateDrmSession(
+ new DrmSession.DrmSessionException(
+ new UnsupportedDrmException(
+ UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
+ }
}
@Override
@@ -64,39 +69,27 @@ public interface DrmSessionManager {
// Do nothing.
}
- /**
- * Returns a {@link DrmSession} that does not execute key requests, with an incremented reference
- * count. When the caller no longer needs to use the instance, it must call {@link
- * DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
- *
- * Placeholder {@link DrmSession DrmSessions} may be used to configure secure decoders for
- * playback of clear content periods. This can reduce the cost of transitioning between clear and
- * encrypted content periods.
- *
- * @param playbackLooper The looper associated with the media playback thread.
- * @param trackType The type of the track to acquire a placeholder session for. Must be one of the
- * {@link C}{@code .TRACK_TYPE_*} constants.
- * @return The placeholder DRM session, or null if this DRM session manager does not support
- * placeholder sessions.
- */
- @Nullable
- default DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
- return null;
- }
-
/**
* Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference
- * count. When the caller no longer needs to use the instance, it must call {@link
+ * count. May return null if the {@link Format#drmInitData} is null and the DRM session manager is
+ * not configured to attach a {@link DrmSession} to clear content. When the caller no longer needs
+ * to use a returned {@link DrmSession}, it must call {@link
* DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
*
+ *
If the provided {@link Format} contains a null {@link Format#drmInitData}, the returned
+ * {@link DrmSession} (if not null) will be a placeholder session which does not execute key
+ * requests, and cannot be used to handle encrypted content. However, a placeholder session may be
+ * used to configure secure decoders for playback of clear content periods, which can reduce the
+ * cost of transitioning between clear and encrypted content.
+ *
* @param playbackLooper The looper associated with the media playback thread.
* @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
* events, and passed on to {@link
* DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
- * @param format The {@link Format} for which to acquire a {@link DrmSession}. Must contain a
- * non-null {@link Format#drmInitData}.
- * @return The DRM session.
+ * @param format The {@link Format} for which to acquire a {@link DrmSession}.
+ * @return The DRM session. May be null if the given {@link Format#drmInitData} is null.
*/
+ @Nullable
DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
@@ -105,16 +98,16 @@ public interface DrmSessionManager {
/**
* Returns the {@link ExoMediaCrypto} type associated to sessions acquired for the given {@link
* Format}. Returns the {@link UnsupportedMediaCrypto} type if this DRM session manager does not
- * support any of the DRM schemes defined in the given {@link Format}. If the {@link Format}
- * describes unencrypted content, returns an {@link ExoMediaCrypto} type if this DRM session
- * manager would associate a {@link #acquirePlaceholderSession placeholder session} to the given
- * {@link Format}, or null otherwise.
+ * support any of the DRM schemes defined in the given {@link Format}. Returns null if {@link
+ * Format#drmInitData} is null and {@link #acquireSession} would return null for the given {@link
+ * Format}.
*
* @param format The {@link Format} for which to return the {@link ExoMediaCrypto} type.
- * @return The {@link ExoMediaCrypto} type associated to sessions acquired using the given
- * parameters, or the {@link UnsupportedMediaCrypto} type if the provided {@code drmInitData}
- * is not supported, or {@code null} if {@code drmInitData} is null and no DRM session will be
- * associated to the given {@code trackType}.
+ * @return The {@link ExoMediaCrypto} type associated to sessions acquired using the given {@link
+ * Format}, or {@link UnsupportedMediaCrypto} if this DRM session manager does not support any
+ * of the DRM schemes defined in the given {@link Format}. May be null if {@link
+ * Format#drmInitData} is null and {@link #acquireSession} would return null for the given
+ * {@link Format}.
*/
@Nullable
Class extends ExoMediaCrypto> getExoMediaCryptoType(Format format);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java
index 71091c878d..b218d0cadb 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java
@@ -184,7 +184,8 @@ public final class OfflineLicenseHelper {
/**
* Downloads an offline license.
*
- * @param format The {@link Format} of the content whose license is to be downloaded.
+ * @param format The {@link Format} of the content whose license is to be downloaded. Must contain
+ * a non-null {@link Format#drmInitData}.
* @return The key set id for the downloaded license.
* @throws DrmSessionException Thrown when a DRM session error occurs.
*/
@@ -278,13 +279,14 @@ public final class OfflineLicenseHelper {
private DrmSession openBlockingKeyRequest(
@Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, Format format) {
+ Assertions.checkNotNull(format.drmInitData);
drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId);
conditionVariable.close();
DrmSession drmSession =
drmSessionManager.acquireSession(handlerThread.getLooper(), eventDispatcher, format);
// Block current thread until key loading is finished
conditionVariable.block();
- return drmSession;
+ return Assertions.checkNotNull(drmSession);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
index afbf05fa9b..566f7fb1c7 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java
@@ -15,28 +15,19 @@
*/
package com.google.android.exoplayer2.source;
-import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
-
import android.content.Context;
import android.net.Uri;
-import android.os.Build;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem;
-import com.google.android.exoplayer2.MediaItem.DrmConfiguration;
-import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
-import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
-import com.google.android.exoplayer2.drm.MediaDrmCallback;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
@@ -44,10 +35,8 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
-import com.google.common.primitives.Ints;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
/**
* The default {@link MediaSourceFactory} implementation.
@@ -79,21 +68,6 @@ import java.util.Map;
* the stream.
*
*
- *
DrmSessionManager creation for protected content
- *
- * For a media item with a {@link DrmConfiguration}, a {@link DefaultDrmSessionManager} is
- * created based on that configuration. The following setter can be used to optionally configure the
- * creation:
- *
- *
- * - {@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory
- * to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link
- * DefaultHttpDataSourceFactory}).
- *
- *
- * For media items without a {@link DrmConfiguration}, the {@link DrmSessionManager} passed to
- * {@link #setDrmSessionManager(DrmSessionManager)} will be used.
- *
*
Ad support for media items with ad tag uri
*
* For a media item with an ad tag uri, an {@link AdSupportProvider} needs to be passed to {@link
@@ -167,21 +141,14 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
}
private static final String TAG = "DefaultMediaSourceFactory";
- private static final String DEFAULT_USER_AGENT =
- ExoPlayerLibraryInfo.VERSION_SLASHY
- + " (Linux;Android "
- + Build.VERSION.RELEASE
- + ") "
- + ExoPlayerLibraryInfo.VERSION_SLASHY;
+ private final MediaSourceDrmHelper mediaSourceDrmHelper;
private final DataSource.Factory dataSourceFactory;
@Nullable private final AdSupportProvider adSupportProvider;
private final SparseArray mediaSourceFactories;
@C.ContentType private final int[] supportedTypes;
- private DrmSessionManager drmSessionManager;
- @Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
- private String userAgent;
+ @Nullable private DrmSessionManager drmSessionManager;
@Nullable private List streamKeys;
/**
@@ -196,8 +163,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
DataSource.Factory dataSourceFactory, @Nullable AdSupportProvider adSupportProvider) {
this.dataSourceFactory = dataSourceFactory;
this.adSupportProvider = adSupportProvider;
- drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
- userAgent = DEFAULT_USER_AGENT;
+ mediaSourceDrmHelper = new MediaSourceDrmHelper();
mediaSourceFactories = loadDelegates(dataSourceFactory);
supportedTypes = new int[mediaSourceFactories.size()];
for (int i = 0; i < mediaSourceFactories.size(); i++) {
@@ -205,49 +171,23 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
}
}
- /**
- * Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
- * HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
- * is passed the {@link DefaultHttpDataSourceFactory} is used.
- *
- * @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
- * DefaultHttpDataSourceFactory}.
- * @return This factory, for convenience.
- */
+ @Override
public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
- this.drmHttpDataSourceFactory = drmHttpDataSourceFactory;
+ mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
return this;
}
- /**
- * Sets the optional user agent to be used for DRM requests.
- *
- * In case a factory has been set by {@link
- * #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}, this user agent is ignored.
- *
- * @param userAgent The user agent to be used for DRM requests.
- * @return This factory, for convenience.
- */
+ @Override
public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
- this.userAgent = userAgent != null ? userAgent : DEFAULT_USER_AGENT;
+ mediaSourceDrmHelper.setDrmUserAgent(userAgent);
return this;
}
- /**
- * Sets the {@link DrmSessionManager} to use for media items that do not specify a {@link
- * DrmConfiguration}. The default value is {@link DrmSessionManager#DUMMY}.
- *
- * @param drmSessionManager The {@link DrmSessionManager}.
- * @return This factory, for convenience.
- */
@Override
public DefaultMediaSourceFactory setDrmSessionManager(
@Nullable DrmSessionManager drmSessionManager) {
- this.drmSessionManager =
- drmSessionManager != null
- ? drmSessionManager
- : DrmSessionManager.getDummyDrmSessionManager();
+ this.drmSessionManager = drmSessionManager;
return this;
}
@@ -292,7 +232,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
@Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
Assertions.checkNotNull(
mediaSourceFactory, "No suitable media source factory found for content type: " + type);
- mediaSourceFactory.setDrmSessionManager(createDrmSessionManager(mediaItem));
+ mediaSourceFactory.setDrmSessionManager(
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem));
mediaSourceFactory.setStreamKeys(
!mediaItem.playbackProperties.streamKeys.isEmpty()
? mediaItem.playbackProperties.streamKeys
@@ -318,46 +259,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
// internal methods
- private DrmSessionManager createDrmSessionManager(MediaItem mediaItem) {
- Assertions.checkNotNull(mediaItem.playbackProperties);
- if (mediaItem.playbackProperties.drmConfiguration == null
- || mediaItem.playbackProperties.drmConfiguration.licenseUri == null
- || Util.SDK_INT < 18) {
- return drmSessionManager;
- }
- DefaultDrmSessionManager drmSessionManager =
- new DefaultDrmSessionManager.Builder()
- .setUuidAndExoMediaDrmProvider(
- mediaItem.playbackProperties.drmConfiguration.uuid,
- FrameworkMediaDrm.DEFAULT_PROVIDER)
- .setMultiSession(mediaItem.playbackProperties.drmConfiguration.multiSession)
- .setPlayClearSamplesWithoutKeys(
- mediaItem.playbackProperties.drmConfiguration.playClearContentWithoutKey)
- .setUseDrmSessionsForClearContent(
- Ints.toArray(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes))
- .build(createHttpMediaDrmCallback(mediaItem.playbackProperties.drmConfiguration));
-
- drmSessionManager.setMode(
- MODE_PLAYBACK, mediaItem.playbackProperties.drmConfiguration.getKeySetId());
-
- return drmSessionManager;
- }
-
- private MediaDrmCallback createHttpMediaDrmCallback(MediaItem.DrmConfiguration drmConfiguration) {
- Assertions.checkNotNull(drmConfiguration.licenseUri);
- HttpMediaDrmCallback drmCallback =
- new HttpMediaDrmCallback(
- drmConfiguration.licenseUri.toString(),
- drmConfiguration.forceDefaultLicenseUri,
- drmHttpDataSourceFactory != null
- ? drmHttpDataSourceFactory
- : new DefaultHttpDataSourceFactory(userAgent));
- for (Map.Entry entry : drmConfiguration.requestHeaders.entrySet()) {
- drmCallback.setKeyRequestProperty(entry.getKey(), entry.getValue());
- }
- return drmCallback;
- }
-
private static MediaSource maybeClipMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
if (mediaItem.clippingProperties.startPositionMs == 0
&& mediaItem.clippingProperties.endPositionMs == C.TIME_END_OF_SOURCE
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java
index 1e8129bf3a..38146c92b2 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java
@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
@@ -168,6 +169,23 @@ public final class ExtractorMediaSource extends CompositeMediaSource {
throw new UnsupportedOperationException();
}
+ /**
+ * @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmHttpDataSourceFactory} instead.
+ */
+ @Deprecated
+ @Override
+ public MediaSourceFactory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmUserAgent} instead. */
+ @Deprecated
+ @Override
+ public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
+ throw new UnsupportedOperationException();
+ }
+
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
@SuppressWarnings("deprecation")
@Deprecated
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java
new file mode 100644
index 0000000000..29325d789e
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
+import static com.google.android.exoplayer2.util.Util.castNonNull;
+
+import android.os.Build;
+import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
+import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Util;
+import com.google.common.primitives.Ints;
+import java.util.Map;
+
+/** A helper to create a {@link DrmSessionManager} from a {@link MediaItem}. */
+public final class MediaSourceDrmHelper {
+
+ private static final String DEFAULT_USER_AGENT =
+ ExoPlayerLibraryInfo.VERSION_SLASHY
+ + " (Linux;Android "
+ + Build.VERSION.RELEASE
+ + ") "
+ + ExoPlayerLibraryInfo.VERSION_SLASHY;
+
+ @Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
+ @Nullable private String userAgent;
+
+ /**
+ * Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
+ * HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
+ * is passed the {@link DefaultHttpDataSourceFactory} is used.
+ *
+ * @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
+ * DefaultHttpDataSourceFactory}.
+ */
+ public void setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ this.drmHttpDataSourceFactory = drmHttpDataSourceFactory;
+ }
+
+ /**
+ * Sets the optional user agent to be used for DRM requests.
+ *
+ * In case a factory has been set by {@link
+ * #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}, this user agent is ignored.
+ *
+ * @param userAgent The user agent to be used for DRM requests.
+ */
+ public void setDrmUserAgent(@Nullable String userAgent) {
+ this.userAgent = userAgent;
+ }
+
+ /** Creates a {@link DrmSessionManager} for the given media item. */
+ public DrmSessionManager create(MediaItem mediaItem) {
+ Assertions.checkNotNull(mediaItem.playbackProperties);
+ @Nullable
+ MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
+ if (drmConfiguration == null || drmConfiguration.licenseUri == null || Util.SDK_INT < 18) {
+ return DrmSessionManager.getDummyDrmSessionManager();
+ }
+ HttpDataSource.Factory dataSourceFactory =
+ drmHttpDataSourceFactory != null
+ ? drmHttpDataSourceFactory
+ : new DefaultHttpDataSourceFactory(userAgent != null ? userAgent : DEFAULT_USER_AGENT);
+ HttpMediaDrmCallback httpDrmCallback =
+ new HttpMediaDrmCallback(
+ castNonNull(drmConfiguration.licenseUri).toString(),
+ drmConfiguration.forceDefaultLicenseUri,
+ dataSourceFactory);
+ for (Map.Entry entry : drmConfiguration.requestHeaders.entrySet()) {
+ httpDrmCallback.setKeyRequestProperty(entry.getKey(), entry.getValue());
+ }
+ DefaultDrmSessionManager drmSessionManager =
+ new DefaultDrmSessionManager.Builder()
+ .setUuidAndExoMediaDrmProvider(
+ drmConfiguration.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
+ .setMultiSession(drmConfiguration.multiSession)
+ .setPlayClearSamplesWithoutKeys(drmConfiguration.playClearContentWithoutKey)
+ .setUseDrmSessionsForClearContent(Ints.toArray(drmConfiguration.sessionForClearTypes))
+ .build(httpDrmCallback);
+ drmSessionManager.setMode(MODE_PLAYBACK, drmConfiguration.getKeySetId());
+ return drmSessionManager;
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java
index e1c52c097b..4175121d38 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceFactory.java
@@ -19,13 +19,34 @@ import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
-import com.google.android.exoplayer2.drm.DrmSession;
+import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.offline.StreamKey;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import java.util.List;
-/** Factory for creating {@link MediaSource}s from URIs. */
+/**
+ * Factory for creating {@link MediaSource}s from URIs.
+ *
+ * DrmSessionManager creation for protected content
+ *
+ * In case a {@link DrmSessionManager} is passed to {@link
+ * #setDrmSessionManager(DrmSessionManager)}, it will be used regardless of the drm configuration of
+ * the media item.
+ *
+ *
For a media item with a {@link MediaItem.DrmConfiguration}, a {@link DefaultDrmSessionManager}
+ * is created based on that configuration. The following setter can be used to optionally configure
+ * the creation:
+ *
+ *
+ * - {@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory
+ * to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link
+ * DefaultHttpDataSourceFactory}).
+ *
+ */
public interface MediaSourceFactory {
/** @deprecated Use {@link MediaItem.PlaybackProperties#streamKeys} instead. */
@@ -35,13 +56,40 @@ public interface MediaSourceFactory {
}
/**
- * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}.
+ * Sets the {@link DrmSessionManager} to use for all media items regardless of their {@link
+ * MediaItem.DrmConfiguration}.
*
* @param drmSessionManager The {@link DrmSessionManager}.
* @return This factory, for convenience.
*/
MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
+ /**
+ * Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
+ * HttpMediaDrmCallbacks} to execute key and provisioning requests over HTTP.
+ *
+ * In case a {@link DrmSessionManager} has been set by {@link
+ * #setDrmSessionManager(DrmSessionManager)}, this data source factory is ignored.
+ *
+ * @param drmHttpDataSourceFactory The HTTP data source factory, or {@code null} to use {@link
+ * DefaultHttpDataSourceFactory}.
+ * @return This factory, for convenience.
+ */
+ MediaSourceFactory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory);
+
+ /**
+ * Sets the optional user agent to be used for DRM requests.
+ *
+ *
In case a factory has been set by {@link
+ * #setDrmHttpDataSourceFactory(HttpDataSource.Factory)} or a {@link DrmSessionManager} has been
+ * set by {@link #setDrmSessionManager(DrmSessionManager)}, this user agent is ignored.
+ *
+ * @param userAgent The user agent to be used for DRM requests.
+ * @return This factory, for convenience.
+ */
+ MediaSourceFactory setDrmUserAgent(@Nullable String userAgent);
+
/**
* Sets an optional {@link LoadErrorHandlingPolicy}.
*
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
index 125891f09c..4d7230cc3a 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
@@ -22,7 +22,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
@@ -30,6 +29,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
@@ -51,9 +51,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public static final class Factory implements MediaSourceFactory {
private final DataSource.Factory dataSourceFactory;
+ private final MediaSourceDrmHelper mediaSourceDrmHelper;
private ExtractorsFactory extractorsFactory;
- private DrmSessionManager drmSessionManager;
+ @Nullable private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private int continueLoadingCheckIntervalBytes;
@Nullable private String customCacheKey;
@@ -78,7 +79,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
this.dataSourceFactory = dataSourceFactory;
this.extractorsFactory = extractorsFactory;
- drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
+ mediaSourceDrmHelper = new MediaSourceDrmHelper();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
}
@@ -146,19 +147,22 @@ public final class ProgressiveMediaSource extends BaseMediaSource
return this;
}
- /**
- * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
- * default value is {@link DrmSessionManager#DUMMY}.
- *
- * @param drmSessionManager The {@link DrmSessionManager}.
- * @return This factory, for convenience.
- */
@Override
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
- this.drmSessionManager =
- drmSessionManager != null
- ? drmSessionManager
- : DrmSessionManager.getDummyDrmSessionManager();
+ this.drmSessionManager = drmSessionManager;
+ return this;
+ }
+
+ @Override
+ public Factory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
+ return this;
+ }
+
+ @Override
+ public Factory setDrmUserAgent(@Nullable String userAgent) {
+ mediaSourceDrmHelper.setDrmUserAgent(userAgent);
return this;
}
@@ -194,7 +198,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
mediaItem,
dataSourceFactory,
extractorsFactory,
- drmSessionManager,
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
loadErrorHandlingPolicy,
continueLoadingCheckIntervalBytes);
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
index 515f805845..28d25feb03 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
@@ -327,13 +327,7 @@ public class SampleQueue implements TrackOutput {
* Attempts to read from the queue.
*
*
{@link Format Formats} read from this method may be associated to a {@link DrmSession}
- * through {@link FormatHolder#drmSession}, which is populated in two scenarios:
- *
- *
- * - The {@link Format} has a non-null {@link Format#drmInitData}.
- *
- The {@link DrmSessionManager} provides placeholder sessions for this queue's track type.
- * See {@link DrmSessionManager#acquirePlaceholderSession(Looper, int)}.
- *
+ * through {@link FormatHolder#drmSession}.
*
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
@@ -842,10 +836,7 @@ public class SampleQueue implements TrackOutput {
// is being used for both DrmInitData.
@Nullable DrmSession previousSession = currentDrmSession;
currentDrmSession =
- newDrmInitData != null
- ? drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat)
- : drmSessionManager.acquirePlaceholderSession(
- playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
+ drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat);
outputFormatHolder.drmSession = currentDrmSession;
if (previousSession != null) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java
index 65abcb4059..4c3f58f2c6 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream.cache;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
+import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -219,9 +220,9 @@ import java.util.TreeSet;
public SimpleCacheSpan setLastTouchTimestamp(
SimpleCacheSpan cacheSpan, long lastTouchTimestamp, boolean updateFile) {
checkState(cachedSpans.remove(cacheSpan));
- File file = cacheSpan.file;
+ File file = checkNotNull(cacheSpan.file);
if (updateFile) {
- File directory = file.getParentFile();
+ File directory = checkNotNull(file.getParentFile());
long position = cacheSpan.position;
File newFile = SimpleCacheSpan.getCacheFile(directory, id, position, lastTouchTimestamp);
if (file.renameTo(newFile)) {
@@ -244,7 +245,9 @@ import java.util.TreeSet;
/** Removes the given span from cache. */
public boolean removeSpan(CacheSpan span) {
if (cachedSpans.remove(span)) {
- span.file.delete();
+ if (span.file != null) {
+ span.file.delete();
+ }
return true;
}
return false;
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
index 452abca7c2..850ac59f04 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java
@@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.upstream.cache;
+import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
+import static com.google.android.exoplayer2.util.Assertions.checkState;
+import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.min;
import android.annotation.SuppressLint;
@@ -61,6 +64,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.checkerframework.checker.nullness.compatqual.NullableType;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Maintains the index of cached content. */
/* package */ class CachedContentIndex {
@@ -155,13 +159,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable byte[] legacyStorageSecretKey,
boolean legacyStorageEncrypt,
boolean preferLegacyStorage) {
- Assertions.checkState(databaseProvider != null || legacyStorageDir != null);
+ checkState(databaseProvider != null || legacyStorageDir != null);
keyToContent = new HashMap<>();
idToKey = new SparseArray<>();
removedIds = new SparseBooleanArray();
newIds = new SparseBooleanArray();
+ @Nullable
Storage databaseStorage =
databaseProvider != null ? new DatabaseStorage(databaseProvider) : null;
+ @Nullable
Storage legacyStorage =
legacyStorageDir != null
? new LegacyStorage(
@@ -170,7 +176,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
legacyStorageEncrypt)
: null;
if (databaseStorage == null || (legacyStorage != null && preferLegacyStorage)) {
- storage = legacyStorage;
+ storage = castNonNull(legacyStorage);
previousStorage = databaseStorage;
} else {
storage = databaseStorage;
@@ -325,7 +331,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Returns a {@link ContentMetadata} for the given key. */
public ContentMetadata getContentMetadata(String key) {
- CachedContent cachedContent = get(key);
+ @Nullable CachedContent cachedContent = get(key);
return cachedContent != null ? cachedContent.getMetadata() : DefaultContentMetadata.EMPTY;
}
@@ -358,7 +364,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* returns the smallest unused non-negative integer.
*/
@VisibleForTesting
- /* package */ static int getNewId(SparseArray idToKey) {
+ /* package */ static int getNewId(SparseArray<@NullableType String> idToKey) {
int size = idToKey.size();
int id = size == 0 ? 0 : (idToKey.keyAt(size - 1) + 1);
if (id < 0) { // In case if we pass max int value.
@@ -512,8 +518,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable private ReusableBufferedOutputStream bufferedOutputStream;
public LegacyStorage(File file, @Nullable byte[] secretKey, boolean encrypt) {
- Cipher cipher = null;
- SecretKeySpec secretKeySpec = null;
+ checkState(secretKey != null || !encrypt);
+ @Nullable Cipher cipher = null;
+ @Nullable SecretKeySpec secretKeySpec = null;
if (secretKey != null) {
Assertions.checkArgument(secretKey.length == 16);
try {
@@ -550,7 +557,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void load(
HashMap content, SparseArray<@NullableType String> idToKey) {
- Assertions.checkState(!changed);
+ checkState(!changed);
if (!readFile(content, idToKey)) {
content.clear();
idToKey.clear();
@@ -588,7 +595,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return true;
}
- DataInputStream input = null;
+ @Nullable DataInputStream input = null;
try {
InputStream inputStream = new BufferedInputStream(atomicFile.openRead());
input = new DataInputStream(inputStream);
@@ -606,7 +613,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
input.readFully(initializationVector);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
try {
- cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
+ cipher.init(Cipher.DECRYPT_MODE, castNonNull(secretKeySpec), ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException(e);
}
@@ -647,6 +654,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} else {
bufferedOutputStream.reset(outputStream);
}
+ ReusableBufferedOutputStream bufferedOutputStream = this.bufferedOutputStream;
output = new DataOutputStream(bufferedOutputStream);
output.writeInt(VERSION);
@@ -655,11 +663,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
if (encrypt) {
byte[] initializationVector = new byte[16];
- random.nextBytes(initializationVector);
+ castNonNull(random).nextBytes(initializationVector);
output.write(initializationVector);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
try {
- cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+ castNonNull(cipher)
+ .init(Cipher.ENCRYPT_MODE, castNonNull(secretKeySpec), ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException(e); // Should never happen.
}
@@ -762,16 +771,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
+ " BLOB NOT NULL)";
private final DatabaseProvider databaseProvider;
- private final SparseArray pendingUpdates;
+ private final SparseArray<@NullableType CachedContent> pendingUpdates;
- private String hexUid;
- private String tableName;
+ private @MonotonicNonNull String hexUid;
+ private @MonotonicNonNull String tableName;
public static void delete(DatabaseProvider databaseProvider, long uid)
throws DatabaseIOException {
delete(databaseProvider, Long.toHexString(uid));
}
+ @SuppressWarnings("nullness:initialization.fields.uninitialized")
public DatabaseStorage(DatabaseProvider databaseProvider) {
this.databaseProvider = databaseProvider;
pendingUpdates = new SparseArray<>();
@@ -788,26 +798,26 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return VersionTable.getVersion(
databaseProvider.getReadableDatabase(),
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
- hexUid)
+ checkNotNull(hexUid))
!= VersionTable.VERSION_UNSET;
}
@Override
public void delete() throws DatabaseIOException {
- delete(databaseProvider, hexUid);
+ delete(databaseProvider, checkNotNull(hexUid));
}
@Override
public void load(
HashMap content, SparseArray<@NullableType String> idToKey)
throws IOException {
- Assertions.checkState(pendingUpdates.size() == 0);
+ checkState(pendingUpdates.size() == 0);
try {
int version =
VersionTable.getVersion(
databaseProvider.getReadableDatabase(),
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
- hexUid);
+ checkNotNull(hexUid));
if (version != TABLE_VERSION) {
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.beginTransactionNonExclusive();
@@ -871,7 +881,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
writableDatabase.beginTransactionNonExclusive();
try {
for (int i = 0; i < pendingUpdates.size(); i++) {
- CachedContent cachedContent = pendingUpdates.valueAt(i);
+ @Nullable CachedContent cachedContent = pendingUpdates.valueAt(i);
if (cachedContent == null) {
deleteRow(writableDatabase, pendingUpdates.keyAt(i));
} else {
@@ -906,7 +916,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return databaseProvider
.getReadableDatabase()
.query(
- tableName,
+ checkNotNull(tableName),
COLUMNS,
/* selection= */ null,
/* selectionArgs= */ null,
@@ -917,13 +927,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private void initializeTable(SQLiteDatabase writableDatabase) throws DatabaseIOException {
VersionTable.setVersion(
- writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, hexUid, TABLE_VERSION);
- dropTable(writableDatabase, tableName);
+ writableDatabase,
+ VersionTable.FEATURE_CACHE_CONTENT_METADATA,
+ checkNotNull(hexUid),
+ TABLE_VERSION);
+ dropTable(writableDatabase, checkNotNull(tableName));
writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA);
}
private void deleteRow(SQLiteDatabase writableDatabase, int key) {
- writableDatabase.delete(tableName, WHERE_ID_EQUALS, new String[] {Integer.toString(key)});
+ writableDatabase.delete(
+ checkNotNull(tableName), WHERE_ID_EQUALS, new String[] {Integer.toString(key)});
}
private void addOrUpdateRow(SQLiteDatabase writableDatabase, CachedContent cachedContent)
@@ -936,7 +950,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
values.put(COLUMN_ID, cachedContent.id);
values.put(COLUMN_KEY, cachedContent.key);
values.put(COLUMN_METADATA, data);
- writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values);
+ writableDatabase.replaceOrThrow(checkNotNull(tableName), /* nullColumnHack= */ null, values);
}
private static void delete(DatabaseProvider databaseProvider, String hexUid)
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
index 293264504f..aa82d41414 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
import com.google.android.exoplayer2.RendererCapabilities;
@@ -147,8 +148,9 @@ public class EventLogger implements AnalyticsListener {
}
@Override
- public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
- logd(eventTime, "playbackSpeed", Float.toString(playbackSpeed));
+ public void onPlaybackParametersChanged(
+ EventTime eventTime, PlaybackParameters playbackParameters) {
+ logd(eventTime, "playbackParameters", playbackParameters.toString());
}
@Override
@@ -316,13 +318,19 @@ public class EventLogger implements AnalyticsListener {
logd(eventTime, "audioInputFormat", Format.toLogString(format));
}
+ @Override
+ public void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {
+ long timeSincePlayoutStartMs = System.currentTimeMillis() - playoutStartSystemTimeMs;
+ logd(eventTime, "audioPositionAdvancing", "timeSincePlayoutStartMs=" + timeSincePlayoutStartMs);
+ }
+
@Override
public void onAudioUnderrun(
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
loge(
eventTime,
"audioTrackUnderrun",
- bufferSize + ", " + bufferSizeMs + ", " + elapsedSinceLastFeedMs + "]",
+ bufferSize + ", " + bufferSizeMs + ", " + elapsedSinceLastFeedMs,
/* throwable= */ null);
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java
index 44c3c5e7fa..df335908c0 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java
@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.util;
+import com.google.android.exoplayer2.PlaybackParameters;
+
/**
* Tracks the progression of media time.
*/
@@ -26,13 +28,13 @@ public interface MediaClock {
long getPositionUs();
/**
- * Attempts to set the playback speed. The media clock may override the speed if changing the
- * speed is not supported.
+ * Attempts to set the playback parameters. The media clock may override the speed if changing the
+ * playback parameters is not supported.
*
- * @param playbackSpeed The playback speed to attempt to set.
+ * @param playbackParameters The playback parameters to attempt to set.
*/
- void setPlaybackSpeed(float playbackSpeed);
+ void setPlaybackParameters(PlaybackParameters playbackParameters);
- /** Returns the active playback speed. */
- float getPlaybackSpeed();
+ /** Returns the active playback parameters. */
+ PlaybackParameters getPlaybackParameters();
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java
index e1df77a200..87970d3c00 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java
@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.util;
import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.PlaybackParameters;
/**
* A {@link MediaClock} whose position advances with real time based on the playback parameters when
@@ -29,8 +29,7 @@ public final class StandaloneMediaClock implements MediaClock {
private boolean started;
private long baseUs;
private long baseElapsedMs;
- private float playbackSpeed;
- private int scaledUsPerMs;
+ private PlaybackParameters playbackParameters;
/**
* Creates a new standalone media clock using the given {@link Clock} implementation.
@@ -39,8 +38,7 @@ public final class StandaloneMediaClock implements MediaClock {
*/
public StandaloneMediaClock(Clock clock) {
this.clock = clock;
- playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED;
- scaledUsPerMs = getScaledUsPerMs(playbackSpeed);
+ playbackParameters = PlaybackParameters.DEFAULT;
}
/**
@@ -80,33 +78,29 @@ public final class StandaloneMediaClock implements MediaClock {
long positionUs = baseUs;
if (started) {
long elapsedSinceBaseMs = clock.elapsedRealtime() - baseElapsedMs;
- if (playbackSpeed == 1f) {
+ if (playbackParameters.speed == 1f) {
positionUs += C.msToUs(elapsedSinceBaseMs);
} else {
// Add the media time in microseconds that will elapse in elapsedSinceBaseMs milliseconds of
// wallclock time
- positionUs += elapsedSinceBaseMs * scaledUsPerMs;
+ positionUs += playbackParameters.getMediaTimeUsForPlayoutTimeMs(elapsedSinceBaseMs);
}
}
return positionUs;
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
// Store the current position as the new base, in case the playback speed has changed.
if (started) {
resetPosition(getPositionUs());
}
- this.playbackSpeed = playbackSpeed;
- scaledUsPerMs = getScaledUsPerMs(playbackSpeed);
+ this.playbackParameters = playbackParameters;
}
@Override
- public float getPlaybackSpeed() {
- return playbackSpeed;
+ public PlaybackParameters getPlaybackParameters() {
+ return playbackParameters;
}
- private static int getScaledUsPerMs(float playbackSpeed) {
- return Math.round(playbackSpeed * 1000f);
- }
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
index 56e5457044..5b26588244 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
@@ -1611,6 +1611,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
case "ELUGA_Prim":
case "ELUGA_Ray_X":
case "EverStar_S":
+ case "F02H":
+ case "F03H":
case "F3111":
case "F3113":
case "F3116":
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java
index 217df762f6..867857cbe5 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.DefaultMediaClock.PlaybackSpeedListener;
+import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParametersListener;
import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import org.junit.Before;
@@ -36,9 +36,10 @@ public class DefaultMediaClockTest {
private static final long TEST_POSITION_US = 123456789012345678L;
private static final long SLEEP_TIME_MS = 1_000;
- private static final float TEST_PLAYBACK_SPEED = 2f;
+ private static final PlaybackParameters TEST_PLAYBACK_PARAMETERS =
+ new PlaybackParameters(/* speed= */ 2f);
- @Mock private PlaybackSpeedListener listener;
+ @Mock private PlaybackParametersListener listener;
private FakeClock fakeClock;
private DefaultMediaClock mediaClock;
@@ -109,44 +110,44 @@ public class DefaultMediaClockTest {
}
@Test
- public void standaloneGetPlaybackSpeed_initializedWithDefaultPlaybackSpeed() {
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
+ public void standaloneGetPlaybackParameters_initializedWithDefaultPlaybackParameters() {
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
- public void standaloneSetPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue() {
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
+ public void standaloneSetPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
- public void standaloneSetPlaybackSpeed_shouldNotTriggerCallback() {
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ public void standaloneSetPlaybackParameters_shouldNotTriggerCallback() {
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
verifyNoMoreInteractions(listener);
}
@Test
- public void standaloneSetPlaybackSpeed_shouldApplyNewPlaybackSpeed() {
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ public void standaloneSetPlaybackParameters_shouldApplyNewPlaybackParameters() {
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
mediaClock.start();
- // Asserts that clock is running with speed declared in getPlaybackSpeed().
+ // Asserts that clock is running with speed declared in getPlaybackParameters().
assertClockIsRunning(/* isReadingAhead= */ false);
}
@Test
- public void standaloneSetOtherPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue() {
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
- mediaClock.setPlaybackSpeed(Player.DEFAULT_PLAYBACK_SPEED);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
+ public void standaloneSetOtherPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ mediaClock.setPlaybackParameters(PlaybackParameters.DEFAULT);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
- public void enableRendererMediaClock_shouldOverwriteRendererPlaybackSpeedIfPossible()
+ public void enableRendererMediaClock_shouldOverwriteRendererPlaybackParametersIfPossible()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
+ new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
verifyNoMoreInteractions(listener);
}
@@ -154,26 +155,27 @@ public class DefaultMediaClockTest {
public void enableRendererMediaClockWithFixedPlaybackSpeed_usesRendererPlaybackSpeed()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
+ new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void enableRendererMediaClockWithFixedPlaybackSpeed_shouldTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
+ new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- verify(listener).onPlaybackSpeedChanged(TEST_PLAYBACK_SPEED);
+ verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void enableRendererMediaClockWithFixedButSamePlaybackSpeed_shouldNotTriggerCallback()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
+ new MediaClockRenderer(
+ PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
verifyNoMoreInteractions(listener);
@@ -182,44 +184,47 @@ public class DefaultMediaClockTest {
@Test
public void disableRendererMediaClock_shouldKeepPlaybackSpeed() throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
+ new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
mediaClock.onRendererDisabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
- public void rendererClockSetPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue()
+ public void rendererClockSetPlaybackSpeed_getPlaybackParametersShouldReturnSameValue()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
+ new MediaClockRenderer(
+ PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
}
@Test
public void rendererClockSetPlaybackSpeed_shouldNotTriggerCallback() throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
+ new MediaClockRenderer(
+ PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
verifyNoMoreInteractions(listener);
}
@Test
- public void rendererClockSetPlaybackSpeedOverwrite_getPlaybackSpeedShouldReturnSameValue()
+ public void rendererClockSetPlaybackSpeedOverwrite_getPlaybackParametersShouldReturnSameValue()
throws ExoPlaybackException {
FakeMediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
+ new MediaClockRenderer(
+ PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ false);
mediaClock.onRendererEnabled(mediaClockRenderer);
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
- assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
+ mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
+ assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
}
@Test
@@ -266,12 +271,13 @@ public class DefaultMediaClockTest {
public void getPositionWithPlaybackSpeedChange_shouldTriggerCallback()
throws ExoPlaybackException {
MediaClockRenderer mediaClockRenderer =
- new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
+ new MediaClockRenderer(
+ PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
mediaClock.onRendererEnabled(mediaClockRenderer);
// Silently change playback speed of renderer clock.
- mediaClockRenderer.playbackSpeed = TEST_PLAYBACK_SPEED;
+ mediaClockRenderer.playbackParameters = TEST_PLAYBACK_PARAMETERS;
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
- verify(listener).onPlaybackSpeedChanged(TEST_PLAYBACK_SPEED);
+ verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
}
@Test
@@ -356,7 +362,7 @@ public class DefaultMediaClockTest {
private void assertClockIsRunning(boolean isReadingAhead) {
long clockStartUs = mediaClock.syncAndGetPositionUs(isReadingAhead);
fakeClock.advanceTime(SLEEP_TIME_MS);
- int scaledUsPerMs = Math.round(mediaClock.getPlaybackSpeed() * 1000f);
+ int scaledUsPerMs = Math.round(mediaClock.getPlaybackParameters().speed * 1000f);
assertThat(mediaClock.syncAndGetPositionUs(isReadingAhead))
.isEqualTo(clockStartUs + (SLEEP_TIME_MS * scaledUsPerMs));
}
@@ -371,37 +377,53 @@ public class DefaultMediaClockTest {
@SuppressWarnings("HidingField")
private static class MediaClockRenderer extends FakeMediaClockRenderer {
- private final boolean playbackSpeedIsMutable;
+ private final boolean playbackParametersAreMutable;
private final boolean isReady;
private final boolean isEnded;
- public float playbackSpeed;
+ public PlaybackParameters playbackParameters;
public long positionUs;
public MediaClockRenderer() throws ExoPlaybackException {
- this(Player.DEFAULT_PLAYBACK_SPEED, false, true, false, false);
+ this(
+ PlaybackParameters.DEFAULT,
+ /* playbackParametersAreMutable= */ false,
+ /* isReady= */ true,
+ /* isEnded= */ false,
+ /* hasReadStreamToEnd= */ false);
}
- public MediaClockRenderer(float playbackSpeed, boolean playbackSpeedIsMutable)
+ public MediaClockRenderer(
+ PlaybackParameters playbackParameters, boolean playbackParametersAreMutable)
throws ExoPlaybackException {
- this(playbackSpeed, playbackSpeedIsMutable, true, false, false);
+ this(
+ playbackParameters,
+ playbackParametersAreMutable,
+ /* isReady= */ true,
+ /* isEnded= */ false,
+ /* hasReadStreamToEnd= */ false);
}
public MediaClockRenderer(boolean isReady, boolean isEnded, boolean hasReadStreamToEnd)
throws ExoPlaybackException {
- this(Player.DEFAULT_PLAYBACK_SPEED, false, isReady, isEnded, hasReadStreamToEnd);
+ this(
+ PlaybackParameters.DEFAULT,
+ /* playbackParametersAreMutable= */ false,
+ isReady,
+ isEnded,
+ hasReadStreamToEnd);
}
private MediaClockRenderer(
- float playbackSpeed,
- boolean playbackSpeedIsMutable,
+ PlaybackParameters playbackParameters,
+ boolean playbackParametersAreMutable,
boolean isReady,
boolean isEnded,
boolean hasReadStreamToEnd)
throws ExoPlaybackException {
super(C.TRACK_TYPE_UNKNOWN);
- this.playbackSpeed = playbackSpeed;
- this.playbackSpeedIsMutable = playbackSpeedIsMutable;
+ this.playbackParameters = playbackParameters;
+ this.playbackParametersAreMutable = playbackParametersAreMutable;
this.isReady = isReady;
this.isEnded = isEnded;
this.positionUs = TEST_POSITION_US;
@@ -416,15 +438,15 @@ public class DefaultMediaClockTest {
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {
- if (playbackSpeedIsMutable) {
- this.playbackSpeed = playbackSpeed;
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {
+ if (playbackParametersAreMutable) {
+ this.playbackParameters = playbackParameters;
}
}
@Override
- public float getPlaybackSpeed() {
- return playbackSpeed;
+ public PlaybackParameters getPlaybackParameters() {
+ return playbackParameters;
}
@Override
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
index 3c43d3c22f..0043cb9e74 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java
@@ -328,11 +328,11 @@ public final class ExoPlayerTest {
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {}
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
@Override
- public float getPlaybackSpeed() {
- return Player.DEFAULT_PLAYBACK_SPEED;
+ public PlaybackParameters getPlaybackParameters() {
+ return PlaybackParameters.DEFAULT;
}
@Override
@@ -1010,7 +1010,7 @@ public final class ExoPlayerTest {
}
})
// Set playback speed (while the fake media period is not yet prepared).
- .setPlaybackSpeed(2f)
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f))
// Complete preparation of the fake media period.
.executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
.build();
@@ -3378,18 +3378,18 @@ public final class ExoPlayerTest {
SimpleExoPlayer player,
DefaultTrackSelector trackSelector,
@Nullable Surface surface) {
- maskedPlaybackSpeeds.add(player.getPlaybackSpeed());
+ maskedPlaybackSpeeds.add(player.getPlaybackParameters().speed);
}
};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
- .setPlaybackSpeed(1.1f)
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.1f))
.apply(getPlaybackSpeedAction)
- .setPlaybackSpeed(1.2f)
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.2f))
.apply(getPlaybackSpeedAction)
- .setPlaybackSpeed(1.3f)
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.3f))
.apply(getPlaybackSpeedAction)
.play()
.build();
@@ -3397,8 +3397,8 @@ public final class ExoPlayerTest {
EventListener listener =
new EventListener() {
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
- reportedPlaybackSpeeds.add(playbackSpeed);
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ reportedPlaybackSpeeds.add(playbackParameters.speed);
}
};
new ExoPlayerTestRunner.Builder(context)
@@ -3424,28 +3424,28 @@ public final class ExoPlayerTest {
}
@Override
- public void setPlaybackSpeed(float playbackSpeed) {}
+ public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
@Override
- public float getPlaybackSpeed() {
- return Player.DEFAULT_PLAYBACK_SPEED;
+ public PlaybackParameters getPlaybackParameters() {
+ return PlaybackParameters.DEFAULT;
}
};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
- .setPlaybackSpeed(1.1f)
- .setPlaybackSpeed(1.2f)
- .setPlaybackSpeed(1.3f)
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.1f))
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.2f))
+ .setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.3f))
.play()
.build();
- List reportedPlaybackParameters = new ArrayList<>();
+ List reportedPlaybackParameters = new ArrayList<>();
EventListener listener =
new EventListener() {
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
- reportedPlaybackParameters.add(playbackSpeed);
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
+ reportedPlaybackParameters.add(playbackParameters);
}
};
new ExoPlayerTestRunner.Builder(context)
@@ -3458,7 +3458,11 @@ public final class ExoPlayerTest {
.blockUntilEnded(TIMEOUT_MS);
assertThat(reportedPlaybackParameters)
- .containsExactly(1.1f, 1.2f, 1.3f, Player.DEFAULT_PLAYBACK_SPEED)
+ .containsExactly(
+ new PlaybackParameters(/* speed= */ 1.1f),
+ new PlaybackParameters(/* speed= */ 1.2f),
+ new PlaybackParameters(/* speed= */ 1.3f),
+ PlaybackParameters.DEFAULT)
.inOrder();
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java
index 7fa586c323..20be8fe12b 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java
@@ -435,7 +435,7 @@ public final class MediaPeriodQueueTest {
/* loadingMediaPeriodId= */ null,
/* playWhenReady= */ false,
Player.PLAYBACK_SUPPRESSION_REASON_NONE,
- /* playbackSpeed= */ Player.DEFAULT_PLAYBACK_SPEED,
+ /* playbackParameters= */ PlaybackParameters.DEFAULT,
/* bufferedPositionUs= */ 0,
/* totalBufferedDurationUs= */ 0,
/* positionUs= */ 0,
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java
index 7d3dfdf103..1238831cbc 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java
@@ -26,6 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RenderersFactory;
@@ -82,7 +83,7 @@ public final class AnalyticsCollectorTest {
private static final int EVENT_POSITION_DISCONTINUITY = 2;
private static final int EVENT_SEEK_STARTED = 3;
private static final int EVENT_SEEK_PROCESSED = 4;
- private static final int EVENT_PLAYBACK_SPEED_CHANGED = 5;
+ private static final int EVENT_PLAYBACK_PARAMETERS_CHANGED = 5;
private static final int EVENT_REPEAT_MODE_CHANGED = 6;
private static final int EVENT_SHUFFLE_MODE_CHANGED = 7;
private static final int EVENT_LOADING_CHANGED = 8;
@@ -106,21 +107,22 @@ public final class AnalyticsCollectorTest {
private static final int EVENT_AUDIO_INPUT_FORMAT_CHANGED = 26;
private static final int EVENT_AUDIO_DISABLED = 27;
private static final int EVENT_AUDIO_SESSION_ID = 28;
- private static final int EVENT_AUDIO_UNDERRUN = 29;
- private static final int EVENT_VIDEO_ENABLED = 30;
- private static final int EVENT_VIDEO_DECODER_INIT = 31;
- private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 32;
- private static final int EVENT_DROPPED_FRAMES = 33;
- private static final int EVENT_VIDEO_DISABLED = 34;
- private static final int EVENT_RENDERED_FIRST_FRAME = 35;
- private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 36;
- private static final int EVENT_VIDEO_SIZE_CHANGED = 37;
- private static final int EVENT_DRM_KEYS_LOADED = 38;
- private static final int EVENT_DRM_ERROR = 39;
- private static final int EVENT_DRM_KEYS_RESTORED = 40;
- private static final int EVENT_DRM_KEYS_REMOVED = 41;
- private static final int EVENT_DRM_SESSION_ACQUIRED = 42;
- private static final int EVENT_DRM_SESSION_RELEASED = 43;
+ private static final int EVENT_AUDIO_POSITION_ADVANCING = 29;
+ private static final int EVENT_AUDIO_UNDERRUN = 30;
+ private static final int EVENT_VIDEO_ENABLED = 31;
+ private static final int EVENT_VIDEO_DECODER_INIT = 32;
+ private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 33;
+ private static final int EVENT_DROPPED_FRAMES = 34;
+ private static final int EVENT_VIDEO_DISABLED = 35;
+ private static final int EVENT_RENDERED_FIRST_FRAME = 36;
+ private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 37;
+ private static final int EVENT_VIDEO_SIZE_CHANGED = 38;
+ private static final int EVENT_DRM_KEYS_LOADED = 39;
+ private static final int EVENT_DRM_ERROR = 40;
+ private static final int EVENT_DRM_KEYS_RESTORED = 41;
+ private static final int EVENT_DRM_KEYS_REMOVED = 42;
+ private static final int EVENT_DRM_SESSION_ACQUIRED = 43;
+ private static final int EVENT_DRM_SESSION_RELEASED = 44;
private static final UUID DRM_SCHEME_UUID =
UUID.nameUUIDFromBytes(TestUtil.createByteArray(7, 8, 9));
@@ -226,6 +228,7 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
+ assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
@@ -305,6 +308,7 @@ public final class AnalyticsCollectorTest {
.containsExactly(period0, period1)
.inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
+ assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
.containsExactly(period0, period1)
@@ -380,6 +384,7 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
+ assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
@@ -476,6 +481,9 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
.containsExactly(period0, period1)
.inOrder();
+ assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING))
+ .containsExactly(period0, period1)
+ .inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
@@ -576,6 +584,9 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
.containsExactly(period1Seq1, period1Seq2)
.inOrder();
+ assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING))
+ .containsExactly(period1Seq1, period1Seq2)
+ .inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0, period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
@@ -1785,8 +1796,9 @@ public final class AnalyticsCollectorTest {
}
@Override
- public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
- reportedEvents.add(new ReportedEvent(EVENT_PLAYBACK_SPEED_CHANGED, eventTime));
+ public void onPlaybackParametersChanged(
+ EventTime eventTime, PlaybackParameters playbackParameters) {
+ reportedEvents.add(new ReportedEvent(EVENT_PLAYBACK_PARAMETERS_CHANGED, eventTime));
}
@Override
@@ -1922,6 +1934,11 @@ public final class AnalyticsCollectorTest {
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_SESSION_ID, eventTime));
}
+ @Override
+ public void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {
+ reportedEvents.add(new ReportedEvent(EVENT_AUDIO_POSITION_ADVANCING, eventTime));
+ }
+
@Override
public void onAudioUnderrun(
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java
index 8a1f8807ea..1f19c2af58 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/PlaybackStatsListenerTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify;
import android.os.SystemClock;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource;
@@ -75,8 +76,8 @@ public final class PlaybackStatsListenerTest {
playbackStatsListener.onPositionDiscontinuity(
EMPTY_TIMELINE_EVENT_TIME, Player.DISCONTINUITY_REASON_SEEK);
- playbackStatsListener.onPlaybackSpeedChanged(
- EMPTY_TIMELINE_EVENT_TIME, /* playbackSpeed= */ 2.0f);
+ playbackStatsListener.onPlaybackParametersChanged(
+ EMPTY_TIMELINE_EVENT_TIME, new PlaybackParameters(/* speed= */ 2.0f));
playbackStatsListener.onPlayWhenReadyChanged(
EMPTY_TIMELINE_EVENT_TIME,
/* playWhenReady= */ true,
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
index 54628f91be..2f86988d42 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java
@@ -25,6 +25,7 @@ import static org.robolectric.annotation.Config.TARGET_SDK;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.util.MimeTypes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -89,7 +90,7 @@ public final class DefaultAudioSinkTest {
@Test
public void handlesBufferAfterReset_withPlaybackSpeed() throws Exception {
- defaultAudioSink.setPlaybackSpeed(/* playbackSpeed= */ 1.5f);
+ defaultAudioSink.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.5f));
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
@@ -99,7 +100,8 @@ public final class DefaultAudioSinkTest {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
- assertThat(defaultAudioSink.getPlaybackSpeed()).isEqualTo(1.5f);
+ assertThat(defaultAudioSink.getPlaybackParameters())
+ .isEqualTo(new PlaybackParameters(/* speed= */ 1.5f));
}
@Test
@@ -117,7 +119,7 @@ public final class DefaultAudioSinkTest {
@Test
public void handlesBufferAfterReset_withFormatChangeAndPlaybackSpeed() throws Exception {
- defaultAudioSink.setPlaybackSpeed(/* playbackSpeed= */ 1.5f);
+ defaultAudioSink.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.5f));
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
@@ -127,7 +129,8 @@ public final class DefaultAudioSinkTest {
configureDefaultAudioSink(CHANNEL_COUNT_MONO);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
- assertThat(defaultAudioSink.getPlaybackSpeed()).isEqualTo(1.5f);
+ assertThat(defaultAudioSink.getPlaybackParameters())
+ .isEqualTo(new PlaybackParameters(/* speed= */ 1.5f));
}
@Test
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java
index 82c8824de1..922431d210 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/MediaCodecAudioRendererTest.java
@@ -49,8 +49,10 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.Config;
/** Unit tests for {@link MediaCodecAudioRenderer} */
+@Config(sdk = 29)
@RunWith(AndroidJUnit4.class)
public class MediaCodecAudioRendererTest {
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java
new file mode 100644
index 0000000000..684399d845
--- /dev/null
+++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/Mp4PlaybackTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.e2etest;
+
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
+import com.google.android.exoplayer2.testutil.DumpFileAsserts;
+import com.google.android.exoplayer2.testutil.PlaybackOutput;
+import com.google.android.exoplayer2.testutil.ShadowMediaCodecConfig;
+import com.google.android.exoplayer2.testutil.TestExoPlayer;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** End-to-end tests using MP4 samples. */
+// TODO(b/143232359): Remove once https://issuetracker.google.com/143232359 is resolved.
+@Config(sdk = 29)
+@RunWith(AndroidJUnit4.class)
+public class Mp4PlaybackTest {
+ @Rule
+ public ShadowMediaCodecConfig mediaCodecConfig =
+ ShadowMediaCodecConfig.forAllSupportedMimeTypes();
+
+ @Test
+ public void h264VideoAacAudio() throws Exception {
+ SimpleExoPlayer player =
+ new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext())
+ .setClock(new AutoAdvancingFakeClock())
+ .build();
+ player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, mediaCodecConfig);
+
+ player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4"));
+ player.prepare();
+ player.play();
+ TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
+
+ DumpFileAsserts.assertOutput(
+ ApplicationProvider.getApplicationContext(),
+ playbackOutput,
+ "playbackdumps/mp4/sample.mp4.dump");
+ }
+}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java
new file mode 100644
index 0000000000..c78e4cfe96
--- /dev/null
+++ b/library/core/src/test/java/com/google/android/exoplayer2/e2etest/TsPlaybackTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.e2etest;
+
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
+import com.google.android.exoplayer2.testutil.DumpFileAsserts;
+import com.google.android.exoplayer2.testutil.PlaybackOutput;
+import com.google.android.exoplayer2.testutil.ShadowMediaCodecConfig;
+import com.google.android.exoplayer2.testutil.TestExoPlayer;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** End-to-end tests using TS samples. */
+// TODO(b/143232359): Remove once https://issuetracker.google.com/143232359 is resolved.
+@Config(sdk = 29)
+@RunWith(AndroidJUnit4.class)
+public class TsPlaybackTest {
+
+ @Rule
+ public ShadowMediaCodecConfig mediaCodecConfig =
+ ShadowMediaCodecConfig.forAllSupportedMimeTypes();
+
+ @Test
+ public void mpegVideoMpegAudioScte35() throws Exception {
+ SimpleExoPlayer player =
+ new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext())
+ .setClock(new AutoAdvancingFakeClock())
+ .build();
+ player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, mediaCodecConfig);
+
+ player.setMediaItem(MediaItem.fromUri("asset:///media/ts/sample_scte35.ts"));
+ player.prepare();
+ player.play();
+ TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
+
+ DumpFileAsserts.assertOutput(
+ ApplicationProvider.getApplicationContext(),
+ playbackOutput,
+ "playbackdumps/ts/sample_scte35.ts.dump");
+ }
+}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/MediaSourceDrmHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MediaSourceDrmHelperTest.java
new file mode 100644
index 0000000000..45384f05ec
--- /dev/null
+++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MediaSourceDrmHelperTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.source;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link MediaSourceDrmHelper}. */
+@RunWith(AndroidJUnit4.class)
+public class MediaSourceDrmHelperTest {
+
+ @Test
+ public void create_noDrmProperties_createsNoopManager() {
+ DrmSessionManager drmSessionManager =
+ new MediaSourceDrmHelper().create(MediaItem.fromUri(Uri.EMPTY));
+
+ assertThat(drmSessionManager).isEqualTo(DrmSessionManager.DUMMY);
+ }
+
+ @Test
+ public void create_createsManager() {
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setUri(Uri.EMPTY)
+ .setDrmLicenseUri(Uri.EMPTY)
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .build();
+
+ DrmSessionManager drmSessionManager = new MediaSourceDrmHelper().create(mediaItem);
+
+ assertThat(drmSessionManager).isNotEqualTo(DrmSessionManager.DUMMY);
+ }
+}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
index 4583c542b3..54e2dd902d 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.Allocator;
@@ -54,7 +55,6 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/** Test for {@link SampleQueue}. */
@@ -69,6 +69,8 @@ public final class SampleQueueTest {
private static final Format FORMAT_SPLICED = buildFormat(/* id= */ "spliced");
private static final Format FORMAT_ENCRYPTED =
new Format.Builder().setId(/* id= */ "encrypted").setDrmInitData(new DrmInitData()).build();
+ private static final Format FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE =
+ FORMAT_ENCRYPTED.copyWithExoMediaCryptoType(MockExoMediaCrypto.class);
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
/*
@@ -128,7 +130,7 @@ public final class SampleQueueTest {
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
private Allocator allocator;
- private DrmSessionManager mockDrmSessionManager;
+ private MockDrmSessionManager mockDrmSessionManager;
private DrmSession mockDrmSession;
private DrmSessionEventListener.EventDispatcher eventDispatcher;
private SampleQueue sampleQueue;
@@ -138,11 +140,8 @@ public final class SampleQueueTest {
@Before
public void setUp() {
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
- mockDrmSessionManager = Mockito.mock(DrmSessionManager.class);
mockDrmSession = Mockito.mock(DrmSession.class);
- when(mockDrmSessionManager.acquireSession(
- ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()))
- .thenReturn(mockDrmSession);
+ mockDrmSessionManager = new MockDrmSessionManager(mockDrmSession);
eventDispatcher = new DrmSessionEventListener.EventDispatcher();
sampleQueue =
new SampleQueue(
@@ -399,7 +398,7 @@ public final class SampleQueueTest {
@Test
public void isReadyReturnsTrueForValidDrmSession() {
writeTestDataWithEncryptedSections();
- assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
@@ -424,7 +423,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
- assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertReadNothing(/* formatRequired= */ false);
assertThat(inputBuffer.waitingForKeys).isTrue();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
@@ -464,9 +463,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
- when(mockDrmSessionManager.acquirePlaceholderSession(
- ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
- .thenReturn(mockPlaceholderDrmSession);
+ mockDrmSessionManager.mockPlaceholderDrmSession = mockPlaceholderDrmSession;
writeTestDataWithEncryptedSections();
int result =
@@ -497,9 +494,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
- when(mockDrmSessionManager.acquirePlaceholderSession(
- ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
- .thenReturn(mockPlaceholderDrmSession);
+ mockDrmSessionManager.mockPlaceholderDrmSession = mockPlaceholderDrmSession;
writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
byte[] sampleData = new byte[] {0, 1, 2};
@@ -540,7 +535,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
- assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertReadNothing(/* formatRequired= */ false);
sampleQueue.maybeThrowError();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_ERROR);
@@ -569,7 +564,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
- assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertReadEncryptedSample(/* sampleIndex= */ 0);
}
@@ -1497,4 +1492,33 @@ public final class SampleQueueTest {
private static Format copyWithLabel(Format format, String label) {
return format.buildUpon().setLabel(label).build();
}
+
+ private static final class MockExoMediaCrypto implements ExoMediaCrypto {}
+
+ private static final class MockDrmSessionManager implements DrmSessionManager {
+
+ private final DrmSession mockDrmSession;
+ @Nullable private DrmSession mockPlaceholderDrmSession;
+
+ private MockDrmSessionManager(DrmSession mockDrmSession) {
+ this.mockDrmSession = mockDrmSession;
+ }
+
+ @Nullable
+ @Override
+ public DrmSession acquireSession(
+ Looper playbackLooper,
+ @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
+ Format format) {
+ return format.drmInitData != null ? mockDrmSession : mockPlaceholderDrmSession;
+ }
+
+ @Nullable
+ @Override
+ public Class extends ExoMediaCrypto> getExoMediaCryptoType(Format format) {
+ return mockPlaceholderDrmSession != null || format.drmInitData != null
+ ? MockExoMediaCrypto.class
+ : null;
+ }
+ }
}
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
index 260c11fa62..2f5b169e30 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
@@ -30,7 +30,6 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.offline.FilteringManifestParser;
@@ -42,6 +41,7 @@ import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceDrmHelper;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaSourceFactory;
@@ -54,6 +54,7 @@ import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
import com.google.android.exoplayer2.upstream.Loader;
@@ -91,9 +92,10 @@ public final class DashMediaSource extends BaseMediaSource {
public static final class Factory implements MediaSourceFactory {
private final DashChunkSource.Factory chunkSourceFactory;
+ private final MediaSourceDrmHelper mediaSourceDrmHelper;
@Nullable private final DataSource.Factory manifestDataSourceFactory;
- private DrmSessionManager drmSessionManager;
+ @Nullable private DrmSessionManager drmSessionManager;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long livePresentationDelayMs;
@@ -126,7 +128,7 @@ public final class DashMediaSource extends BaseMediaSource {
@Nullable DataSource.Factory manifestDataSourceFactory) {
this.chunkSourceFactory = checkNotNull(chunkSourceFactory);
this.manifestDataSourceFactory = manifestDataSourceFactory;
- drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
+ mediaSourceDrmHelper = new MediaSourceDrmHelper();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
@@ -155,19 +157,22 @@ public final class DashMediaSource extends BaseMediaSource {
return this;
}
- /**
- * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
- * default value is {@link DrmSessionManager#DUMMY}.
- *
- * @param drmSessionManager The {@link DrmSessionManager}.
- * @return This factory, for convenience.
- */
@Override
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
- this.drmSessionManager =
- drmSessionManager != null
- ? drmSessionManager
- : DrmSessionManager.getDummyDrmSessionManager();
+ this.drmSessionManager = drmSessionManager;
+ return this;
+ }
+
+ @Override
+ public Factory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
+ return this;
+ }
+
+ @Override
+ public Factory setDrmUserAgent(@Nullable String userAgent) {
+ mediaSourceDrmHelper.setDrmUserAgent(userAgent);
return this;
}
@@ -312,7 +317,7 @@ public final class DashMediaSource extends BaseMediaSource {
/* manifestParser= */ null,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
- drmSessionManager,
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
loadErrorHandlingPolicy,
livePresentationDelayMs,
livePresentationDelayOverridesManifest);
@@ -403,7 +408,7 @@ public final class DashMediaSource extends BaseMediaSource {
manifestParser,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
- drmSessionManager,
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
loadErrorHandlingPolicy,
livePresentationDelayMs,
livePresentationDelayOverridesManifest);
diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java
index a59cb1d1f2..95b1daeb6e 100644
--- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java
+++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java
@@ -20,10 +20,34 @@ package com.google.android.exoplayer2.extractor;
*/
public interface ExtractorOutput {
+ /**
+ * Placeholder {@link ExtractorOutput} implementation throwing an {@link
+ * UnsupportedOperationException} in each method.
+ */
+ ExtractorOutput PLACEHOLDER =
+ new ExtractorOutput() {
+
+ @Override
+ public TrackOutput track(int id, int type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void endTracks() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void seekMap(SeekMap seekMap) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
/**
* Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
- *
- * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
+ *
+ *
The same {@link TrackOutput} is returned if multiple calls are made with the same {@code
+ * id}.
*
* @param id A track identifier.
* @param type The type of the track. Typically one of the {@link com.google.android.exoplayer2.C}
diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
index 287ebc6ce6..660605ebe5 100644
--- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
+++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java
@@ -48,6 +48,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.AvcConfig;
import com.google.android.exoplayer2.video.ColorInfo;
+import com.google.android.exoplayer2.video.DolbyVisionConfig;
import com.google.android.exoplayer2.video.HevcConfig;
import java.io.IOException;
import java.lang.annotation.Documented;
@@ -170,6 +171,9 @@ public class MatroskaExtractor implements Extractor {
private static final int ID_FLAG_FORCED = 0x55AA;
private static final int ID_DEFAULT_DURATION = 0x23E383;
private static final int ID_MAX_BLOCK_ADDITION_ID = 0x55EE;
+ private static final int ID_BLOCK_ADDITION_MAPPING = 0x41E4;
+ private static final int ID_BLOCK_ADD_ID_TYPE = 0x41E7;
+ private static final int ID_BLOCK_ADD_ID_EXTRA_DATA = 0x41ED;
private static final int ID_NAME = 0x536E;
private static final int ID_CODEC_ID = 0x86;
private static final int ID_CODEC_PRIVATE = 0x63A2;
@@ -234,6 +238,17 @@ public class MatroskaExtractor implements Extractor {
*/
private static final int BLOCK_ADDITIONAL_ID_VP9_ITU_T_35 = 4;
+ /**
+ * BlockAddIdType value for Dolby Vision configuration with profile <= 7. See also
+ * https://www.matroska.org/technical/codec_specs.html.
+ */
+ private static final int BLOCK_ADD_ID_TYPE_DVCC = 0x64766343;
+ /**
+ * BlockAddIdType value for Dolby Vision configuration with profile > 7. See also
+ * https://www.matroska.org/technical/codec_specs.html.
+ */
+ private static final int BLOCK_ADD_ID_TYPE_DVVC = 0x64767643;
+
private static final int LACING_NONE = 0;
private static final int LACING_XIPH = 1;
private static final int LACING_FIXED_SIZE = 2;
@@ -501,6 +516,7 @@ public class MatroskaExtractor implements Extractor {
case ID_CLUSTER:
case ID_TRACKS:
case ID_TRACK_ENTRY:
+ case ID_BLOCK_ADDITION_MAPPING:
case ID_AUDIO:
case ID_VIDEO:
case ID_CONTENT_ENCODINGS:
@@ -535,6 +551,7 @@ public class MatroskaExtractor implements Extractor {
case ID_FLAG_FORCED:
case ID_DEFAULT_DURATION:
case ID_MAX_BLOCK_ADDITION_ID:
+ case ID_BLOCK_ADD_ID_TYPE:
case ID_CODEC_DELAY:
case ID_SEEK_PRE_ROLL:
case ID_CHANNELS:
@@ -562,6 +579,7 @@ public class MatroskaExtractor implements Extractor {
case ID_LANGUAGE:
return EbmlProcessor.ELEMENT_TYPE_STRING;
case ID_SEEK_ID:
+ case ID_BLOCK_ADD_ID_EXTRA_DATA:
case ID_CONTENT_COMPRESSION_SETTINGS:
case ID_CONTENT_ENCRYPTION_KEY_ID:
case ID_SIMPLE_BLOCK:
@@ -814,6 +832,9 @@ public class MatroskaExtractor implements Extractor {
case ID_MAX_BLOCK_ADDITION_ID:
currentTrack.maxBlockAdditionId = (int) value;
break;
+ case ID_BLOCK_ADD_ID_TYPE:
+ currentTrack.blockAddIdType = (int) value;
+ break;
case ID_CODEC_DELAY:
currentTrack.codecDelayNs = value;
break;
@@ -1076,6 +1097,9 @@ public class MatroskaExtractor implements Extractor {
seekEntryIdBytes.setPosition(0);
seekEntryId = (int) seekEntryIdBytes.readUnsignedInt();
break;
+ case ID_BLOCK_ADD_ID_EXTRA_DATA:
+ handleBlockAddIDExtraData(currentTrack, input, contentSize);
+ break;
case ID_CODEC_PRIVATE:
currentTrack.codecPrivate = new byte[contentSize];
input.readFully(currentTrack.codecPrivate, 0, contentSize);
@@ -1244,6 +1268,18 @@ public class MatroskaExtractor implements Extractor {
}
}
+ protected void handleBlockAddIDExtraData(Track track, ExtractorInput input, int contentSize)
+ throws IOException {
+ if (track.blockAddIdType == BLOCK_ADD_ID_TYPE_DVVC
+ || track.blockAddIdType == BLOCK_ADD_ID_TYPE_DVCC) {
+ track.dolbyVisionConfigBytes = new byte[contentSize];
+ input.readFully(track.dolbyVisionConfigBytes, 0, contentSize);
+ } else {
+ // Unhandled BlockAddIDExtraData.
+ input.skipFully(contentSize);
+ }
+ }
+
protected void handleBlockAdditionalData(
Track track, int blockAdditionalId, ExtractorInput input, int contentSize)
throws IOException {
@@ -1883,6 +1919,7 @@ public class MatroskaExtractor implements Extractor {
public int type;
public int defaultSampleDurationNs;
public int maxBlockAdditionId;
+ private int blockAddIdType;
public boolean hasContentEncryption;
public byte[] sampleStrippedBytes;
public TrackOutput.CryptoData cryptoData;
@@ -1921,6 +1958,7 @@ public class MatroskaExtractor implements Extractor {
public float whitePointChromaticityY = Format.NO_VALUE;
public float maxMasteringLuminance = Format.NO_VALUE;
public float minMasteringLuminance = Format.NO_VALUE;
+ @Nullable public byte[] dolbyVisionConfigBytes;
// Audio elements. Initially set to their default values.
public int channelCount = 1;
@@ -2091,6 +2129,16 @@ public class MatroskaExtractor implements Extractor {
throw new ParserException("Unrecognized codec identifier.");
}
+ if (dolbyVisionConfigBytes != null) {
+ @Nullable
+ DolbyVisionConfig dolbyVisionConfig =
+ DolbyVisionConfig.parse(new ParsableByteArray(this.dolbyVisionConfigBytes));
+ if (dolbyVisionConfig != null) {
+ codecs = dolbyVisionConfig.codecs;
+ mimeType = MimeTypes.VIDEO_DOLBY_VISION;
+ }
+ }
+
@C.SelectionFlags int selectionFlags = 0;
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
index 756cd43fcc..859ce49b26 100644
--- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
+++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
@@ -16,6 +16,10 @@
package com.google.android.exoplayer2.extractor.mp4;
import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks;
+import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
+import static com.google.android.exoplayer2.util.Assertions.checkState;
+import static com.google.android.exoplayer2.util.Util.castNonNull;
+import static com.google.android.exoplayer2.util.Util.nullSafeArrayCopy;
import static java.lang.Math.max;
import android.util.Pair;
@@ -42,7 +46,6 @@ import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
-import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
@@ -59,7 +62,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Extracts data from the FMP4 container format. */
@SuppressWarnings("ConstantField")
@@ -175,7 +177,7 @@ public class FragmentedMp4Extractor implements Extractor {
private boolean processSeiNalUnitPayload;
// Outputs.
- private @MonotonicNonNull ExtractorOutput extractorOutput;
+ private ExtractorOutput extractorOutput;
private TrackOutput[] emsgTrackOutputs;
private TrackOutput[] ceaTrackOutputs;
@@ -270,9 +272,9 @@ public class FragmentedMp4Extractor implements Extractor {
durationUs = C.TIME_UNSET;
pendingSeekTimeUs = C.TIME_UNSET;
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
+ extractorOutput = ExtractorOutput.PLACEHOLDER;
emsgTrackOutputs = new TrackOutput[0];
ceaTrackOutputs = new TrackOutput[0];
- enterReadingAtomHeaderState();
}
@Override
@@ -283,6 +285,7 @@ public class FragmentedMp4Extractor implements Extractor {
@Override
public void init(ExtractorOutput output) {
extractorOutput = output;
+ enterReadingAtomHeaderState();
initExtraTracks();
if (sideloadedTrack != null) {
TrackBundle bundle =
@@ -429,8 +432,9 @@ public class FragmentedMp4Extractor implements Extractor {
if (atomSize > Integer.MAX_VALUE) {
throw new ParserException("Leaf atom with length > 2147483647 (unsupported).");
}
- atomData = new ParsableByteArray((int) atomSize);
+ ParsableByteArray atomData = new ParsableByteArray((int) atomSize);
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE);
+ this.atomData = atomData;
parserState = STATE_READING_ATOM_PAYLOAD;
} else {
if (atomSize > Integer.MAX_VALUE) {
@@ -445,6 +449,7 @@ public class FragmentedMp4Extractor implements Extractor {
private void readAtomPayload(ExtractorInput input) throws IOException {
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
+ @Nullable ParsableByteArray atomData = this.atomData;
if (atomData != null) {
input.readFully(atomData.getData(), Atom.HEADER_SIZE, atomPayloadSize);
onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
@@ -485,12 +490,12 @@ public class FragmentedMp4Extractor implements Extractor {
}
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
- Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
+ checkState(sideloadedTrack == null, "Unexpected moov box.");
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
- // Read declaration of track fragments in the Moov box.
- ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
+ // Read declaration of track fragments in the moov box.
+ ContainerAtom mvex = checkNotNull(moov.getContainerAtomOfType(Atom.TYPE_mvex));
SparseArray defaultSampleValuesArray = new SparseArray<>();
long duration = C.TIME_UNSET;
int mvexChildrenSize = mvex.leafChildren.size();
@@ -531,7 +536,7 @@ public class FragmentedMp4Extractor implements Extractor {
}
extractorOutput.endTracks();
} else {
- Assertions.checkState(trackBundles.size() == trackCount);
+ checkState(trackBundles.size() == trackCount);
for (int i = 0; i < trackCount; i++) {
TrackSampleTable sampleTable = sampleTables.get(i);
Track track = sampleTable.track;
@@ -554,7 +559,7 @@ public class FragmentedMp4Extractor implements Extractor {
// See https://github.com/google/ExoPlayer/issues/4477.
return defaultSampleValuesArray.valueAt(/* index= */ 0);
}
- return Assertions.checkNotNull(defaultSampleValuesArray.get(trackId));
+ return checkNotNull(defaultSampleValuesArray.get(trackId));
}
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
@@ -589,7 +594,7 @@ public class FragmentedMp4Extractor implements Extractor {
emsgTrackOutputs[emsgTrackOutputCount++] =
extractorOutput.track(nextExtraTrackId++, C.TRACK_TYPE_METADATA);
}
- emsgTrackOutputs = Arrays.copyOf(emsgTrackOutputs, emsgTrackOutputCount);
+ emsgTrackOutputs = nullSafeArrayCopy(emsgTrackOutputs, emsgTrackOutputCount);
for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) {
eventMessageTrackOutput.format(EMSG_FORMAT);
}
@@ -604,7 +609,7 @@ public class FragmentedMp4Extractor implements Extractor {
/** Handles an emsg atom (defined in 23009-1). */
private void onEmsgLeafAtomRead(ParsableByteArray atom) {
- if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) {
+ if (emsgTrackOutputs.length == 0) {
return;
}
atom.setPosition(Atom.HEADER_SIZE);
@@ -619,8 +624,8 @@ public class FragmentedMp4Extractor implements Extractor {
long id;
switch (version) {
case 0:
- schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
- value = Assertions.checkNotNull(atom.readNullTerminatedString());
+ schemeIdUri = checkNotNull(atom.readNullTerminatedString());
+ value = checkNotNull(atom.readNullTerminatedString());
timescale = atom.readUnsignedInt();
presentationTimeDeltaUs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
@@ -638,8 +643,8 @@ public class FragmentedMp4Extractor implements Extractor {
durationMs =
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
id = atom.readUnsignedInt();
- schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
- value = Assertions.checkNotNull(atom.readNullTerminatedString());
+ schemeIdUri = checkNotNull(atom.readNullTerminatedString());
+ value = checkNotNull(atom.readNullTerminatedString());
break;
default:
Log.w(TAG, "Skipping unsupported emsg version: " + version);
@@ -717,7 +722,7 @@ public class FragmentedMp4Extractor implements Extractor {
*/
private static void parseTraf(ContainerAtom traf, SparseArray trackBundleArray,
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
- LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
+ LeafAtom tfhd = checkNotNull(traf.getLeafAtomOfType(Atom.TYPE_tfhd));
@Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray);
if (trackBundle == null) {
return;
@@ -730,7 +735,7 @@ public class FragmentedMp4Extractor implements Extractor {
trackBundle.currentlyInFragment = true;
@Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
- fragment.nextFragmentDecodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
+ fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data);
fragment.nextFragmentDecodeTimeIncludesMoov = true;
} else {
fragment.nextFragmentDecodeTime = fragmentDecodeTime;
@@ -742,11 +747,11 @@ public class FragmentedMp4Extractor implements Extractor {
@Nullable
TrackEncryptionBox encryptionBox =
trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox(
- fragment.header.sampleDescriptionIndex);
+ checkNotNull(fragment.header).sampleDescriptionIndex);
@Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
if (saiz != null) {
- parseSaiz(encryptionBox, saiz.data, fragment);
+ parseSaiz(checkNotNull(encryptionBox), saiz.data, fragment);
}
@Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
@@ -964,7 +969,7 @@ public class FragmentedMp4Extractor implements Extractor {
Track track = trackBundle.moovSampleTable.track;
TrackFragment fragment = trackBundle.fragment;
- DefaultSampleValues defaultSampleValues = fragment.header;
+ DefaultSampleValues defaultSampleValues = castNonNull(fragment.header);
fragment.trunLength[index] = trun.readUnsignedIntToInt();
fragment.trunDataPosition[index] = fragment.dataPosition;
@@ -994,7 +999,7 @@ public class FragmentedMp4Extractor implements Extractor {
&& track.editListDurations[0] == 0) {
edtsOffsetUs =
Util.scaleLargeTimestamp(
- track.editListMediaTimes[0], C.MICROS_PER_SECOND, track.timescale);
+ castNonNull(track.editListMediaTimes)[0], C.MICROS_PER_SECOND, track.timescale);
}
int[] sampleSizeTable = fragment.sampleSizeTable;
@@ -1161,7 +1166,7 @@ public class FragmentedMp4Extractor implements Extractor {
int perSampleIvSize = sgpd.readUnsignedByte();
byte[] keyId = new byte[16];
sgpd.readBytes(keyId, 0, keyId.length);
- byte[] constantIv = null;
+ @Nullable byte[] constantIv = null;
if (perSampleIvSize == 0) {
int constantIvSize = sgpd.readUnsignedByte();
constantIv = new byte[constantIvSize];
@@ -1238,7 +1243,7 @@ public class FragmentedMp4Extractor implements Extractor {
}
private void readEncryptionData(ExtractorInput input) throws IOException {
- TrackBundle nextTrackBundle = null;
+ @Nullable TrackBundle nextTrackBundle = null;
long nextDataOffset = Long.MAX_VALUE;
int trackBundlesSize = trackBundles.size();
for (int i = 0; i < trackBundlesSize; i++) {
@@ -1277,71 +1282,70 @@ public class FragmentedMp4Extractor implements Extractor {
* @throws IOException If an error occurs reading from the input.
*/
private boolean readSample(ExtractorInput input) throws IOException {
- if (parserState == STATE_READING_SAMPLE_START) {
- if (currentTrackBundle == null) {
- @Nullable TrackBundle currentTrackBundle = getNextTrackBundle(trackBundles);
- if (currentTrackBundle == null) {
- // We've run out of samples in the current mdat. Discard any trailing data and prepare to
- // read the header of the next atom.
- int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
- if (bytesToSkip < 0) {
- throw new ParserException("Offset to end of mdat was negative.");
- }
- input.skipFully(bytesToSkip);
- enterReadingAtomHeaderState();
- return false;
- }
-
- long nextDataPosition = currentTrackBundle.getCurrentSampleOffset();
- // We skip bytes preceding the next sample to read.
- int bytesToSkip = (int) (nextDataPosition - input.getPosition());
+ @Nullable TrackBundle trackBundle = currentTrackBundle;
+ if (trackBundle == null) {
+ trackBundle = getNextTrackBundle(trackBundles);
+ if (trackBundle == null) {
+ // We've run out of samples in the current mdat. Discard any trailing data and prepare to
+ // read the header of the next atom.
+ int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
if (bytesToSkip < 0) {
- // Assume the sample data must be contiguous in the mdat with no preceding data.
- Log.w(TAG, "Ignoring negative offset to sample data.");
- bytesToSkip = 0;
+ throw new ParserException("Offset to end of mdat was negative.");
}
input.skipFully(bytesToSkip);
- this.currentTrackBundle = currentTrackBundle;
+ enterReadingAtomHeaderState();
+ return false;
}
- sampleSize = currentTrackBundle.getCurrentSampleSize();
+ long nextDataPosition = trackBundle.getCurrentSampleOffset();
+ // We skip bytes preceding the next sample to read.
+ int bytesToSkip = (int) (nextDataPosition - input.getPosition());
+ if (bytesToSkip < 0) {
+ // Assume the sample data must be contiguous in the mdat with no preceding data.
+ Log.w(TAG, "Ignoring negative offset to sample data.");
+ bytesToSkip = 0;
+ }
+ input.skipFully(bytesToSkip);
+ currentTrackBundle = trackBundle;
+ }
+ if (parserState == STATE_READING_SAMPLE_START) {
+ sampleSize = trackBundle.getCurrentSampleSize();
- if (currentTrackBundle.currentSampleIndex < currentTrackBundle.firstSampleToOutputIndex) {
+ if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) {
input.skipFully(sampleSize);
- currentTrackBundle.skipSampleEncryptionData();
- if (!currentTrackBundle.next()) {
+ trackBundle.skipSampleEncryptionData();
+ if (!trackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
return true;
}
- if (currentTrackBundle.moovSampleTable.track.sampleTransformation
+ if (trackBundle.moovSampleTable.track.sampleTransformation
== Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
}
- if (MimeTypes.AUDIO_AC4.equals(
- currentTrackBundle.moovSampleTable.track.format.sampleMimeType)) {
+ if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
// AC4 samples need to be prefixed with a clear sample header.
sampleBytesWritten =
- currentTrackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
+ trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
- currentTrackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
+ trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
} else {
sampleBytesWritten =
- currentTrackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0);
+ trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0);
}
sampleSize += sampleBytesWritten;
parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0;
}
- Track track = currentTrackBundle.moovSampleTable.track;
- TrackOutput output = currentTrackBundle.output;
- long sampleTimeUs = currentTrackBundle.getCurrentSamplePresentationTimeUs();
+ Track track = trackBundle.moovSampleTable.track;
+ TrackOutput output = trackBundle.output;
+ long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs();
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
@@ -1407,11 +1411,11 @@ public class FragmentedMp4Extractor implements Extractor {
}
}
- @C.BufferFlags int sampleFlags = currentTrackBundle.getCurrentSampleFlags();
+ @C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags();
// Encryption data.
- TrackOutput.CryptoData cryptoData = null;
- TrackEncryptionBox encryptionBox = currentTrackBundle.getEncryptionBoxIfEncrypted();
+ @Nullable TrackOutput.CryptoData cryptoData = null;
+ @Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted();
if (encryptionBox != null) {
cryptoData = encryptionBox.cryptoData;
}
@@ -1420,7 +1424,7 @@ public class FragmentedMp4Extractor implements Extractor {
// After we have the sampleTimeUs, we can commit all the pending metadata samples
outputPendingMetadataSamples(sampleTimeUs);
- if (!currentTrackBundle.next()) {
+ if (!trackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
@@ -1452,7 +1456,7 @@ public class FragmentedMp4Extractor implements Extractor {
*/
@Nullable
private static TrackBundle getNextTrackBundle(SparseArray trackBundles) {
- TrackBundle nextTrackBundle = null;
+ @Nullable TrackBundle nextTrackBundle = null;
long nextSampleOffset = Long.MAX_VALUE;
int trackBundlesSize = trackBundles.size();
@@ -1579,6 +1583,8 @@ public class FragmentedMp4Extractor implements Extractor {
TrackSampleTable moovSampleTable,
DefaultSampleValues defaultSampleValues) {
this.output = output;
+ this.moovSampleTable = moovSampleTable;
+ this.defaultSampleValues = defaultSampleValues;
fragment = new TrackFragment();
scratch = new ParsableByteArray();
encryptionSignalByte = new ParsableByteArray(1);
@@ -1587,9 +1593,8 @@ public class FragmentedMp4Extractor implements Extractor {
}
public void reset(TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) {
- Assertions.checkNotNull(moovSampleTable.track);
this.moovSampleTable = moovSampleTable;
- this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues);
+ this.defaultSampleValues = defaultSampleValues;
output.format(moovSampleTable.track.format);
resetFragmentInfo();
}
@@ -1598,7 +1603,7 @@ public class FragmentedMp4Extractor implements Extractor {
@Nullable
TrackEncryptionBox encryptionBox =
moovSampleTable.track.getSampleDescriptionEncryptionBox(
- fragment.header.sampleDescriptionIndex);
+ castNonNull(fragment.header).sampleDescriptionIndex);
@Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null;
DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType);
Format format =
@@ -1706,7 +1711,7 @@ public class FragmentedMp4Extractor implements Extractor {
* @return The number of written bytes.
*/
public int outputSampleEncryptionData(int sampleSize, int clearHeaderSize) {
- TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
+ @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
if (encryptionBox == null) {
return 0;
}
@@ -1718,7 +1723,7 @@ public class FragmentedMp4Extractor implements Extractor {
vectorSize = encryptionBox.perSampleIvSize;
} else {
// The default initialization vector should be used.
- byte[] initVectorData = encryptionBox.defaultInitializationVector;
+ byte[] initVectorData = castNonNull(encryptionBox.defaultInitializationVector);
defaultInitializationVector.reset(initVectorData, initVectorData.length);
initializationVectorData = defaultInitializationVector;
vectorSize = initVectorData.length;
@@ -1815,7 +1820,7 @@ public class FragmentedMp4Extractor implements Extractor {
// Encryption is not supported yet for samples specified in the sample table.
return null;
}
- int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
+ int sampleDescriptionIndex = castNonNull(fragment.header).sampleDescriptionIndex;
@Nullable
TrackEncryptionBox encryptionBox =
fragment.trackEncryptionBox != null
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
index 02d7ebd9a0..b58f3da928 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
@@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem;
-import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.Extractor;
@@ -36,6 +35,7 @@ import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceDrmHelper;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.SequenceableLoader;
@@ -49,6 +49,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.MimeTypes;
@@ -93,12 +94,13 @@ public final class HlsMediaSource extends BaseMediaSource
public static final class Factory implements MediaSourceFactory {
private final HlsDataSourceFactory hlsDataSourceFactory;
+ private final MediaSourceDrmHelper mediaSourceDrmHelper;
private HlsExtractorFactory extractorFactory;
private HlsPlaylistParserFactory playlistParserFactory;
private HlsPlaylistTracker.Factory playlistTrackerFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private DrmSessionManager drmSessionManager;
+ @Nullable private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private boolean allowChunklessPreparation;
@MetadataType private int metadataType;
@@ -125,10 +127,10 @@ public final class HlsMediaSource extends BaseMediaSource
*/
public Factory(HlsDataSourceFactory hlsDataSourceFactory) {
this.hlsDataSourceFactory = checkNotNull(hlsDataSourceFactory);
+ mediaSourceDrmHelper = new MediaSourceDrmHelper();
playlistParserFactory = new DefaultHlsPlaylistParserFactory();
playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY;
extractorFactory = HlsExtractorFactory.DEFAULT;
- drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
metadataType = METADATA_TYPE_ID3;
@@ -285,19 +287,22 @@ public final class HlsMediaSource extends BaseMediaSource
return this;
}
- /**
- * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
- * default value is {@link DrmSessionManager#DUMMY}.
- *
- * @param drmSessionManager The {@link DrmSessionManager}.
- * @return This factory, for convenience.
- */
@Override
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
- this.drmSessionManager =
- drmSessionManager != null
- ? drmSessionManager
- : DrmSessionManager.getDummyDrmSessionManager();
+ this.drmSessionManager = drmSessionManager;
+ return this;
+ }
+
+ @Override
+ public Factory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
+ return this;
+ }
+
+ @Override
+ public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
+ mediaSourceDrmHelper.setDrmUserAgent(userAgent);
return this;
}
@@ -374,7 +379,7 @@ public final class HlsMediaSource extends BaseMediaSource
hlsDataSourceFactory,
extractorFactory,
compositeSequenceableLoaderFactory,
- drmSessionManager,
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
loadErrorHandlingPolicy,
playlistTrackerFactory.createTracker(
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
index 4a1f5c353c..a2ebb06936 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
@@ -27,7 +27,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.offline.FilteringManifestParser;
@@ -39,6 +38,7 @@ import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.MediaSourceDrmHelper;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaSourceFactory;
@@ -50,6 +50,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestP
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
import com.google.android.exoplayer2.upstream.Loader;
@@ -77,10 +78,11 @@ public final class SsMediaSource extends BaseMediaSource
public static final class Factory implements MediaSourceFactory {
private final SsChunkSource.Factory chunkSourceFactory;
+ private final MediaSourceDrmHelper mediaSourceDrmHelper;
@Nullable private final DataSource.Factory manifestDataSourceFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
- private DrmSessionManager drmSessionManager;
+ @Nullable private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long livePresentationDelayMs;
@Nullable private ParsingLoadable.Parser extends SsManifest> manifestParser;
@@ -111,7 +113,7 @@ public final class SsMediaSource extends BaseMediaSource
@Nullable DataSource.Factory manifestDataSourceFactory) {
this.chunkSourceFactory = checkNotNull(chunkSourceFactory);
this.manifestDataSourceFactory = manifestDataSourceFactory;
- drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
+ mediaSourceDrmHelper = new MediaSourceDrmHelper();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
@@ -197,19 +199,22 @@ public final class SsMediaSource extends BaseMediaSource
return this;
}
- /**
- * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
- * default value is {@link DrmSessionManager#DUMMY}.
- *
- * @param drmSessionManager The {@link DrmSessionManager}.
- * @return This factory, for convenience.
- */
@Override
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
- this.drmSessionManager =
- drmSessionManager != null
- ? drmSessionManager
- : DrmSessionManager.getDummyDrmSessionManager();
+ this.drmSessionManager = drmSessionManager;
+ return this;
+ }
+
+ @Override
+ public Factory setDrmHttpDataSourceFactory(
+ @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
+ mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
+ return this;
+ }
+
+ @Override
+ public Factory setDrmUserAgent(@Nullable String userAgent) {
+ mediaSourceDrmHelper.setDrmUserAgent(userAgent);
return this;
}
@@ -280,7 +285,7 @@ public final class SsMediaSource extends BaseMediaSource
/* manifestParser= */ null,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
- drmSessionManager,
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
loadErrorHandlingPolicy,
livePresentationDelayMs);
}
@@ -357,7 +362,7 @@ public final class SsMediaSource extends BaseMediaSource
manifestParser,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
- drmSessionManager,
+ drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
loadErrorHandlingPolicy,
livePresentationDelayMs);
}
diff --git a/library/ui/proguard-rules.txt b/library/ui/proguard-rules.txt
index 9bfde914b2..ad7c139ea8 100644
--- a/library/ui/proguard-rules.txt
+++ b/library/ui/proguard-rules.txt
@@ -3,7 +3,7 @@
# Constructor method accessed via reflection in TrackSelectionDialogBuilder
-dontnote androidx.appcompat.app.AlertDialog.Builder
-keepclassmembers class androidx.appcompat.app.AlertDialog$Builder {
- (android.content.Context);
+ (android.content.Context, int);
public android.content.Context getContext();
public androidx.appcompat.app.AlertDialog$Builder setTitle(java.lang.CharSequence);
public androidx.appcompat.app.AlertDialog$Builder setView(android.view.View);
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
index 62e9094cf0..fe802f9c0e 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java
@@ -1085,8 +1085,8 @@ public class PlayerControlView extends FrameLayout {
long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
- // Calculate the delay until the next update in real time, taking playbackSpeed into account.
- float playbackSpeed = player.getPlaybackSpeed();
+ // Calculate the delay until the next update in real time, taking playback speed into account.
+ float playbackSpeed = player.getPlaybackParameters().speed;
long delayMs =
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
index d0e7b0da9e..06a5341499 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
@@ -37,6 +37,7 @@ import androidx.media.app.NotificationCompat.MediaStyle;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
@@ -923,7 +924,7 @@ public class PlayerNotificationManager {
* The media is not {@link Player#isCurrentWindowDynamic() dynamically changing its
* duration} (like for example a live stream).
* The media is not {@link Player#isPlayingAd() interrupted by an ad}.
- * The media is played at {@link Player#getPlaybackSpeed() regular speed}.
+ * The media is played at {@link Player#getPlaybackParameters() regular speed}.
* The device is running at least API 21 (Lollipop).
*
*
@@ -1086,7 +1087,7 @@ public class PlayerNotificationManager {
&& player.isPlaying()
&& !player.isPlayingAd()
&& !player.isCurrentWindowDynamic()
- && player.getPlaybackSpeed() == 1f) {
+ && player.getPlaybackParameters().speed == 1f) {
builder
.setWhen(System.currentTimeMillis() - player.getContentPosition())
.setShowWhen(true)
@@ -1336,7 +1337,7 @@ public class PlayerNotificationManager {
}
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
postStartOrUpdateNotification();
}
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java
index ae523b4387..07106686ad 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java
@@ -42,6 +42,7 @@ import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RendererCapabilities;
@@ -1408,8 +1409,8 @@ public class StyledPlayerControlView extends FrameLayout {
long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
- // Calculate the delay until the next update in real time, taking playbackSpeed into account.
- float playbackSpeed = player.getPlaybackSpeed();
+ // Calculate the delay until the next update in real time, taking playback speed into account.
+ float playbackSpeed = player.getPlaybackParameters().speed;
long delayMs =
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
@@ -1425,7 +1426,7 @@ public class StyledPlayerControlView extends FrameLayout {
if (player == null) {
return;
}
- float speed = player.getPlaybackSpeed();
+ float speed = player.getPlaybackParameters().speed;
int currentSpeedMultBy100 = Math.round(speed * 100);
int indexForCurrentSpeed = playbackSpeedMultBy100List.indexOf(currentSpeedMultBy100);
if (indexForCurrentSpeed == UNDEFINED_POSITION) {
@@ -1481,7 +1482,7 @@ public class StyledPlayerControlView extends FrameLayout {
if (player == null) {
return;
}
- player.setPlaybackSpeed(speed);
+ player.setPlaybackParameters(new PlaybackParameters(speed));
}
/* package */ void requestPlayPauseFocus() {
@@ -1771,7 +1772,7 @@ public class StyledPlayerControlView extends FrameLayout {
}
@Override
- public void onPlaybackSpeedChanged(float playbackSpeed) {
+ public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
updateSettingsPlaybackSpeedLists();
}
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java
index ec8b3562a8..a230744511 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TrackSelectionDialogBuilder.java
@@ -25,6 +25,7 @@ import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
+import androidx.annotation.StyleRes;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
@@ -51,6 +52,7 @@ public final class TrackSelectionDialogBuilder {
}
private final Context context;
+ @StyleRes private int themeResId;
private final CharSequence title;
private final MappedTrackInfo mappedTrackInfo;
private final int rendererIndex;
@@ -124,6 +126,17 @@ public final class TrackSelectionDialogBuilder {
newOverrides.isEmpty() ? null : newOverrides.get(0)));
}
+ /**
+ * Sets the resource ID of the theme used to inflate this dialog.
+ *
+ * @param themeResId The resource ID of the theme.
+ * @return This builder, for convenience.
+ */
+ public TrackSelectionDialogBuilder setTheme(@StyleRes int themeResId) {
+ this.themeResId = themeResId;
+ return this;
+ }
+
/**
* Sets whether the selection is initially shown as disabled.
*
@@ -221,7 +234,7 @@ public final class TrackSelectionDialogBuilder {
}
private Dialog buildForPlatform() {
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId);
// Inflate with the builder's context to ensure the correct style is used.
LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
@@ -245,8 +258,8 @@ public final class TrackSelectionDialogBuilder {
// the APK size even with shrinking. See https://issuetracker.google.com/161514204.
// LINT.IfChange
Class> builderClazz = Class.forName("androidx.appcompat.app.AlertDialog$Builder");
- Constructor> builderConstructor = builderClazz.getConstructor(Context.class);
- Object builder = builderConstructor.newInstance(context);
+ Constructor> builderConstructor = builderClazz.getConstructor(Context.class, int.class);
+ Object builder = builderConstructor.newInstance(context, themeResId);
// Inflate with the builder's context to ensure the correct style is used.
Context builderContext = (Context) builderClazz.getMethod("getContext").invoke(builder);
diff --git a/library/ui/src/main/res/layout/exo_styled_player_control_view.xml b/library/ui/src/main/res/layout/exo_styled_player_control_view.xml
index 083ce48d06..0b0dd84b1f 100644
--- a/library/ui/src/main/res/layout/exo_styled_player_control_view.xml
+++ b/library/ui/src/main/res/layout/exo_styled_player_control_view.xml
@@ -134,6 +134,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end"
+ android:layout_marginBottom="@dimen/exo_custom_progress_thumb_size"
android:visibility="invisible">
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java
index d25836eee3..0f2c856dd7 100644
--- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/CommonEncryptionDrmTest.java
@@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.playbacktests.gts;
+import static com.google.android.exoplayer2.playbacktests.gts.GtsTestUtil.shouldSkipWidevineTest;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import com.google.android.exoplayer2.Player;
@@ -64,7 +66,7 @@ public final class CommonEncryptionDrmTest {
@Test
public void cencSchemeTypeV18() {
- if (Util.SDK_INT < 18) {
+ if (Util.SDK_INT < 18 || shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -74,23 +76,9 @@ public final class CommonEncryptionDrmTest {
.run();
}
- @Test
- public void cbc1SchemeTypeV25() {
- if (Util.SDK_INT < 25) {
- // cbc1 support was added in API 24, but it is stable from API 25 onwards.
- // See [internal: b/65634809].
- // Pass.
- return;
- }
- testRunner
- .setStreamName("test_widevine_h264_scheme_cbc1")
- .setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBC1)
- .run();
- }
-
@Test
public void cbcsSchemeTypeV25() {
- if (Util.SDK_INT < 25) {
+ if (Util.SDK_INT < 25 || shouldSkipWidevineTest(testRule.getActivity())) {
// cbcs support was added in API 24, but it is stable from API 25 onwards.
// See [internal: b/65634809].
// Pass.
@@ -101,9 +89,4 @@ public final class CommonEncryptionDrmTest {
.setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBCS)
.run();
}
-
- @Test
- public void censSchemeTypeV25() {
- // TODO: Implement once content is available. Track [internal: b/31219813].
- }
}
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java
index 259c2e61f6..a2f557ca0d 100644
--- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.playbacktests.gts;
+import static com.google.android.exoplayer2.playbacktests.gts.GtsTestUtil.shouldSkipWidevineTest;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -356,7 +357,7 @@ public final class DashStreamingTest {
@Test
public void widevineH264FixedV18() throws Exception {
- if (Util.SDK_INT < 18) {
+ if (Util.SDK_INT < 18 || shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -373,7 +374,9 @@ public final class DashStreamingTest {
@Test
public void widevineH264AdaptiveV18() throws Exception {
- if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
+ if (Util.SDK_INT < 18
+ || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)
+ || shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -390,7 +393,9 @@ public final class DashStreamingTest {
@Test
public void widevineH264AdaptiveWithSeekingV18() throws Exception {
- if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
+ if (Util.SDK_INT < 18
+ || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)
+ || shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -408,7 +413,9 @@ public final class DashStreamingTest {
@Test
public void widevineH264AdaptiveWithRendererDisablingV18() throws Exception {
- if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
+ if (Util.SDK_INT < 18
+ || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)
+ || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -428,7 +435,7 @@ public final class DashStreamingTest {
@Test
public void widevineH265FixedV23() throws Exception {
- if (Util.SDK_INT < 23) {
+ if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -445,7 +452,7 @@ public final class DashStreamingTest {
@Test
public void widevineH265AdaptiveV24() throws Exception {
- if (Util.SDK_INT < 24) {
+ if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -462,7 +469,7 @@ public final class DashStreamingTest {
@Test
public void widevineH265AdaptiveWithSeekingV24() throws Exception {
- if (Util.SDK_INT < 24) {
+ if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -480,7 +487,7 @@ public final class DashStreamingTest {
@Test
public void widevineH265AdaptiveWithRendererDisablingV24() throws Exception {
- if (Util.SDK_INT < 24) {
+ if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -500,7 +507,7 @@ public final class DashStreamingTest {
@Test
public void widevineVp9Fixed360pV23() throws Exception {
- if (Util.SDK_INT < 23) {
+ if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -517,7 +524,7 @@ public final class DashStreamingTest {
@Test
public void widevineVp9AdaptiveV24() throws Exception {
- if (Util.SDK_INT < 24) {
+ if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -534,7 +541,7 @@ public final class DashStreamingTest {
@Test
public void widevineVp9AdaptiveWithSeekingV24() throws Exception {
- if (Util.SDK_INT < 24) {
+ if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -552,7 +559,7 @@ public final class DashStreamingTest {
@Test
public void widevineVp9AdaptiveWithRendererDisablingV24() throws Exception {
- if (Util.SDK_INT < 24) {
+ if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -573,7 +580,7 @@ public final class DashStreamingTest {
// 23.976 fps.
@Test
public void widevine23FpsH264FixedV23() throws Exception {
- if (Util.SDK_INT < 23) {
+ if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -591,7 +598,7 @@ public final class DashStreamingTest {
// 24 fps.
@Test
public void widevine24FpsH264FixedV23() throws Exception {
- if (Util.SDK_INT < 23) {
+ if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
@@ -609,7 +616,7 @@ public final class DashStreamingTest {
// 29.97 fps.
@Test
public void widevine29FpsH264FixedV23() throws Exception {
- if (Util.SDK_INT < 23) {
+ if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
// Pass.
return;
}
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java
index 2033ef3096..c148010fdb 100644
--- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestData.java
@@ -45,8 +45,6 @@ import com.google.android.exoplayer2.util.Util;
// Widevine encrypted content manifests using different common encryption schemes.
public static final String WIDEVINE_SCHEME_CENC = BASE_URL_COMMON_ENCRYPTION + "tears-cenc.mpd";
- public static final String WIDEVINE_SCHEME_CBC1 =
- BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbc1.mpd";
public static final String WIDEVINE_SCHEME_CBCS =
BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbcs.mpd";
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
index 23b5cc7f17..81425c34ea 100644
--- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java
@@ -100,7 +100,7 @@ public final class DashWidevineOfflineTest {
@Test
public void widevineOfflineLicenseV22() throws Exception {
- if (Util.SDK_INT < 22) {
+ if (Util.SDK_INT < 22 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
return; // Pass.
}
downloadLicense();
@@ -113,7 +113,9 @@ public final class DashWidevineOfflineTest {
@Test
public void widevineOfflineReleasedLicenseV22() throws Throwable {
- if (Util.SDK_INT < 22 || Util.SDK_INT > 28) {
+ if (Util.SDK_INT < 22
+ || Util.SDK_INT > 28
+ || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
return; // Pass.
}
downloadLicense();
@@ -136,7 +138,7 @@ public final class DashWidevineOfflineTest {
@Test
public void widevineOfflineReleasedLicenseV29() throws Throwable {
- if (Util.SDK_INT < 29) {
+ if (Util.SDK_INT < 29 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
return; // Pass.
}
downloadLicense();
@@ -158,7 +160,7 @@ public final class DashWidevineOfflineTest {
@Test
public void widevineOfflineExpiredLicenseV22() throws Exception {
- if (Util.SDK_INT < 22) {
+ if (Util.SDK_INT < 22 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
return; // Pass.
}
downloadLicense();
@@ -188,7 +190,7 @@ public final class DashWidevineOfflineTest {
@Test
public void widevineOfflineLicenseExpiresOnPauseV22() throws Exception {
- if (Util.SDK_INT < 22) {
+ if (Util.SDK_INT < 22 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
return; // Pass.
}
downloadLicense();
@@ -198,7 +200,7 @@ public final class DashWidevineOfflineTest {
offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
long licenseDuration = licenseDurationRemainingSec.first;
assertWithMessage(
- "License duration should be less than 30 sec. " + "Server settings might have changed.")
+ "License duration should be less than 30 sec. Server settings might have changed.")
.that(licenseDuration < 30)
.isTrue();
ActionSchedule schedule = new ActionSchedule.Builder(TAG)
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
index 36c58d46b2..71270d21c5 100644
--- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
@@ -24,6 +24,7 @@ import android.media.MediaFormat;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
@@ -84,13 +85,14 @@ import java.util.ArrayList;
private final long[] timestampsList;
private final ArrayDeque inputFormatChangeTimesUs;
- private final boolean enableMediaFormatChangeTimeCheck;
+
+ private boolean skipToPositionBeforeRenderingFirstFrame;
+ private boolean shouldMediaFormatChangeTimesBeChecked;
private int startIndex;
private int queueSize;
private int bufferCount;
private int minimumInsertIndex;
- private boolean skipToPositionBeforeRenderingFirstFrame;
private boolean inputFormatChanged;
private boolean outputMediaFormatChanged;
@@ -112,10 +114,6 @@ import java.util.ArrayList;
maxDroppedFrameCountToNotify);
timestampsList = new long[ARRAY_SIZE];
inputFormatChangeTimesUs = new ArrayDeque<>();
-
- // As per [Internal ref: b/149818050, b/149751672], MediaFormat changes can occur early for
- // SDK 29 and 30. Should be fixed for SDK 31 onwards.
- enableMediaFormatChangeTimeCheck = Util.SDK_INT < 29 || Util.SDK_INT >= 31;
}
@Override
@@ -137,6 +135,10 @@ import java.util.ArrayList;
// frames up to the current playback position [Internal: b/66494991].
skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED;
super.configureCodec(codecInfo, codecAdapter, format, crypto, operatingRate);
+
+ // Output MediaFormat changes are known to occur too early until API 30 (see [internal:
+ // b/149818050, b/149751672]).
+ shouldMediaFormatChangeTimesBeChecked = Util.SDK_INT > 30;
}
@Override
@@ -247,10 +249,12 @@ import java.util.ArrayList;
}
if (outputMediaFormatChanged) {
- long inputFormatChangeTimeUs = inputFormatChangeTimesUs.remove();
+ long inputFormatChangeTimeUs =
+ inputFormatChangeTimesUs.isEmpty() ? C.TIME_UNSET : inputFormatChangeTimesUs.remove();
outputMediaFormatChanged = false;
- if (enableMediaFormatChangeTimeCheck && presentationTimeUs != inputFormatChangeTimeUs) {
+ if (shouldMediaFormatChangeTimesBeChecked
+ && presentationTimeUs != inputFormatChangeTimeUs) {
throw new IllegalStateException(
"Expected output MediaFormat change timestamp ("
+ presentationTimeUs
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/GtsTestUtil.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/GtsTestUtil.java
new file mode 100644
index 0000000000..6223539056
--- /dev/null
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/GtsTestUtil.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.exoplayer2.playbacktests.gts;
+
+import static com.google.android.exoplayer2.C.WIDEVINE_UUID;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.MediaDrm;
+
+/** Utility methods for GTS tests. */
+public final class GtsTestUtil {
+
+ private GtsTestUtil() {}
+
+ /** Returns true if the device doesn't support Widevine and this is permitted. */
+ public static boolean shouldSkipWidevineTest(Context context) {
+ if (isGmsInstalled(context)) {
+ // GMS devices are required to support Widevine.
+ return false;
+ }
+ // For non-GMS devices Widevine is optional.
+ return !MediaDrm.isCryptoSchemeSupported(WIDEVINE_UUID);
+ }
+
+ private static boolean isGmsInstalled(Context context) {
+ try {
+ context
+ .getPackageManager()
+ .getPackageInfo("com.google.android.gms", PackageManager.GET_SIGNATURES);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/testdata/src/test/assets/playbackdumps/mp4/sample.mp4.dump b/testdata/src/test/assets/playbackdumps/mp4/sample.mp4.dump
new file mode 100644
index 0000000000..5256ea561e
--- /dev/null
+++ b/testdata/src/test/assets/playbackdumps/mp4/sample.mp4.dump
@@ -0,0 +1,81 @@
+MediaCodec (audio/mp4a-latm):
+ buffers.length = 46
+ buffers[0] = length 23, hash 47DE9131
+ buffers[1] = length 6, hash 31EC5206
+ buffers[2] = length 148, hash 894A176B
+ buffers[3] = length 189, hash CEF235A1
+ buffers[4] = length 205, hash BBF5F7B0
+ buffers[5] = length 210, hash F278B193
+ buffers[6] = length 210, hash 82DA1589
+ buffers[7] = length 207, hash 5BE231DF
+ buffers[8] = length 225, hash 18819EE1
+ buffers[9] = length 215, hash CA7FA67B
+ buffers[10] = length 211, hash 581A1C18
+ buffers[11] = length 216, hash ADB88187
+ buffers[12] = length 229, hash 2E8BA4DC
+ buffers[13] = length 232, hash 22F0C510
+ buffers[14] = length 235, hash 867AD0DC
+ buffers[15] = length 231, hash 84E823A8
+ buffers[16] = length 226, hash 1BEF3A95
+ buffers[17] = length 216, hash EAA345AE
+ buffers[18] = length 229, hash 6957411F
+ buffers[19] = length 219, hash 41275022
+ buffers[20] = length 241, hash 6495DF96
+ buffers[21] = length 228, hash 63D95906
+ buffers[22] = length 238, hash 34F676F9
+ buffers[23] = length 234, hash E5CBC045
+ buffers[24] = length 231, hash 5FC43661
+ buffers[25] = length 217, hash 682708ED
+ buffers[26] = length 239, hash D43780FC
+ buffers[27] = length 243, hash C5E17980
+ buffers[28] = length 231, hash AC5837BA
+ buffers[29] = length 230, hash 169EE895
+ buffers[30] = length 238, hash C48FF3F1
+ buffers[31] = length 225, hash 531E4599
+ buffers[32] = length 232, hash CB3C6B8D
+ buffers[33] = length 243, hash F8C94C7
+ buffers[34] = length 232, hash A646A7D0
+ buffers[35] = length 237, hash E8B787A5
+ buffers[36] = length 228, hash 3FA7A29F
+ buffers[37] = length 235, hash B9B33B0A
+ buffers[38] = length 264, hash 71A4869E
+ buffers[39] = length 257, hash D049B54C
+ buffers[40] = length 227, hash 66757231
+ buffers[41] = length 227, hash BD374F1B
+ buffers[42] = length 235, hash 999477F6
+ buffers[43] = length 229, hash FFF98DF0
+ buffers[44] = length 6, hash 31B22286
+ buffers[45] = length 0, hash 1
+MediaCodec (video/avc):
+ buffers.length = 31
+ buffers[0] = length 36692, hash D216076E
+ buffers[1] = length 5312, hash D45D3CA0
+ buffers[2] = length 599, hash 1BE7812D
+ buffers[3] = length 7735, hash 4490F110
+ buffers[4] = length 987, hash 560B5036
+ buffers[5] = length 673, hash ED7CD8C7
+ buffers[6] = length 523, hash 3020DF50
+ buffers[7] = length 6061, hash 736C72B2
+ buffers[8] = length 992, hash FE132F23
+ buffers[9] = length 623, hash 5B2C1816
+ buffers[10] = length 421, hash 742E69C1
+ buffers[11] = length 4899, hash F72F86A1
+ buffers[12] = length 568, hash 519A8E50
+ buffers[13] = length 620, hash 3990AA39
+ buffers[14] = length 5450, hash F06EC4AA
+ buffers[15] = length 1051, hash 92DFA63A
+ buffers[16] = length 874, hash 69587FB4
+ buffers[17] = length 781, hash 36BE495B
+ buffers[18] = length 4725, hash AC0C8CD3
+ buffers[19] = length 1022, hash 5D8BFF34
+ buffers[20] = length 790, hash 99413A99
+ buffers[21] = length 610, hash 5E129290
+ buffers[22] = length 2751, hash 769974CB
+ buffers[23] = length 745, hash B78A477A
+ buffers[24] = length 621, hash CF741E7A
+ buffers[25] = length 505, hash 1DB4894E
+ buffers[26] = length 1268, hash C15348DC
+ buffers[27] = length 880, hash C2DE85D0
+ buffers[28] = length 530, hash C98BC6A8
+ buffers[29] = length 568, hash 4FE5C8EA
+ buffers[30] = length 0, hash 1
diff --git a/testdata/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump b/testdata/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump
new file mode 100644
index 0000000000..9e850d0f14
--- /dev/null
+++ b/testdata/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump
@@ -0,0 +1,19 @@
+MediaCodec (audio/mpeg-L2):
+ buffers.length = 5
+ buffers[0] = length 1253, hash 727FD1C6
+ buffers[1] = length 1254, hash 73FB07B8
+ buffers[2] = length 1254, hash 73FB07B8
+ buffers[3] = length 1254, hash 73FB07B8
+ buffers[4] = length 0, hash 1
+MediaCodec (video/mpeg2):
+ buffers.length = 3
+ buffers[0] = length 20711, hash 34341E8
+ buffers[1] = length 18112, hash EC44B35B
+ buffers[2] = length 0, hash 1
+MetadataOutput:
+ Metadata[0]:
+ entry[0] = SpliceInsertCommand
+ Metadata[1]:
+ entry[0] = SpliceInsertCommand
+ Metadata[2]:
+ entry[0] = SpliceInsertCommand
diff --git a/testutils/build.gradle b/testutils/build.gradle
index 93b3acf53f..8cd443e07f 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -24,6 +24,7 @@ dependencies {
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation project(modulePrefix + 'library-core')
+ implementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
index 5b8d501d00..ca514432f2 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java
@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.IllegalSeekPositionException;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.PlayerMessage.Target;
@@ -608,26 +609,26 @@ public abstract class Action {
}
}
- /** Calls {@link Player#setPlaybackSpeed(float)}. */
- public static final class SetPlaybackSpeed extends Action {
+ /** Calls {@link Player#setPlaybackParameters(PlaybackParameters)}. */
+ public static final class SetPlaybackParameters extends Action {
- private final float playbackSpeed;
+ private final PlaybackParameters playbackParameters;
/**
- * Creates a set playback speed action instance.
+ * Creates a set playback parameters action instance.
*
* @param tag A tag to use for logging.
- * @param playbackSpeed The playback speed.
+ * @param playbackParameters The playback parameters.
*/
- public SetPlaybackSpeed(String tag, float playbackSpeed) {
- super(tag, "SetPlaybackSpeed:" + playbackSpeed);
- this.playbackSpeed = playbackSpeed;
+ public SetPlaybackParameters(String tag, PlaybackParameters playbackParameters) {
+ super(tag, "SetPlaybackParameters:" + playbackParameters);
+ this.playbackParameters = playbackParameters;
}
@Override
protected void doActionImpl(
SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
- player.setPlaybackSpeed(playbackSpeed);
+ player.setPlaybackParameters(playbackParameters);
}
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java
index 8051e997b3..fa672b844a 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java
@@ -20,6 +20,7 @@ import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.PlayerMessage.Target;
@@ -35,7 +36,7 @@ import com.google.android.exoplayer2.testutil.Action.Seek;
import com.google.android.exoplayer2.testutil.Action.SendMessages;
import com.google.android.exoplayer2.testutil.Action.SetAudioAttributes;
import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady;
-import com.google.android.exoplayer2.testutil.Action.SetPlaybackSpeed;
+import com.google.android.exoplayer2.testutil.Action.SetPlaybackParameters;
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
import com.google.android.exoplayer2.testutil.Action.SetRepeatMode;
import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled;
@@ -214,14 +215,14 @@ public final class ActionSchedule {
}
/**
- * Schedules a playback speed setting action.
+ * Schedules a playback parameters setting action.
*
- * @param playbackSpeed The playback speed to set.
+ * @param playbackParameters The playback parameters to set.
* @return The builder, for convenience.
- * @see Player#setPlaybackSpeed(float)
+ * @see Player#setPlaybackParameters(PlaybackParameters)
*/
- public Builder setPlaybackSpeed(float playbackSpeed) {
- return apply(new SetPlaybackSpeed(tag, playbackSpeed));
+ public Builder setPlaybackParameters(PlaybackParameters playbackParameters) {
+ return apply(new SetPlaybackParameters(tag, playbackParameters));
}
/**
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java
index 7444d35e8e..e66a30935e 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java
@@ -52,7 +52,7 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest {
DefaultAudioSink.failOnSpuriousAudioTimestamp = true;
}
- public static final long MAX_PLAYING_TIME_DISCREPANCY_MS = 2000;
+ public static final long MAX_PLAYING_TIME_DISCREPANCY_MS = 5000;
public static final long EXPECTED_PLAYING_TIME_MEDIA_DURATION_MS = -2;
public static final long EXPECTED_PLAYING_TIME_UNSET = -1;
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java
index d3eec0b85b..4a3b9e923e 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java
@@ -31,6 +31,7 @@ import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
@@ -48,7 +49,7 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
@Nullable private final TransferListener transferListener;
private final long durationUs;
- @MonotonicNonNull private Callback callback;
+ private @MonotonicNonNull Callback callback;
private ChunkSampleStream[] sampleStreams;
private SequenceableLoader sequenceableLoader;
@@ -99,7 +100,7 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
}
}
sampleStreams = newSampleStreamArray(validStreams.size());
- validStreams.toArray(sampleStreams);
+ Util.nullSafeListToArray(validStreams, sampleStreams);
this.sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
return returnPositionUs;
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
index 5ed4e5bf5f..1e2f6159a5 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
@@ -30,6 +30,7 @@ public class FakeAudioRenderer extends FakeRenderer {
private final AudioRendererEventListener.EventDispatcher eventDispatcher;
private final DecoderCounters decoderCounters;
private boolean notifiedAudioSessionId;
+ private boolean notifiedPositionAdvancing;
public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
super(C.TRACK_TYPE_AUDIO);
@@ -43,6 +44,7 @@ public class FakeAudioRenderer extends FakeRenderer {
super.onEnabled(joining, mayRenderStartOfStream);
eventDispatcher.enabled(decoderCounters);
notifiedAudioSessionId = false;
+ notifiedPositionAdvancing = false;
}
@Override
@@ -67,6 +69,10 @@ public class FakeAudioRenderer extends FakeRenderer {
eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
notifiedAudioSessionId = true;
}
+ if (shouldProcess && !notifiedPositionAdvancing) {
+ eventDispatcher.positionAdvancing(System.currentTimeMillis());
+ notifiedPositionAdvancing = true;
+ }
return shouldProcess;
}
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java
index a9ca00ac64..5f858bea99 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java
@@ -217,8 +217,7 @@ public class FakeDataSource extends BaseDataSource {
* this method.
*/
public final DataSpec[] getAndClearOpenedDataSpecs() {
- DataSpec[] dataSpecs = new DataSpec[openedDataSpecs.size()];
- openedDataSpecs.toArray(dataSpecs);
+ DataSpec[] dataSpecs = openedDataSpecs.toArray(new DataSpec[0]);
openedDataSpecs.clear();
return dataSpecs;
}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java
index 824d3c02e3..7d63e129db 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeSampleStream.java
@@ -30,7 +30,6 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.Iterables;
import java.io.IOException;
@@ -274,10 +273,7 @@ public class FakeSampleStream implements SampleStream {
@Nullable DrmSession previousSession = currentDrmSession;
Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
currentDrmSession =
- newDrmInitData != null
- ? drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat)
- : drmSessionManager.acquirePlaceholderSession(
- playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
+ drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat);
outputFormatHolder.drmSession = currentDrmSession;
if (previousSession != null) {
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/PlaybackOutput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/PlaybackOutput.java
new file mode 100644
index 0000000000..69429709a4
--- /dev/null
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/PlaybackOutput.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.testutil;
+
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class to capture output from a playback test.
+ *
+ * Implements {@link Dumper.Dumpable} so the output can be easily dumped to a string for
+ * comparison against previous test runs.
+ */
+public final class PlaybackOutput implements Dumper.Dumpable {
+
+ private final ShadowMediaCodecConfig codecConfig;
+
+ // TODO: Add support for subtitles too
+ private final List metadatas;
+
+ private PlaybackOutput(SimpleExoPlayer player, ShadowMediaCodecConfig codecConfig) {
+ this.codecConfig = codecConfig;
+
+ metadatas = Collections.synchronizedList(new ArrayList<>());
+ // TODO: Consider passing playback position into MetadataOutput and TextOutput. Calling
+ // player.getCurrentPosition() inside onMetadata/Cues will likely be non-deterministic
+ // because renderer-thread != playback-thread.
+ player.addMetadataOutput(metadatas::add);
+ }
+
+ /**
+ * Create an instance that captures the metadata and text output from {@code player} and the audio
+ * and video output via the {@link TeeCodec TeeCodecs} exposed by {@code mediaCodecConfig}.
+ *
+ * Must be called before playback to ensure metadata and text output is captured
+ * correctly.
+ *
+ * @param player The {@link SimpleExoPlayer} to capture metadata and text output from.
+ * @param mediaCodecConfig The {@link ShadowMediaCodecConfig} to capture audio and video output
+ * from.
+ * @return A new instance that can be used to dump the playback output.
+ */
+ public static PlaybackOutput register(
+ SimpleExoPlayer player, ShadowMediaCodecConfig mediaCodecConfig) {
+ return new PlaybackOutput(player, mediaCodecConfig);
+ }
+
+ @Override
+ public void dump(Dumper dumper) {
+ ImmutableMap codecs = codecConfig.getCodecs();
+ ImmutableList mimeTypes = ImmutableList.sortedCopyOf(codecs.keySet());
+ for (String mimeType : mimeTypes) {
+ dumper.add(Assertions.checkNotNull(codecs.get(mimeType)));
+ }
+
+ dumpMetadata(dumper);
+ }
+
+ private void dumpMetadata(Dumper dumper) {
+ if (metadatas.isEmpty()) {
+ return;
+ }
+ dumper.startBlock("MetadataOutput");
+ for (int i = 0; i < metadatas.size(); i++) {
+ dumper.startBlock("Metadata[" + i + "]");
+ Metadata metadata = metadatas.get(i);
+ for (int j = 0; j < metadata.length(); j++) {
+ dumper.add("entry[" + j + "]", metadata.get(j).getClass().getSimpleName());
+ }
+ dumper.endBlock();
+ }
+ dumper.endBlock();
+ }
+}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ShadowMediaCodecConfig.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ShadowMediaCodecConfig.java
new file mode 100644
index 0000000000..d1b4e784b8
--- /dev/null
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ShadowMediaCodecConfig.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.testutil;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.primitives.Ints;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.rules.ExternalResource;
+import org.robolectric.shadows.MediaCodecInfoBuilder;
+import org.robolectric.shadows.ShadowMediaCodec;
+import org.robolectric.shadows.ShadowMediaCodecList;
+
+/**
+ * A JUnit @Rule to configure Roboelectric's {@link ShadowMediaCodec}.
+ *
+ * Registers a {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} for each audio/video
+ * MIME type known by ExoPlayer, and provides access to the bytes passed to these via {@link
+ * TeeCodec}.
+ */
+public final class ShadowMediaCodecConfig extends ExternalResource {
+
+ private final Map codecsByMimeType;
+
+ private ShadowMediaCodecConfig() {
+ this.codecsByMimeType = new HashMap<>();
+ }
+
+ public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
+ return new ShadowMediaCodecConfig();
+ }
+
+ public ImmutableMap getCodecs() {
+ return ImmutableMap.copyOf(codecsByMimeType);
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ // Video codecs
+ MediaCodecInfo.CodecProfileLevel avcProfileLevel =
+ createProfileLevel(
+ MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
+ MediaCodecInfo.CodecProfileLevel.AVCLevel62);
+ configureCodec(
+ /* codecName= */ "exotest.video.avc",
+ MimeTypes.VIDEO_H264,
+ ImmutableList.of(avcProfileLevel),
+ ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
+ MediaCodecInfo.CodecProfileLevel mpeg2ProfileLevel =
+ createProfileLevel(
+ MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain,
+ MediaCodecInfo.CodecProfileLevel.MPEG2LevelML);
+ configureCodec(
+ /* codecName= */ "exotest.video.mpeg2",
+ MimeTypes.VIDEO_MPEG2,
+ ImmutableList.of(mpeg2ProfileLevel),
+ ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
+
+ // Audio codecs
+ configureCodec("exotest.audio.aac", MimeTypes.AUDIO_AAC);
+ configureCodec("exotest.audio.mpegl2", MimeTypes.AUDIO_MPEG_L2);
+ }
+
+ @Override
+ protected void after() {
+ codecsByMimeType.clear();
+ ShadowMediaCodecList.reset();
+ ShadowMediaCodec.clearCodecs();
+ }
+
+ private void configureCodec(String codecName, String mimeType) {
+ configureCodec(
+ codecName,
+ mimeType,
+ /* profileLevels= */ ImmutableList.of(),
+ /* colorFormats= */ ImmutableList.of());
+ }
+
+ private void configureCodec(
+ String codecName,
+ String mimeType,
+ List profileLevels,
+ List colorFormats) {
+ MediaFormat mediaFormat = new MediaFormat();
+ mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
+ MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities =
+ MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat);
+ if (!profileLevels.isEmpty()) {
+ capabilities.setProfileLevels(profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0]));
+ }
+ if (!colorFormats.isEmpty()) {
+ capabilities.setColorFormats(Ints.toArray(colorFormats));
+ }
+ ShadowMediaCodecList.addCodec(
+ MediaCodecInfoBuilder.newBuilder()
+ .setName(codecName)
+ .setCapabilities(capabilities.build())
+ .build());
+ // TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed
+ // to configure() so we don't have to specify large buffers here.
+ TeeCodec codec = new TeeCodec(mimeType);
+ ShadowMediaCodec.addDecoder(
+ codecName,
+ new ShadowMediaCodec.CodecConfig(
+ /* inputBufferSize= */ 50_000, /* outputBufferSize= */ 50_000, codec));
+ codecsByMimeType.put(mimeType, codec);
+ }
+
+ private static MediaCodecInfo.CodecProfileLevel createProfileLevel(int profile, int level) {
+ MediaCodecInfo.CodecProfileLevel profileLevel = new MediaCodecInfo.CodecProfileLevel();
+ profileLevel.profile = profile;
+ profileLevel.level = level;
+ return profileLevel;
+ }
+}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
index 250dd01c0f..7a96e1c797 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
@@ -314,32 +314,16 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
throw new UnsupportedOperationException();
}
- /** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
- @SuppressWarnings("deprecation")
- @Deprecated
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
throw new UnsupportedOperationException();
}
- /** @deprecated Use {@link #getPlaybackSpeed()} instead. */
- @SuppressWarnings("deprecation")
- @Deprecated
@Override
public PlaybackParameters getPlaybackParameters() {
throw new UnsupportedOperationException();
}
- @Override
- public void setPlaybackSpeed(float playbackSpeed) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public float getPlaybackSpeed() {
- throw new UnsupportedOperationException();
- }
-
@Override
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
throw new UnsupportedOperationException();
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TeeCodec.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TeeCodec.java
new file mode 100644
index 0000000000..fd9b374d46
--- /dev/null
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TeeCodec.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.testutil;
+
+import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.common.collect.ImmutableList;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.robolectric.shadows.ShadowMediaCodec;
+
+/**
+ * A {@link ShadowMediaCodec.CodecConfig.Codec} for Robolectric's {@link ShadowMediaCodec} that
+ * records the contents of buffers passed to it before copying the contents into the output buffer.
+ *
+ * This also implements {@link Dumper.Dumpable} so the recorded buffers can be written out to a
+ * dump file.
+ */
+public final class TeeCodec implements ShadowMediaCodec.CodecConfig.Codec, Dumper.Dumpable {
+
+ private final String mimeType;
+ private final List receivedBuffers;
+
+ public TeeCodec(String mimeType) {
+ this.mimeType = mimeType;
+ this.receivedBuffers = Collections.synchronizedList(new ArrayList<>());
+ }
+
+ @Override
+ public void process(ByteBuffer in, ByteBuffer out) {
+ byte[] bytes = new byte[in.remaining()];
+ in.get(bytes);
+ receivedBuffers.add(bytes);
+
+ if (!MimeTypes.isAudio(mimeType)) {
+ // Don't output audio bytes, because ShadowAudioTrack doesn't advance the playback position so
+ // playback never completes.
+ // TODO: Update ShadowAudioTrack to advance the playback position in a realistic way.
+ out.put(bytes);
+ }
+ }
+
+ @Override
+ public void dump(Dumper dumper) {
+ if (receivedBuffers.isEmpty()) {
+ return;
+ }
+ dumper.startBlock("MediaCodec (" + mimeType + ")");
+ dumper.add("buffers.length", receivedBuffers.size());
+ for (int i = 0; i < receivedBuffers.size(); i++) {
+ dumper.add("buffers[" + i + "]", receivedBuffers.get(i));
+ }
+
+ dumper.endBlock();
+ }
+
+ /**
+ * Return the buffers received by this codec.
+ *
+ * The list is sorted in the order the buffers were passed to {@link #process(ByteBuffer,
+ * ByteBuffer)}.
+ */
+ public ImmutableList getReceivedBuffers() {
+ return ImmutableList.copyOf(receivedBuffers);
+ }
+}