Skip to content

Commit

Permalink
Merge pull request #5986 from google/dev-v2-r2.10.2
Browse files Browse the repository at this point in the history
r2.10.2
  • Loading branch information
ojw28 authored Jun 23, 2019
2 parents 7e40708 + b9c8861 commit f6297f4
Show file tree
Hide file tree
Showing 65 changed files with 1,427 additions and 487 deletions.
50 changes: 50 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
# Release notes #

### 2.10.2 ###

* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
([#5779](https://github.com/google/ExoPlayer/issues/5779)).
* Add `SilenceMediaSource` that can be used to play silence of a given
duration ([#5735](https://github.com/google/ExoPlayer/issues/5735)).
* Offline:
* Prevent unexpected `DownloadHelper.Callback.onPrepared` callbacks after
preparation of a `DownloadHelper` fails
([#5915](https://github.com/google/ExoPlayer/issues/5915)).
* Fix `CacheUtil.cache()` downloading too much data
([#5927](https://github.com/google/ExoPlayer/issues/5927)).
* Fix misreporting cached bytes when caching is paused
([#5573](https://github.com/google/ExoPlayer/issues/5573)).
* UI:
* Allow setting `DefaultTimeBar` attributes on `PlayerView` and
`PlayerControlView`.
* Change playback controls toggle from touch down to touch up events
([#5784](https://github.com/google/ExoPlayer/issues/5784)).
* Fix issue where playback controls were not kept visible on key presses
([#5963](https://github.com/google/ExoPlayer/issues/5963)).
* Subtitles:
* CEA-608: Handle XDS and TEXT modes
([#5807](https://github.com/google/ExoPlayer/pull/5807)).
* TTML: Fix bitmap rendering
([#5633](https://github.com/google/ExoPlayer/pull/5633)).
* IMA: Fix ad pod index offset calculation without preroll
([#5928](https://github.com/google/ExoPlayer/issues/5928)).
* Add a `playWhenReady` flag to MediaSessionConnector.PlaybackPreparer methods
to indicate whether a controller sent a play or only a prepare command. This
allows to take advantage of decoder reuse with the MediaSessionConnector
([#5891](https://github.com/google/ExoPlayer/issues/5891)).
* Add `ProgressUpdateListener` to `PlayerControlView`
([#5834](https://github.com/google/ExoPlayer/issues/5834)).
* Add support for auto-detecting UDP streams in `DefaultDataSource`
([#6036](https://github.com/google/ExoPlayer/pull/6036)).
* Allow enabling decoder fallback with `DefaultRenderersFactory`
([#5942](https://github.com/google/ExoPlayer/issues/5942)).
* Gracefully handle revoked `ACCESS_NETWORK_STATE` permission
([#6019](https://github.com/google/ExoPlayer/issues/6019)).
* Fix decoding problems when seeking back after seeking beyond a mid-roll ad
([#6009](https://github.com/google/ExoPlayer/issues/6009)).
* Fix application of `maxAudioBitrate` for adaptive audio track groups
([#6006](https://github.com/google/ExoPlayer/issues/6006)).
* Fix bug caused by parallel adaptive track selection using `Format`s without
bitrate information
([#5971](https://github.com/google/ExoPlayer/issues/5971)).
* Fix bug in `CastPlayer.getCurrentWindowIndex()`
([#5955](https://github.com/google/ExoPlayer/issues/5955)).

### 2.10.1 ###

* Offline: Add option to remove all downloads.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ allprojects {
jcenter()
}
project.ext {
exoplayerPublishEnabled = true
exoplayerPublishEnabled = false
}
if (it.hasProperty('externalBuildDir')) {
if (!new File(externalBuildDir).isAbsolute()) {
Expand Down
4 changes: 2 additions & 2 deletions constants.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.10.1'
releaseVersionCode = 2010001
releaseVersion = '2.10.2'
releaseVersionCode = 2010002
minSdkVersion = 16
targetSdkVersion = 28
compileSdkVersion = 28
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
private final Listener listener;
private final ConcatenatingMediaSource concatenatingMediaSource;

private boolean castMediaQueueCreationPending;
private int currentItemIndex;
private Player currentPlayer;

Expand Down Expand Up @@ -268,9 +267,6 @@ public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
public void onTimelineChanged(
Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
updateCurrentItemIndex();
if (currentPlayer == castPlayer && timeline.isEmpty()) {
castMediaQueueCreationPending = true;
}
}

// CastPlayer.SessionAvailabilityListener implementation.
Expand Down Expand Up @@ -332,7 +328,6 @@ private void setCurrentPlayer(Player currentPlayer) {
this.currentPlayer = currentPlayer;

// Media queue management.
castMediaQueueCreationPending = currentPlayer == castPlayer;
if (currentPlayer == exoPlayer) {
exoPlayer.prepare(concatenatingMediaSource);
}
Expand All @@ -352,12 +347,11 @@ private void setCurrentPlayer(Player currentPlayer) {
*/
private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
maybeSetCurrentItemAndNotify(itemIndex);
if (castMediaQueueCreationPending) {
if (currentPlayer == castPlayer && castPlayer.getCurrentTimeline().isEmpty()) {
MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()];
for (int i = 0; i < items.length; i++) {
items[i] = buildMediaQueueItem(mediaQueue.get(i));
}
castMediaQueueCreationPending = false;
castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF);
} else {
currentPlayer.seekTo(itemIndex, positionMs);
Expand Down
2 changes: 1 addition & 1 deletion extensions/cast/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ android {
}

dependencies {
api 'com.google.android.gms:play-services-cast-framework:16.1.2'
api 'com.google.android.gms:play-services-cast-framework:16.2.0'
implementation 'androidx.annotation:annotation:1.0.2'
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* {@link Player} implementation that communicates with a Cast receiver app.
Expand Down Expand Up @@ -86,8 +89,10 @@ public final class CastPlayer extends BasePlayer {
private final StatusListener statusListener;
private final SeekResultCallback seekResultCallback;

// Listeners.
private final CopyOnWriteArraySet<EventListener> listeners;
// Listeners and notification.
private final CopyOnWriteArrayList<ListenerHolder> listeners;
private final ArrayList<ListenerNotificationTask> notificationsBatch;
private final ArrayDeque<ListenerNotificationTask> ongoingNotificationsTasks;
private SessionAvailabilityListener sessionAvailabilityListener;

// Internal state.
Expand All @@ -113,7 +118,9 @@ public CastPlayer(CastContext castContext) {
period = new Timeline.Period();
statusListener = new StatusListener();
seekResultCallback = new SeekResultCallback();
listeners = new CopyOnWriteArraySet<>();
listeners = new CopyOnWriteArrayList<>();
notificationsBatch = new ArrayList<>();
ongoingNotificationsTasks = new ArrayDeque<>();

SessionManager sessionManager = castContext.getSessionManager();
sessionManager.addSessionManagerListener(statusListener, CastSession.class);
Expand Down Expand Up @@ -296,12 +303,17 @@ public Looper getApplicationLooper() {

@Override
public void addListener(EventListener listener) {
listeners.add(listener);
listeners.addIfAbsent(new ListenerHolder(listener));
}

@Override
public void removeListener(EventListener listener) {
listeners.remove(listener);
for (ListenerHolder listenerHolder : listeners) {
if (listenerHolder.listener.equals(listener)) {
listenerHolder.release();
listeners.remove(listenerHolder);
}
}
}

@Override
Expand Down Expand Up @@ -347,14 +359,13 @@ public void seekTo(int windowIndex, long positionMs) {
pendingSeekCount++;
pendingSeekWindowIndex = windowIndex;
pendingSeekPositionMs = positionMs;
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
}
notificationsBatch.add(
new ListenerNotificationTask(
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK)));
} else if (pendingSeekCount == 0) {
for (EventListener listener : listeners) {
listener.onSeekProcessed();
}
notificationsBatch.add(new ListenerNotificationTask(EventListener::onSeekProcessed));
}
flushNotifications();
}

@Override
Expand Down Expand Up @@ -530,40 +541,51 @@ public void updateInternalState() {
|| this.playWhenReady != playWhenReady) {
this.playbackState = playbackState;
this.playWhenReady = playWhenReady;
for (EventListener listener : listeners) {
listener.onPlayerStateChanged(this.playWhenReady, this.playbackState);
}
notificationsBatch.add(
new ListenerNotificationTask(
listener -> listener.onPlayerStateChanged(this.playWhenReady, this.playbackState)));
}
@RepeatMode int repeatMode = fetchRepeatMode(remoteMediaClient);
if (this.repeatMode != repeatMode) {
this.repeatMode = repeatMode;
for (EventListener listener : listeners) {
listener.onRepeatModeChanged(repeatMode);
}
notificationsBatch.add(
new ListenerNotificationTask(listener -> listener.onRepeatModeChanged(this.repeatMode)));
}
maybeUpdateTimelineAndNotify();

int currentWindowIndex = C.INDEX_UNSET;
MediaQueueItem currentItem = remoteMediaClient.getCurrentItem();
if (currentItem != null) {
currentWindowIndex = currentTimeline.getIndexOfPeriod(currentItem.getItemId());
}
if (currentWindowIndex == C.INDEX_UNSET) {
// The timeline is empty. Fall back to index 0, which is what ExoPlayer would do.
currentWindowIndex = 0;
}
int currentWindowIndex = fetchCurrentWindowIndex(getMediaStatus());
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) {
this.currentWindowIndex = currentWindowIndex;
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION);
}
notificationsBatch.add(
new ListenerNotificationTask(
listener ->
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION)));
}
if (updateTracksAndSelections()) {
for (EventListener listener : listeners) {
listener.onTracksChanged(currentTrackGroups, currentTrackSelection);
}
notificationsBatch.add(
new ListenerNotificationTask(
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection)));
}
maybeUpdateTimelineAndNotify();
flushNotifications();
}

private void maybeUpdateTimelineAndNotify() {
if (updateTimeline()) {
@Player.TimelineChangeReason int reason = waitingForInitialTimeline
? Player.TIMELINE_CHANGE_REASON_PREPARED : Player.TIMELINE_CHANGE_REASON_DYNAMIC;
waitingForInitialTimeline = false;
for (EventListener listener : listeners) {
listener.onTimelineChanged(currentTimeline, null, reason);
}
notificationsBatch.add(
new ListenerNotificationTask(
listener ->
listener.onTimelineChanged(currentTimeline, /* manifest= */ null, reason)));
}
}

Expand Down Expand Up @@ -701,16 +723,6 @@ private static int fetchRepeatMode(RemoteMediaClient remoteMediaClient) {
}
}

/**
* Retrieves the current item index from {@code mediaStatus} and maps it into a window index. If
* there is no media session, returns 0.
*/
private static int fetchCurrentWindowIndex(@Nullable MediaStatus mediaStatus) {
Integer currentItemId = mediaStatus != null
? mediaStatus.getIndexById(mediaStatus.getCurrentItemId()) : null;
return currentItemId != null ? currentItemId : 0;
}

private static boolean isTrackActive(long id, long[] activeTrackIds) {
for (long activeTrackId : activeTrackIds) {
if (activeTrackId == id) {
Expand Down Expand Up @@ -826,7 +838,23 @@ public void onSessionResuming(CastSession castSession, String s) {

}

// Result callbacks hooks.
// Internal methods.

private void flushNotifications() {
boolean recursiveNotification = !ongoingNotificationsTasks.isEmpty();
ongoingNotificationsTasks.addAll(notificationsBatch);
notificationsBatch.clear();
if (recursiveNotification) {
// This will be handled once the current notification task is finished.
return;
}
while (!ongoingNotificationsTasks.isEmpty()) {
ongoingNotificationsTasks.peekFirst().execute();
ongoingNotificationsTasks.removeFirst();
}
}

// Internal classes.

private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {

Expand All @@ -840,9 +868,25 @@ public void onResult(@NonNull MediaChannelResult result) {
if (--pendingSeekCount == 0) {
pendingSeekWindowIndex = C.INDEX_UNSET;
pendingSeekPositionMs = C.TIME_UNSET;
for (EventListener listener : listeners) {
listener.onSeekProcessed();
}
notificationsBatch.add(new ListenerNotificationTask(EventListener::onSeekProcessed));
flushNotifications();
}
}
}

private final class ListenerNotificationTask {

private final Iterator<ListenerHolder> listenersSnapshot;
private final ListenerInvocation listenerInvocation;

private ListenerNotificationTask(ListenerInvocation listenerInvocation) {
this.listenersSnapshot = listeners.iterator();
this.listenerInvocation = listenerInvocation;
}

public void execute() {
while (listenersSnapshot.hasNext()) {
listenersSnapshot.next().invoke(listenerInvocation);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions extensions/ima/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ android {
dependencies {
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.11.2'
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:1.0.2'
implementation 'com.google.android.gms:play-services-ads-identifier:16.0.0'
testImplementation project(modulePrefix + 'testutils-robolectric')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1054,13 +1054,8 @@ private void startAdPlayback() {
long contentPositionMs = player.getCurrentPosition();
int adGroupIndexForPosition =
adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs));
if (adGroupIndexForPosition == 0) {
podIndexOffset = 0;
} else if (adGroupIndexForPosition == C.INDEX_UNSET) {
// There is no preroll and midroll pod indices start at 1.
podIndexOffset = -1;
} else /* adGroupIndexForPosition > 0 */ {
// Skip ad groups before the one at or immediately before the playback position.
if (adGroupIndexForPosition > 0 && adGroupIndexForPosition != C.INDEX_UNSET) {
// Skip any ad groups before the one at or immediately before the playback position.
for (int i = 0; i < adGroupIndexForPosition; i++) {
adPlaybackState = adPlaybackState.withSkippedAdGroup(i);
}
Expand All @@ -1070,9 +1065,18 @@ private void startAdPlayback() {
long adGroupBeforeTimeUs = adGroupTimesUs[adGroupIndexForPosition - 1];
double midpointTimeUs = (adGroupForPositionTimeUs + adGroupBeforeTimeUs) / 2d;
adsRenderingSettings.setPlayAdsAfterTime(midpointTimeUs / C.MICROS_PER_SECOND);
}

// We're removing one or more ads, which means that the earliest ad (if any) will be a
// midroll/postroll. Midroll pod indices start at 1.
// IMA indexes any remaining midroll ad pods from 1. A preroll (if present) has index 0.
// Store an index offset as we want to index all ads (including skipped ones) from 0.
if (adGroupIndexForPosition == 0 && adGroupTimesUs[0] == 0) {
// We are playing a preroll.
podIndexOffset = 0;
} else if (adGroupIndexForPosition == C.INDEX_UNSET) {
// There's no ad to play which means there's no preroll.
podIndexOffset = -1;
} else {
// We are playing a midroll and any ads before it were skipped.
podIndexOffset = adGroupIndexForPosition - 1;
}

Expand Down
Loading

0 comments on commit f6297f4

Please sign in to comment.