From 1094da2b61a05cacef7036ce1bb81c698bab90f8 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 31 Oct 2018 02:42:14 -0700 Subject: [PATCH] Fix player state transitions for postroll ads Before this change, the player state would be STATE_ENDED then STATE_BUFFERING (when the postroll ad was marked as played) then STATE_ENDED again. Queueing a final content media period with start point equal to the content duration after a postroll ensures that the player state doesn't change to STATE_ENDED transiently. Switch from using C.TIME_END_OF_SOURCE to C.TIME_UNSET for media periods that should not have an end point and are not followed by an ad. Content media periods before postrolls have end position C.TIME_END_OF_SOURCE. If the postroll ad loads, its content position is set to the content duration, which should be known at the point of loading the postroll, then a final 'empty' content media period with start position equal to its duration is queued. If the postroll fails to load, this empty content media period is queued up directly after the preceding content media period. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219443683 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 3 +- .../android/exoplayer2/MediaPeriodHolder.java | 53 ++-- .../android/exoplayer2/MediaPeriodInfo.java | 35 ++- .../android/exoplayer2/MediaPeriodQueue.java | 96 ++----- .../google/android/exoplayer2/Timeline.java | 2 +- .../exoplayer2/source/MediaSource.java | 13 +- .../source/ads/AdPlaybackState.java | 20 +- .../exoplayer2/MediaPeriodQueueTest.java | 257 ++++++++++++++++++ library/ui/src/main/res/values-af/strings.xml | 2 +- library/ui/src/main/res/values-am/strings.xml | 2 +- library/ui/src/main/res/values-ar/strings.xml | 2 +- library/ui/src/main/res/values-az/strings.xml | 2 +- .../src/main/res/values-b+sr+Latn/strings.xml | 2 +- library/ui/src/main/res/values-be/strings.xml | 2 +- library/ui/src/main/res/values-bg/strings.xml | 2 +- library/ui/src/main/res/values-bs/strings.xml | 2 +- library/ui/src/main/res/values-ca/strings.xml | 2 +- library/ui/src/main/res/values-cs/strings.xml | 2 +- library/ui/src/main/res/values-da/strings.xml | 2 +- library/ui/src/main/res/values-de/strings.xml | 2 +- library/ui/src/main/res/values-el/strings.xml | 2 +- .../ui/src/main/res/values-es-rUS/strings.xml | 2 +- library/ui/src/main/res/values-es/strings.xml | 2 +- library/ui/src/main/res/values-et/strings.xml | 2 +- library/ui/src/main/res/values-eu/strings.xml | 2 +- library/ui/src/main/res/values-fa/strings.xml | 2 +- library/ui/src/main/res/values-fi/strings.xml | 2 +- .../ui/src/main/res/values-fr-rCA/strings.xml | 2 +- library/ui/src/main/res/values-fr/strings.xml | 2 +- library/ui/src/main/res/values-gl/strings.xml | 2 +- library/ui/src/main/res/values-hi/strings.xml | 2 +- library/ui/src/main/res/values-hr/strings.xml | 2 +- library/ui/src/main/res/values-hu/strings.xml | 2 +- library/ui/src/main/res/values-hy/strings.xml | 2 +- library/ui/src/main/res/values-in/strings.xml | 2 +- library/ui/src/main/res/values-is/strings.xml | 2 +- library/ui/src/main/res/values-it/strings.xml | 2 +- library/ui/src/main/res/values-ja/strings.xml | 2 +- library/ui/src/main/res/values-ka/strings.xml | 2 +- library/ui/src/main/res/values-kk/strings.xml | 2 +- library/ui/src/main/res/values-km/strings.xml | 2 +- library/ui/src/main/res/values-ko/strings.xml | 2 +- library/ui/src/main/res/values-ky/strings.xml | 2 +- library/ui/src/main/res/values-lt/strings.xml | 2 +- library/ui/src/main/res/values-lv/strings.xml | 2 +- library/ui/src/main/res/values-mk/strings.xml | 2 +- library/ui/src/main/res/values-mn/strings.xml | 2 +- library/ui/src/main/res/values-mr/strings.xml | 2 +- library/ui/src/main/res/values-ms/strings.xml | 2 +- library/ui/src/main/res/values-my/strings.xml | 2 +- library/ui/src/main/res/values-nb/strings.xml | 2 +- library/ui/src/main/res/values-nl/strings.xml | 2 +- library/ui/src/main/res/values-pl/strings.xml | 2 +- .../ui/src/main/res/values-pt-rPT/strings.xml | 2 +- library/ui/src/main/res/values-pt/strings.xml | 2 +- library/ui/src/main/res/values-ro/strings.xml | 2 +- library/ui/src/main/res/values-ru/strings.xml | 2 +- library/ui/src/main/res/values-sk/strings.xml | 2 +- library/ui/src/main/res/values-sl/strings.xml | 2 +- library/ui/src/main/res/values-sq/strings.xml | 2 +- library/ui/src/main/res/values-sr/strings.xml | 2 +- library/ui/src/main/res/values-sv/strings.xml | 2 +- library/ui/src/main/res/values-sw/strings.xml | 2 +- library/ui/src/main/res/values-th/strings.xml | 2 +- library/ui/src/main/res/values-tr/strings.xml | 2 +- library/ui/src/main/res/values-uk/strings.xml | 2 +- library/ui/src/main/res/values-uz/strings.xml | 2 +- library/ui/src/main/res/values-vi/strings.xml | 2 +- .../ui/src/main/res/values-zh-rCN/strings.xml | 2 +- .../ui/src/main/res/values-zh-rHK/strings.xml | 2 +- .../ui/src/main/res/values-zh-rTW/strings.xml | 2 +- library/ui/src/main/res/values-zu/strings.xml | 2 +- 72 files changed, 443 insertions(+), 164 deletions(-) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index cc621c6218..71bc048902 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -666,7 +666,8 @@ public final class ImaAdsLoader // until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered // just after an ad group isn't incorrectly attributed to the next ad group. int nextAdGroupIndex = - adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); + adPlaybackState.getAdGroupIndexAfterPositionUs( + C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); if (nextAdGroupIndex != expectedAdGroupIndex && nextAdGroupIndex != C.INDEX_UNSET) { long nextAdGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]); if (nextAdGroupTimeMs == C.TIME_END_OF_SOURCE) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 4fc21475d9..7becac7b55 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.source.ClippingMediaPeriod; import com.google.android.exoplayer2.source.EmptySampleStream; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -88,16 +89,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; this.info = info; sampleStreams = new SampleStream[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length]; - MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, allocator); - if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { - mediaPeriod = - new ClippingMediaPeriod( - mediaPeriod, - /* enableInitialDiscontinuity= */ true, - /* startUs= */ 0, - info.id.endPositionUs); - } - this.mediaPeriod = mediaPeriod; + mediaPeriod = createMediaPeriod(info.id, mediaSource, allocator); } /** @@ -302,16 +294,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; public void release() { disableTrackSelectionsInResult(); trackSelectorResult = null; - try { - if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { - mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); - } else { - mediaSource.releasePeriod(mediaPeriod); - } - } catch (RuntimeException e) { - // There's nothing we can do. - Log.e(TAG, "Period release failed.", e); - } + releaseMediaPeriod(info.id, mediaSource, mediaPeriod); } /** @@ -413,4 +396,34 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; private boolean isLoadingMediaPeriod() { return next == null; } + + /** Returns a media period corresponding to the given {@code id}. */ + private static MediaPeriod createMediaPeriod( + MediaPeriodId id, MediaSource mediaSource, Allocator allocator) { + MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator); + if (id.endPositionUs != C.TIME_UNSET && id.endPositionUs != C.TIME_END_OF_SOURCE) { + mediaPeriod = + new ClippingMediaPeriod( + mediaPeriod, + /* enableInitialDiscontinuity= */ true, + /* startUs= */ 0, + id.endPositionUs); + } + return mediaPeriod; + } + + /** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */ + private static void releaseMediaPeriod( + MediaPeriodId id, MediaSource mediaSource, MediaPeriod mediaPeriod) { + try { + if (id.endPositionUs != C.TIME_UNSET && id.endPositionUs != C.TIME_END_OF_SOURCE) { + mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); + } else { + mediaSource.releasePeriod(mediaPeriod); + } + } catch (RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Period release failed.", e); + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java index ba19b54c3f..e57100931e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java @@ -15,8 +15,10 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; +import com.google.android.exoplayer2.util.Util; /** Stores the information required to load and play a {@link MediaPeriod}. */ /* package */ final class MediaPeriodInfo { @@ -32,8 +34,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; public final long contentPositionUs; /** * The duration of the media period, like {@link MediaPeriodId#endPositionUs} but with {@link - * C#TIME_END_OF_SOURCE} resolved to the timeline period duration. May be {@link C#TIME_UNSET} if - * the end position is not known. + * C#TIME_END_OF_SOURCE} and {@link C#TIME_UNSET} resolved to the timeline period duration if + * known. */ public final long durationUs; /** @@ -72,4 +74,33 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; isLastInTimelinePeriod, isFinal); } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MediaPeriodInfo that = (MediaPeriodInfo) o; + return startPositionUs == that.startPositionUs + && contentPositionUs == that.contentPositionUs + && durationUs == that.durationUs + && isLastInTimelinePeriod == that.isLastInTimelinePeriod + && isFinal == that.isFinal + && Util.areEqual(id, that.id); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + (int) startPositionUs; + result = 31 * result + (int) contentPositionUs; + result = 31 * result + (int) durationUs; + result = 31 * result + (isLastInTimelinePeriod ? 1 : 0); + result = 31 * result + (isFinal ? 1 : 0); + return result; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 40bc658953..de09fc13a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -351,17 +351,18 @@ import com.google.android.exoplayer2.util.Assertions; * @return The updated media period info for the current timeline. */ public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info) { - boolean isLastInPeriod = isLastInPeriod(info.id); - boolean isLastInTimeline = isLastInTimeline(info.id, isLastInPeriod); + MediaPeriodId id = info.id; + boolean isLastInPeriod = isLastInPeriod(id); + boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); timeline.getPeriodByUid(info.id.periodUid, period); long durationUs = - info.id.isAd() - ? period.getAdDurationUs(info.id.adGroupIndex, info.id.adIndexInAdGroup) - : (info.id.endPositionUs == C.TIME_END_OF_SOURCE + id.isAd() + ? period.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup) + : (id.endPositionUs == C.TIME_UNSET || id.endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() - : info.id.endPositionUs); + : id.endPositionUs); return new MediaPeriodInfo( - info.id, + id, info.startPositionUs, info.contentPositionUs, durationUs, @@ -404,7 +405,7 @@ import com.google.android.exoplayer2.util.Assertions; int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); long endPositionUs = nextAdGroupIndex == C.INDEX_UNSET - ? C.TIME_END_OF_SOURCE + ? C.TIME_UNSET : period.getAdGroupTimeUs(nextAdGroupIndex); return new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs); } else { @@ -580,7 +581,8 @@ import com.google.android.exoplayer2.util.Assertions; } MediaPeriodId periodId = resolveMediaPeriodIdForAds(nextPeriodUid, startPositionUs, windowSequenceNumber); - return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); + return getMediaPeriodInfo( + periodId, /* contentPositionUs= */ startPositionUs, startPositionUs); } MediaPeriodId currentPeriodId = mediaPeriodInfo.id; @@ -626,14 +628,14 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfoForContent( currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber); } - } else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) { + } else { // Play the next ad group if it's available. int nextAdGroupIndex = period.getAdGroupIndexForPositionUs(mediaPeriodInfo.id.endPositionUs); if (nextAdGroupIndex == C.INDEX_UNSET) { - // The next ad group can't be played. Play content from the ad group position instead. + // The next ad group can't be played. Play content from the previous end position instead. return getMediaPeriodInfoForContent( currentPeriodId.periodUid, - mediaPeriodInfo.id.endPositionUs, + /* startPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); } int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); @@ -643,30 +645,8 @@ import com.google.android.exoplayer2.util.Assertions; currentPeriodId.periodUid, nextAdGroupIndex, adIndexInAdGroup, - mediaPeriodInfo.id.endPositionUs, + /* contentPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); - } else { - // Check if the postroll ad should be played. - int adGroupCount = period.getAdGroupCount(); - if (adGroupCount == 0) { - return null; - } - int adGroupIndex = adGroupCount - 1; - if (period.getAdGroupTimeUs(adGroupIndex) != C.TIME_END_OF_SOURCE - || period.hasPlayedAdGroup(adGroupIndex)) { - return null; - } - int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); - if (!period.isAdAvailable(adGroupIndex, adIndexInAdGroup)) { - return null; - } - long contentDurationUs = period.getDurationUs(); - return getMediaPeriodInfoForAd( - currentPeriodId.periodUid, - adGroupIndex, - adIndexInAdGroup, - contentDurationUs, - currentPeriodId.windowSequenceNumber); } } @@ -696,8 +676,6 @@ import com.google.android.exoplayer2.util.Assertions; long windowSequenceNumber) { MediaPeriodId id = new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); - boolean isLastInPeriod = isLastInPeriod(id); - boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = timeline .getPeriodByUid(id.periodUid, period) @@ -711,49 +689,35 @@ import com.google.android.exoplayer2.util.Assertions; startPositionUs, contentPositionUs, durationUs, - isLastInPeriod, - isLastInTimeline); + /* isLastInTimelinePeriod= */ false, + /* isFinal= */ false); } private MediaPeriodInfo getMediaPeriodInfoForContent( Object periodUid, long startPositionUs, long windowSequenceNumber) { int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); long endPositionUs = - nextAdGroupIndex == C.INDEX_UNSET - ? C.TIME_END_OF_SOURCE - : period.getAdGroupTimeUs(nextAdGroupIndex); + nextAdGroupIndex != C.INDEX_UNSET + ? period.getAdGroupTimeUs(nextAdGroupIndex) + : C.TIME_UNSET; MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs); - timeline.getPeriodByUid(id.periodUid, period); boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = - endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs; + endPositionUs == C.TIME_UNSET || endPositionUs == C.TIME_END_OF_SOURCE + ? period.durationUs + : endPositionUs; return new MediaPeriodInfo( - id, startPositionUs, C.TIME_UNSET, durationUs, isLastInPeriod, isLastInTimeline); + id, + startPositionUs, + /* contentPositionUs= */ C.TIME_UNSET, + durationUs, + isLastInPeriod, + isLastInTimeline); } private boolean isLastInPeriod(MediaPeriodId id) { - int adGroupCount = timeline.getPeriodByUid(id.periodUid, period).getAdGroupCount(); - if (adGroupCount == 0) { - return true; - } - - int lastAdGroupIndex = adGroupCount - 1; - boolean isAd = id.isAd(); - if (period.getAdGroupTimeUs(lastAdGroupIndex) != C.TIME_END_OF_SOURCE) { - // There's no postroll ad. - return !isAd && id.endPositionUs == C.TIME_END_OF_SOURCE; - } - - int postrollAdCount = period.getAdCountInAdGroup(lastAdGroupIndex); - if (postrollAdCount == C.LENGTH_UNSET) { - // We won't know if this is the last ad until we know how many postroll ads there are. - return false; - } - - boolean isLastAd = - isAd && id.adGroupIndex == lastAdGroupIndex && id.adIndexInAdGroup == postrollAdCount - 1; - return isLastAd || (!isAd && period.getFirstAdIndexToPlay(lastAdGroupIndex) == postrollAdCount); + return !id.isAd() && id.endPositionUs == C.TIME_UNSET; } private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 1639920aaa..61a31f7db5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -441,7 +441,7 @@ public abstract class Timeline { * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ public int getAdGroupIndexAfterPositionUs(long positionUs) { - return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs); + return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs, durationUs); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 2a94e884a6..d8335131f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -89,11 +89,10 @@ public interface MediaSource { public final long windowSequenceNumber; /** - * The end position of the media to play within the media period, in microseconds, or {@link - * C#TIME_END_OF_SOURCE} if the end position is the end of the media period. - * - *

Note that this only applies if the media period is for content (i.e., not for an ad) and - * is clipped to the position of the next ad group. + * The end position to which the media period's content is clipped in order to play a following + * ad group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if + * this media period is an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll + * ad follows at the end of this content media period. */ public final long endPositionUs; @@ -115,7 +114,7 @@ public interface MediaSource { * windows this media period is part of. */ public MediaPeriodId(Object periodUid, long windowSequenceNumber) { - this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_END_OF_SOURCE); + this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_UNSET); } /** @@ -143,7 +142,7 @@ public interface MediaSource { */ public MediaPeriodId( Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) { - this(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_END_OF_SOURCE); + this(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_UNSET); } private MediaPeriodId( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 41adb78906..fc1355df6a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -310,7 +310,9 @@ public final class AdPlaybackState { * unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has no * ads remaining to be played, or if there is no such ad group. * - * @param positionUs The position at or before which to find an ad group, in microseconds. + * @param positionUs The position at or before which to find an ad group, in microseconds, or + * {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case the index of any + * unplayed postroll ad group will be returned). * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ public int getAdGroupIndexForPositionUs(long positionUs) { @@ -327,10 +329,18 @@ public final class AdPlaybackState { * Returns the index of the next ad group after {@code positionUs} that has ads remaining to be * played. Returns {@link C#INDEX_UNSET} if there is no such ad group. * - * @param positionUs The position after which to find an ad group, in microseconds. + * @param positionUs The position after which to find an ad group, in microseconds, or {@link + * C#TIME_END_OF_SOURCE} for the end of the stream (in which case there can be no ad group + * after the position). + * @param periodDurationUs The duration of the containing period in microseconds, or {@link + * C#TIME_UNSET} if not known. * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ - public int getAdGroupIndexAfterPositionUs(long positionUs) { + public int getAdGroupIndexAfterPositionUs(long positionUs, long periodDurationUs) { + if (positionUs == C.TIME_END_OF_SOURCE + || (periodDurationUs != C.TIME_UNSET && positionUs >= periodDurationUs)) { + return C.INDEX_UNSET; + } // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. // In practice we expect there to be few ad groups so the search shouldn't be expensive. int index = 0; @@ -457,6 +467,10 @@ public final class AdPlaybackState { } private boolean isPositionBeforeAdGroup(long positionUs, int adGroupIndex) { + if (positionUs == C.TIME_END_OF_SOURCE) { + // The end of the content is at (but not before) any postroll ad, and after any other ads. + return false; + } long adGroupPositionUs = adGroupTimesUs[adGroupIndex]; if (adGroupPositionUs == C.TIME_END_OF_SOURCE) { return contentDurationUs == C.TIME_UNSET || positionUs < contentDurationUs; 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 new file mode 100644 index 0000000000..e8f43e3fe6 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2018 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; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; + +import android.net.Uri; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; +import com.google.android.exoplayer2.source.SinglePeriodTimeline; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; +import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.upstream.Allocator; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit tests for {@link MediaPeriodQueue}. */ +@RunWith(RobolectricTestRunner.class) +public final class MediaPeriodQueueTest { + + private static final long CONTENT_DURATION_US = 30 * C.MICROS_PER_SECOND; + private static final long FIRST_AD_START_TIME_US = 10 * C.MICROS_PER_SECOND; + private static final long SECOND_AD_START_TIME_US = 20 * C.MICROS_PER_SECOND; + + private static final Timeline CONTENT_TIMELINE = + new SinglePeriodTimeline(CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false); + private static final Uri AD_URI = Uri.EMPTY; + + private MediaPeriodQueue mediaPeriodQueue; + private AdPlaybackState adPlaybackState; + private Timeline timeline; + private Object periodUid; + + private PlaybackInfo playbackInfo; + private RendererCapabilities[] rendererCapabilities; + private TrackSelector trackSelector; + private Allocator allocator; + private MediaSource mediaSource; + + @Before + public void setUp() { + mediaPeriodQueue = new MediaPeriodQueue(); + mediaSource = mock(MediaSource.class); + rendererCapabilities = new RendererCapabilities[0]; + trackSelector = mock(TrackSelector.class); + allocator = mock(Allocator.class); + } + + @Test + public void testGetNextMediaPeriodInfo_withoutAds_returnsLastMediaPeriodInfo() { + setupInitialTimeline(/* initialPositionUs= */ 0); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() { + setupInitialTimeline(/* initialPositionUs= */ 0, /* adGroupTimesUs= */ 0); + setAdGroupLoaded(/* adGroupIndex= */ 0); + assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ 0); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withMidrollAds_returnsCorrectMediaPeriodInfos() { + setupInitialTimeline( + /* initialPositionUs= */ 0, + /* adGroupTimesUs= */ FIRST_AD_START_TIME_US, + SECOND_AD_START_TIME_US); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ FIRST_AD_START_TIME_US, + /* durationUs= */ FIRST_AD_START_TIME_US, + /* isLast= */ false); + // The next media period info should be null as we haven't loaded the ad yet. + advance(); + assertNull(getNextMediaPeriodInfo()); + setAdGroupLoaded(/* adGroupIndex= */ 0); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ FIRST_AD_START_TIME_US, + /* endPositionUs= */ SECOND_AD_START_TIME_US, + /* durationUs= */ SECOND_AD_START_TIME_US, + /* isLast= */ false); + advance(); + setAdGroupLoaded(/* adGroupIndex= */ 1); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 1, /* contentPositionUs= */ SECOND_AD_START_TIME_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ SECOND_AD_START_TIME_US, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withMidrollAndPostroll_returnsCorrectMediaPeriodInfos() { + setupInitialTimeline( + /* initialPositionUs= */ 0, + /* adGroupTimesUs= */ FIRST_AD_START_TIME_US, + C.TIME_END_OF_SOURCE); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ FIRST_AD_START_TIME_US, + /* durationUs= */ FIRST_AD_START_TIME_US, + /* isLast= */ false); + advance(); + setAdGroupLoaded(/* adGroupIndex= */ 0); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ FIRST_AD_START_TIME_US, + /* endPositionUs= */ C.TIME_END_OF_SOURCE, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ false); + advance(); + setAdGroupLoaded(/* adGroupIndex= */ 1); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 1, /* contentPositionUs= */ CONTENT_DURATION_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ CONTENT_DURATION_US, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() { + setupInitialTimeline(/* initialPositionUs= */ 0, /* adGroupTimesUs= */ C.TIME_END_OF_SOURCE); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ C.TIME_END_OF_SOURCE, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ false); + advance(); + setAdGroupFailedToLoad(/* adGroupIndex= */ 0); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ CONTENT_DURATION_US, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + private void setupInitialTimeline(long initialPositionUs, long... adGroupTimesUs) { + adPlaybackState = + new AdPlaybackState(adGroupTimesUs).withContentDurationUs(CONTENT_DURATION_US); + timeline = new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState); + periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); + mediaPeriodQueue.setTimeline(timeline); + playbackInfo = + new PlaybackInfo( + timeline, + /* manifest= */ null, + mediaPeriodQueue.resolveMediaPeriodIdForAds(periodUid, initialPositionUs), + /* startPositionUs= */ 0, + /* contentPositionUs= */ 0, + Player.STATE_READY, + /* isLoading= */ false, + /* trackGroups= */ null, + /* trackSelectorResult= */ null, + /* loadingMediaPeriodId= */ null, + /* bufferedPositionUs= */ 0, + /* totalBufferedDurationUs= */ 0, + /* positionUs= */ 0); + } + + private void advance() { + mediaPeriodQueue.enqueueNextMediaPeriod( + rendererCapabilities, trackSelector, allocator, mediaSource, getNextMediaPeriodInfo()); + mediaPeriodQueue.advancePlayingPeriod(); + } + + private MediaPeriodInfo getNextMediaPeriodInfo() { + return mediaPeriodQueue.getNextMediaPeriodInfo(/* rendererPositionUs= */ 0, playbackInfo); + } + + private void setAdGroupLoaded(int adGroupIndex) { + adPlaybackState = + adPlaybackState + .withAdCount(adGroupIndex, /* adCount= */ 1) + .withAdUri(adGroupIndex, /* adIndexInAdGroup= */ 0, AD_URI); + updateTimeline(); + } + + private void setAdGroupFailedToLoad(int adGroupIndex) { + adPlaybackState = + adPlaybackState + .withAdCount(adGroupIndex, /* adCount= */ 1) + .withAdLoadError(adGroupIndex, /* adIndexInAdGroup= */ 0); + updateTimeline(); + } + + private void updateTimeline() { + timeline = new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState); + mediaPeriodQueue.setTimeline(timeline); + } + + private void assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + long startPositionUs, long endPositionUs, long durationUs, boolean isLast) { + assertThat(getNextMediaPeriodInfo()) + .isEqualTo( + new MediaPeriodInfo( + new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, endPositionUs), + startPositionUs, + /* contentPositionUs= */ C.TIME_UNSET, + durationUs, + /* isLastInTimelinePeriod= */ isLast, + /* isFinal= */ isLast)); + } + + private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositionUs) { + assertThat(getNextMediaPeriodInfo()) + .isEqualTo( + new MediaPeriodInfo( + new MediaPeriodId( + periodUid, + adGroupIndex, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 0), + /* startPositionUs= */ 0, + contentPositionUs, + /* durationUs= */ C.TIME_UNSET, + /* isLastInTimelinePeriod= */ false, + /* isFinal= */ false)); + } +} diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml index 5b24387028..729d3b9609 100644 --- a/library/ui/src/main/res/values-af/strings.xml +++ b/library/ui/src/main/res/values-af/strings.xml @@ -12,7 +12,7 @@ Herhaal alles Skommel Volskermmodus - VR mode + VR-modus Aflaai Aflaaie Laai tans af diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index 933019ac9f..bae3776e35 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -12,7 +12,7 @@ ሁሉንም ድገም በውዝ የሙሉ ማያ ሁነታ - VR mode + የቪአር ሁነታ አውርድ የወረዱ በማውረድ ላይ diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index ec79ce48e9..c78aac1e65 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -12,7 +12,7 @@ تكرار الكل ترتيب عشوائي وضع ملء الشاشة - VR mode + وضع VR تنزيل التنزيلات جارٍ التنزيل. diff --git a/library/ui/src/main/res/values-az/strings.xml b/library/ui/src/main/res/values-az/strings.xml index 3a80bab572..c2f208e54e 100644 --- a/library/ui/src/main/res/values-az/strings.xml +++ b/library/ui/src/main/res/values-az/strings.xml @@ -12,7 +12,7 @@ Hamısı təkrarlansın Qarışdırın Tam ekran rejimi - VR mode + VR rejimi Endirin Endirmələr Endirilir diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index 23e1463725..6e90860a9c 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Pusti nasumično Režim celog ekrana - VR mode + VR režim Preuzmi Preuzimanja Preuzima se diff --git a/library/ui/src/main/res/values-be/strings.xml b/library/ui/src/main/res/values-be/strings.xml index 33ae18711a..f0b28d9b2e 100644 --- a/library/ui/src/main/res/values-be/strings.xml +++ b/library/ui/src/main/res/values-be/strings.xml @@ -12,7 +12,7 @@ Паўтарыць усе Перамяшаць Поўнаэкранны рэжым - VR mode + VR-рэжым Спампаваць Спампоўкі Спампоўваецца diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index 50ffac7b0a..fd2b21efce 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -12,7 +12,7 @@ Повтаряне на всички Разбъркване Режим на цял екран - VR mode + режим за VR Изтегляне Изтегляния Изтегля се diff --git a/library/ui/src/main/res/values-bs/strings.xml b/library/ui/src/main/res/values-bs/strings.xml index 942921f28e..fce36435fd 100644 --- a/library/ui/src/main/res/values-bs/strings.xml +++ b/library/ui/src/main/res/values-bs/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Izmiješaj Način rada preko cijelog ekrana - VR mode + VR način rada Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index c787ff0194..2875ccfe83 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -12,7 +12,7 @@ Repeteix tot Reprodueix aleatòriament Mode de pantalla completa - VR mode + Mode RV Baixa Baixades S\'està baixant diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index 31451856fc..7964068543 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -12,7 +12,7 @@ Opakovat vše Náhodně Režim celé obrazovky - VR mode + Režim VR Stáhnout Stahování Stahování diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index 6a446b0963..03409e1cdd 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -12,7 +12,7 @@ Gentag alle Bland Fuld skærm - VR mode + VR-tilstand Download Downloads Downloader diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index d2b162ba72..a096211b00 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -12,7 +12,7 @@ Alle wiederholen Zufallsmix Vollbildmodus - VR mode + VR-Modus Herunterladen Downloads Wird heruntergeladen diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index 6eb9fbc6de..598d1c45dc 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -12,7 +12,7 @@ Επανάληψη όλων Τυχαία αναπαραγωγή Λειτουργία πλήρους οθόνης - VR mode + Λειτουργία VR mode Λήψη Λήψεις Λήψη diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index 92604c5e34..1bfc2513ba 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -12,7 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa - VR mode + Modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index 4c6fcf7aa7..d424331765 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -12,7 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa - VR mode + Modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-et/strings.xml b/library/ui/src/main/res/values-et/strings.xml index d631499a78..38ee173bec 100644 --- a/library/ui/src/main/res/values-et/strings.xml +++ b/library/ui/src/main/res/values-et/strings.xml @@ -12,7 +12,7 @@ Korda kõiki Esita juhuslikus järjekorras Täisekraani režiim - VR mode + VR-režiim Allalaadimine Allalaadimised Allalaadimine diff --git a/library/ui/src/main/res/values-eu/strings.xml b/library/ui/src/main/res/values-eu/strings.xml index efdee2239d..8bc5f0670c 100644 --- a/library/ui/src/main/res/values-eu/strings.xml +++ b/library/ui/src/main/res/values-eu/strings.xml @@ -12,7 +12,7 @@ Errepikatu guztiak Erreproduzitu ausaz Pantaila osoko modua - VR mode + EB modua Deskargak Deskargak Deskargatzen diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index 50e52edd84..bef2aebcfe 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -12,7 +12,7 @@ تکرار همه درهم حالت تمام‌صفحه - VR mode + حالت واقعیت مجازی بارگیری بارگیری‌ها درحال بارگیری diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index 72819a376e..3280dff99b 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -12,7 +12,7 @@ Toista kaikki uudelleen Satunnaistoisto Koko näytön tila - VR mode + VR-tila Lataa Lataukset Ladataan diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index 6b8d459117..ce125f7d06 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -12,7 +12,7 @@ Tout lire en boucle Lecture aléatoire Mode Plein écran - VR mode + Mode RV Télécharger Téléchargements Téléchargement en cours… diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index 487007f573..b143832ca1 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -12,7 +12,7 @@ Tout lire en boucle Aléatoire Mode plein écran - VR mode + Mode RV Télécharger Téléchargements Téléchargement… diff --git a/library/ui/src/main/res/values-gl/strings.xml b/library/ui/src/main/res/values-gl/strings.xml index a5420df692..c321d28bec 100644 --- a/library/ui/src/main/res/values-gl/strings.xml +++ b/library/ui/src/main/res/values-gl/strings.xml @@ -12,7 +12,7 @@ Repetir todas as pistas Reprodución aleatoria Modo de pantalla completa - VR mode + Modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index a94462b4d9..373c0f4303 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -12,7 +12,7 @@ सभी को दोहराएं शफ़ल करें फ़ुलस्क्रीन मोड - VR mode + VR मोड डाउनलोड करें डाउनलोड की गई मीडिया फ़ाइलें डाउनलोड हो रहा है diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index 624d7f04fe..9958f9b4dc 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Reproduciraj nasumično Prikaz na cijelom zaslonu - VR mode + VR način Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index e1f4d77e96..f517ef6212 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -12,7 +12,7 @@ Összes szám ismétlése Keverés Teljes képernyős mód - VR mode + VR-mód Letöltés Letöltések Letöltés folyamatban diff --git a/library/ui/src/main/res/values-hy/strings.xml b/library/ui/src/main/res/values-hy/strings.xml index 390d848aff..d3060b06c4 100644 --- a/library/ui/src/main/res/values-hy/strings.xml +++ b/library/ui/src/main/res/values-hy/strings.xml @@ -12,7 +12,7 @@ Կրկնել բոլորը Խառնել Լիաէկրան ռեժիմ - VR mode + VR ռեժիմ Ներբեռնել Ներբեռնումներ Ներբեռնում diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index 5ac1696c8b..06c55fc020 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -12,7 +12,7 @@ Ulangi semua Acak Mode layar penuh - VR mode + Mode VR Download Download Mendownload diff --git a/library/ui/src/main/res/values-is/strings.xml b/library/ui/src/main/res/values-is/strings.xml index 4f0fbf0975..4a0b84db2f 100644 --- a/library/ui/src/main/res/values-is/strings.xml +++ b/library/ui/src/main/res/values-is/strings.xml @@ -12,7 +12,7 @@ Endurtaka allt Stokka Allur skjárinn - VR mode + sýndarveruleikastilling Sækja Niðurhal Sækir diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index 6d070c6aa9..84da9905b2 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -12,7 +12,7 @@ Ripeti tutto Riproduzione casuale Modalità a schermo intero - VR mode + Modalità VR Scarica Download Download… diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index eea78b2fb5..e159292348 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -12,7 +12,7 @@ 全曲をリピート シャッフル 全画面モード - VR mode + VR モード ダウンロード ダウンロード ダウンロードしています diff --git a/library/ui/src/main/res/values-ka/strings.xml b/library/ui/src/main/res/values-ka/strings.xml index 16f3bea5bf..ff879c5dd8 100644 --- a/library/ui/src/main/res/values-ka/strings.xml +++ b/library/ui/src/main/res/values-ka/strings.xml @@ -12,7 +12,7 @@ ყველას გამეორება არეულად დაკვრა სრულეკრანიანი რეჟიმი - VR mode + VR რეჟიმი ჩამოტვირთვა ჩამოტვირთვები მიმდინარეობს ჩამოტვირთვა diff --git a/library/ui/src/main/res/values-kk/strings.xml b/library/ui/src/main/res/values-kk/strings.xml index 26fe5d6019..f634579932 100644 --- a/library/ui/src/main/res/values-kk/strings.xml +++ b/library/ui/src/main/res/values-kk/strings.xml @@ -12,7 +12,7 @@ Барлығын қайталау Араластыру Толық экран режимі - VR mode + VR режимі Жүктеп алу Жүктеп алынғандар Жүктеп алынуда diff --git a/library/ui/src/main/res/values-km/strings.xml b/library/ui/src/main/res/values-km/strings.xml index ce88214f35..17476b16d1 100644 --- a/library/ui/src/main/res/values-km/strings.xml +++ b/library/ui/src/main/res/values-km/strings.xml @@ -12,7 +12,7 @@ លេង​ឡើងវិញ​ទាំងអស់ ច្របល់ មុខងារពេញ​អេក្រង់ - VR mode + មុខងារ VR ទាញយក ទាញយក កំពុង​ទាញ​យក diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index d61346ff90..a4096711f2 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -12,7 +12,7 @@ 모두 반복 셔플 전체화면 모드 - VR mode + 가상 현실 모드 다운로드 다운로드 다운로드 중 diff --git a/library/ui/src/main/res/values-ky/strings.xml b/library/ui/src/main/res/values-ky/strings.xml index ee7a90ede7..c85b70f108 100644 --- a/library/ui/src/main/res/values-ky/strings.xml +++ b/library/ui/src/main/res/values-ky/strings.xml @@ -12,7 +12,7 @@ Баарын кайталоо Аралаштыруу Толук экран режими - VR mode + VR режими Жүктөп алуу Жүктөлүп алынгандар Жүктөлүп алынууда diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index b3d296b4d8..12710e41fd 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -12,7 +12,7 @@ Kartoti viską Maišyti Viso ekrano režimas - VR mode + VR režimas Atsisiųsti Atsisiuntimai Atsisiunčiama diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index ed61a7a27f..a55570112f 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -12,7 +12,7 @@ Atkārtot visu Atskaņot jauktā secībā Pilnekrāna režīms - VR mode + VR režīms Lejupielādēt Lejupielādes Notiek lejupielāde diff --git a/library/ui/src/main/res/values-mk/strings.xml b/library/ui/src/main/res/values-mk/strings.xml index 4ed15c3913..f4382aaeae 100644 --- a/library/ui/src/main/res/values-mk/strings.xml +++ b/library/ui/src/main/res/values-mk/strings.xml @@ -12,7 +12,7 @@ Повтори ги сите Измешај Режим на цел екран - VR mode + Режим на VR Преземи Преземања Се презема diff --git a/library/ui/src/main/res/values-mn/strings.xml b/library/ui/src/main/res/values-mn/strings.xml index 81c90f34c5..9d8fb29408 100644 --- a/library/ui/src/main/res/values-mn/strings.xml +++ b/library/ui/src/main/res/values-mn/strings.xml @@ -12,7 +12,7 @@ Бүгдийг нь дахин тоглуулах Холих Бүтэн дэлгэцийн горим - VR mode + VR горим Татах Татaлт Татаж байна diff --git a/library/ui/src/main/res/values-mr/strings.xml b/library/ui/src/main/res/values-mr/strings.xml index 7b698e5bcf..d1ee83be30 100644 --- a/library/ui/src/main/res/values-mr/strings.xml +++ b/library/ui/src/main/res/values-mr/strings.xml @@ -12,7 +12,7 @@ सर्व रीपीट करा शफल करा फुल स्क्रीन मोड - VR mode + VR मोड डाउनलोड करा डाउनलोड डाउनलोड होत आहे diff --git a/library/ui/src/main/res/values-ms/strings.xml b/library/ui/src/main/res/values-ms/strings.xml index cfe822f2b8..3b3fc3109f 100644 --- a/library/ui/src/main/res/values-ms/strings.xml +++ b/library/ui/src/main/res/values-ms/strings.xml @@ -12,7 +12,7 @@ Ulang semua Rombak Mod skrin penuh - VR mode + Mod VR Muat turun Muat turun Memuat turun diff --git a/library/ui/src/main/res/values-my/strings.xml b/library/ui/src/main/res/values-my/strings.xml index 34e69c2977..68c1f27056 100644 --- a/library/ui/src/main/res/values-my/strings.xml +++ b/library/ui/src/main/res/values-my/strings.xml @@ -12,7 +12,7 @@ အားလုံး ပြန်ကျော့ရန် ရောသမမွှေ မျက်နှာပြင်အပြည့် မုဒ် - VR mode + VR မုဒ် ဒေါင်းလုဒ် လုပ်ရန် ဒေါင်းလုဒ်များ ဒေါင်းလုဒ်လုပ်နေသည် diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index 5f8e862f5d..e24dcbf959 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -12,7 +12,7 @@ Gjenta alle Tilfeldig rekkefølge Fullskjermmodus - VR mode + VR-modus Last ned Nedlastinger Laster ned diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index a3401a0114..18dd96ed9e 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -12,7 +12,7 @@ Alles herhalen Shuffle Modus \'Volledig scherm\' - VR mode + VR-modus Downloaden Downloads Downloaden diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index adb81b5779..66eb7d81c7 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -12,7 +12,7 @@ Powtórz wszystkie Odtwarzanie losowe Tryb pełnoekranowy - VR mode + Tryb VR Pobierz Pobieranie Pobieram diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index f5d8092723..5d199683d9 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -12,7 +12,7 @@ Repetir tudo Reproduzir aleatoriamente Modo de ecrã inteiro - VR mode + Modo de RV Transferir Transferências A transferir… diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index 2e86dd7acd..151d235554 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -12,7 +12,7 @@ Repetir tudo Aleatório Modo de tela cheia - VR mode + Modo RV Fazer o download Downloads Fazendo o download diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index a0bd5a0a16..93f5129d13 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -12,7 +12,7 @@ Repetați-le pe toate Redați aleatoriu Modul Ecran complet - VR mode + Mod RV Descărcați Descărcări Se descarcă diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index a45022ffb4..77d96f0bc0 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -12,7 +12,7 @@ Повторять все Перемешать Полноэкранный режим - VR mode + VR-режим Скачать Скачивания Скачивание… diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index 2d3997ed89..ae6c0fbb12 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -12,7 +12,7 @@ Opakovať všetko Náhodne prehrávať Režim celej obrazovky - VR mode + režim VR Stiahnuť Stiahnuté Sťahuje sa diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index 01fa00320a..16d9a6af03 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -12,7 +12,7 @@ Ponavljanje vseh Naključno predvajanje Celozaslonski način - VR mode + Način VR Prenos Prenosi Prenašanje diff --git a/library/ui/src/main/res/values-sq/strings.xml b/library/ui/src/main/res/values-sq/strings.xml index 29007fa20e..fcef9643e8 100644 --- a/library/ui/src/main/res/values-sq/strings.xml +++ b/library/ui/src/main/res/values-sq/strings.xml @@ -12,7 +12,7 @@ Përsërit të gjitha Përziej Modaliteti me ekran të plotë - VR mode + Modaliteti RV Shkarko Shkarkimet Po shkarkohet diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index 659fb113bc..19018f0bac 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -12,7 +12,7 @@ Понови све Пусти насумично Режим целог екрана - VR mode + ВР режим Преузми Преузимања Преузима се diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index d9c48aaebe..1f3e7db320 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -12,7 +12,7 @@ Upprepa alla Blanda spår Helskärmsläge - VR mode + VR-läge Ladda ned Nedladdningar Laddar ned diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index 83e6b7bc47..b699116dd7 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -12,7 +12,7 @@ Rudia zote Changanya Hali ya skrini nzima - VR mode + Hali ya Uhalisia Pepe Pakua Vipakuliwa Inapakua diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index 454a0f361d..7827c99d93 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -12,7 +12,7 @@ เล่นซ้ำทั้งหมด สุ่ม โหมดเต็มหน้าจอ - VR mode + โหมด VR ดาวน์โหลด ดาวน์โหลด กำลังดาวน์โหลด diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index 43bf4f885f..d47ca1e2aa 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -12,7 +12,7 @@ Tümünü tekrarla Karıştır Tam ekran modu - VR mode + VR modu İndir İndirilenler İndiriliyor diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index 0b00dbbe82..5f3097a256 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -12,7 +12,7 @@ Повторити всі Перемішати Повноекранний режим - VR mode + Режим віртуальної реальності Завантажити Завантаження Завантажується diff --git a/library/ui/src/main/res/values-uz/strings.xml b/library/ui/src/main/res/values-uz/strings.xml index b5cea1f59e..9427fc4149 100644 --- a/library/ui/src/main/res/values-uz/strings.xml +++ b/library/ui/src/main/res/values-uz/strings.xml @@ -12,7 +12,7 @@ Hammasini takrorlash Aralash Butun ekran rejimi - VR mode + VR rejimi Yuklab olish Yuklanmalar Yuklab olinmoqda diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index d5299d3e6d..ae6abb90f5 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -12,7 +12,7 @@ Lặp lại tất cả Phát ngẫu nhiên Chế độ toàn màn hình - VR mode + Chế độ thực tế ảo Tải xuống Tài nguyên đã tải xuống Đang tải xuống diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index ad3e4fe68d..cfb9a6fb11 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -12,7 +12,7 @@ 全部重复播放 随机播放 全屏模式 - VR mode + VR 模式 下载 下载内容 正在下载 diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index f538231146..be96b852f5 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -12,7 +12,7 @@ 全部重複播放 隨機播放 全螢幕模式 - VR mode + 虛擬現實模式 下載 下載內容 正在下載 diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index da4b01cc93..c17c9e44ed 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -12,7 +12,7 @@ 重複播放所有項目 隨機播放 全螢幕模式 - VR mode + 虛擬實境模式 下載 下載 下載中 diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index 4b80136a85..70eccd3c36 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -12,7 +12,7 @@ Phinda konke Shova Imodi yesikrini esigcwele - VR mode + Inqubo ye-VR Landa Ukulandwa Iyalanda