Merge pull request #261 from androidx/release-1.0.0-rc02

1.0.0-rc02
This commit is contained in:
tonihei 2023-03-03 10:02:19 +00:00 committed by GitHub
commit 1ef427f640
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
297 changed files with 1228 additions and 3844 deletions

View File

@ -17,6 +17,7 @@ body:
label: Media3 Version label: Media3 Version
description: What version of Media3 are you using? description: What version of Media3 are you using?
options: options:
- 1.0.0-rc02
- 1.0.0-rc01 - 1.0.0-rc01
- 1.0.0-beta03 - 1.0.0-beta03
- 1.0.0-beta02 - 1.0.0-beta02

View File

@ -1,5 +1,49 @@
# Release notes # Release notes
### 1.0.0-rc02 (2023-03-02)
This release corresponds to the
[ExoPlayer 2.18.4 release](https://github.com/google/ExoPlayer/releases/tag/r2.18.4).
* Core library:
* Fix network type detection on API 33
([#10970](https://github.com/google/ExoPlayer/issues/10970)).
* Fix `NullPointerException` when calling `ExoPlayer.isTunnelingEnabled`
([#10977](https://github.com/google/ExoPlayer/issues/10977)).
* Downloads:
* Make the maximum difference of the start time of two segments to be
merged configurable in `SegmentDownloader` and subclasses
([#248](https://github.com/androidx/media/pull/248)).
* Audio:
* Fix broken gapless MP3 playback on Samsung devices
([#8594](https://github.com/google/ExoPlayer/issues/8594)).
* Fix bug where playback speeds set immediately after disabling audio may
be overridden by a previous speed change
([#10882](https://github.com/google/ExoPlayer/issues/10882)).
* Video:
* Map HEVC HDR10 format to `HEVCProfileMain10HDR10` instead of
`HEVCProfileMain10`.
* Add workaround for a device issue on Chromecast with Google TV and
Lenovo M10 FHD Plus that causes 60fps AVC streams to be marked as
unsupported
([#10898](https://github.com/google/ExoPlayer/issues/10898)).
* Fix frame release performance issues when playing media with a frame
rate far higher than the screen refresh rate.
* Cast:
* Fix transient `STATE_IDLE` when transitioning between media items
([#245](https://github.com/androidx/media/issues/245)).
* RTSP:
* Catch the IllegalArgumentException thrown in parsing of invalid RTSP
Describe response messages
([#10971](https://github.com/google/ExoPlayer/issues/10971)).
* Session:
* Fix a bug where notification play/pause button doesn't update with
player state ([#192](https://github.com/androidx/media/issues/192)).
* IMA extension:
* Fix a bug which prevented DAI streams without any ads from starting
because the first (and in the case without ads the only) `LOADED` event
wasn't received.
### 1.0.0-rc01 (2023-02-16) ### 1.0.0-rc01 (2023-02-16)
This release corresponds to the This release corresponds to the
@ -73,17 +117,12 @@ This release corresponds to the
([#233](https://github.com/androidx/media/issues/233)). ([#233](https://github.com/androidx/media/issues/233)).
* Make `QueueTimeline` more robust in case of a shady legacy session state * Make `QueueTimeline` more robust in case of a shady legacy session state
([#241](https://github.com/androidx/media/issues/241)). ([#241](https://github.com/androidx/media/issues/241)).
* Metadata:
* Parse multiple null-separated values from ID3 frames, as permitted by
ID3 v2.4.
* Add `MediaMetadata.mediaType` to denote the type of content or the type
of folder described by the metadata.
* Add `MediaMetadata.isBrowsable` as a replacement for
`MediaMetadata.folderType`. The folder type will be deprecated in the
next release.
* Cast extension: * Cast extension:
* Bump Cast SDK version to 21.2.0. * Bump Cast SDK version to 21.2.0.
* IMA extension: * IMA extension:
* Map `PLAYER_STATE_LOADING` to `STATE_BUFFERING`
([#245](\(https://github.com/androidx/media/issues/245\)).
* IMA extension
* Remove player listener of the `ImaServerSideAdInsertionMediaSource` on * Remove player listener of the `ImaServerSideAdInsertionMediaSource` on
the application thread to avoid threading issues. the application thread to avoid threading issues.
* Add a property `focusSkipButtonWhenAvailable` to the * Add a property `focusSkipButtonWhenAvailable` to the
@ -92,6 +131,8 @@ This release corresponds to the
* Add a method `focusSkipButton()` to the * Add a method `focusSkipButton()` to the
`ImaServerSideAdInsertionMediaSource.AdsLoader` to programmatically `ImaServerSideAdInsertionMediaSource.AdsLoader` to programmatically
request to focus the skip button. request to focus the skip button.
* Fix a bug which prevented playback from starting for a DAI stream
without any ads.
* Bump IMA SDK version to 3.29.0. * Bump IMA SDK version to 3.29.0.
* Demo app: * Demo app:
* Request notification permission for download notifications at runtime * Request notification permission for download notifications at runtime

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
project.ext { project.ext {
releaseVersion = '1.0.0-rc01' releaseVersion = '1.0.0-rc02'
releaseVersionCode = 1_000_000_2_01 releaseVersionCode = 1_000_000_2_02
minSdkVersion = 16 minSdkVersion = 16
appTargetSdkVersion = 33 appTargetSdkVersion = 33
// API version before restricting local file access. // API version before restricting local file access.

View File

@ -1237,6 +1237,7 @@ public final class CastPlayer extends BasePlayer {
int receiverAppStatus = remoteMediaClient.getPlayerState(); int receiverAppStatus = remoteMediaClient.getPlayerState();
switch (receiverAppStatus) { switch (receiverAppStatus) {
case MediaStatus.PLAYER_STATE_BUFFERING: case MediaStatus.PLAYER_STATE_BUFFERING:
case MediaStatus.PLAYER_STATE_LOADING:
return STATE_BUFFERING; return STATE_BUFFERING;
case MediaStatus.PLAYER_STATE_PLAYING: case MediaStatus.PLAYER_STATE_PLAYING:
case MediaStatus.PLAYER_STATE_PAUSED: case MediaStatus.PLAYER_STATE_PAUSED:
@ -1299,6 +1300,7 @@ public final class CastPlayer extends BasePlayer {
return false; return false;
} }
@SuppressWarnings("VisibleForTests")
private static int getCastRepeatMode(@RepeatMode int repeatMode) { private static int getCastRepeatMode(@RepeatMode int repeatMode) {
switch (repeatMode) { switch (repeatMode) {
case REPEAT_MODE_ONE: case REPEAT_MODE_ONE:

View File

@ -331,7 +331,10 @@ public final class C {
*/ */
@UnstableApi public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS; @UnstableApi public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS;
/** Represents the behavior affecting whether spatialization will be used. */ /**
* Represents the behavior affecting whether spatialization will be used. One of {@link
* #SPATIALIZATION_BEHAVIOR_AUTO} or {@link #SPATIALIZATION_BEHAVIOR_NEVER}.
*/
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)

View File

@ -28,7 +28,7 @@ import androidx.media3.common.util.UnstableApi;
import java.util.List; import java.util.List;
/** /**
* A {@link Player} that forwards operations to another {@link Player}. Applications can use this * A {@link Player} that forwards method calls to another {@link Player}. Applications can use this
* class to suppress or modify specific operations, by overriding the respective methods. * class to suppress or modify specific operations, by overriding the respective methods.
*/ */
@UnstableApi @UnstableApi

View File

@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */ /** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.0.0-rc01"; public static final String VERSION = "1.0.0-rc02";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */ /** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-rc01"; public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-rc02";
/** /**
* The version of the library expressed as an integer, for example 1002003300. * The version of the library expressed as an integer, for example 1002003300.
@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
* (123-045-006-3-00). * (123-045-006-3-00).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 1_000_000_2_01; public static final int VERSION_INT = 1_000_000_2_02;
/** Whether the library was compiled with {@link Assertions} checks enabled. */ /** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true; public static final boolean ASSERTIONS_ENABLED = true;

View File

@ -27,8 +27,12 @@ import java.util.regex.Pattern;
/** /**
* Parser for color expressions found in styling formats, e.g. TTML and CSS. * Parser for color expressions found in styling formats, e.g. TTML and CSS.
* *
* @see <a href="https://w3c.github.io/webvtt/#styling">WebVTT CSS Styling</a> * <p>See also:
* @see <a href="https://www.w3.org/TR/ttml2/">Timed Text Markup Language 2 (TTML2) - 10.3.5</a> *
* <ul>
* <li><a href="https://w3c.github.io/webvtt/#styling">WebVTT CSS Styling</a>
* <li><a href="https://www.w3.org/TR/ttml2/">Timed Text Markup Language 2 (TTML2) - 10.3.5</a>
* </ul>
*/ */
@UnstableApi @UnstableApi
public final class ColorParser { public final class ColorParser {

View File

@ -94,7 +94,7 @@ public final class NetworkTypeObserver {
networkType = C.NETWORK_TYPE_UNKNOWN; networkType = C.NETWORK_TYPE_UNKNOWN;
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
Util.registerReceiverNotExported(context, new Receiver(), filter); context.registerReceiver(new Receiver(), filter);
} }
/** /**

View File

@ -206,6 +206,10 @@ public final class Util {
* apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link * apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link
* #SDK_INT} is 33 or above. * #SDK_INT} is 33 or above.
* *
* <p>Do not use this method if registering a receiver for a <a
* href="https://android.googlesource.com/platform/frameworks/base/+/master/core/res/AndroidManifest.xml">protected
* system broadcast</a>.
*
* @param context The context on which {@link Context#registerReceiver} will be called. * @param context The context on which {@link Context#registerReceiver} will be called.
* @param receiver The {@link BroadcastReceiver} to register. This value may be null. * @param receiver The {@link BroadcastReceiver} to register. This value may be null.
* @param filter Selects the Intent broadcasts to be received. * @param filter Selects the Intent broadcasts to be received.
@ -222,33 +226,6 @@ public final class Util {
} }
} }
/**
* Registers a {@link BroadcastReceiver} that's not intended to receive broadcasts from other
* apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link
* #SDK_INT} is 33 or above.
*
* @param context The context on which {@link Context#registerReceiver} will be called.
* @param receiver The {@link BroadcastReceiver} to register. This value may be null.
* @param filter Selects the Intent broadcasts to be received.
* @param handler Handler identifying the thread that will receive the Intent.
* @return The first sticky intent found that matches {@code filter}, or null if there are none.
*/
@UnstableApi
@Nullable
public static Intent registerReceiverNotExported(
Context context, BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
if (SDK_INT < 33) {
return context.registerReceiver(receiver, filter, /* broadcastPermission= */ null, handler);
} else {
return context.registerReceiver(
receiver,
filter,
/* broadcastPermission= */ null,
handler,
Context.RECEIVER_NOT_EXPORTED);
}
}
/** /**
* Calls {@link Context#startForegroundService(Intent)} if {@link #SDK_INT} is 26 or higher, or * Calls {@link Context#startForegroundService(Intent)} if {@link #SDK_INT} is 26 or higher, or
* {@link Context#startService(Intent)} otherwise. * {@link Context#startService(Intent)} otherwise.

View File

@ -794,11 +794,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public boolean exists() throws DatabaseIOException { public boolean exists() throws DatabaseIOException {
return VersionTable.getVersion( try {
databaseProvider.getReadableDatabase(), return VersionTable.getVersion(
VersionTable.FEATURE_CACHE_CONTENT_METADATA, databaseProvider.getReadableDatabase(),
checkNotNull(hexUid)) VersionTable.FEATURE_CACHE_CONTENT_METADATA,
!= VersionTable.VERSION_UNSET; checkNotNull(hexUid))
!= VersionTable.VERSION_UNSET;
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
} }
@Override @Override

View File

@ -21,7 +21,6 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Handler; import android.os.Handler;
import androidx.media3.common.util.Util;
/* package */ final class AudioBecomingNoisyManager { /* package */ final class AudioBecomingNoisyManager {
@ -47,8 +46,8 @@ import androidx.media3.common.util.Util;
*/ */
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (enabled && !receiverRegistered) { if (enabled && !receiverRegistered) {
Util.registerReceiverNotExported( context.registerReceiver(
context, receiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); receiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
receiverRegistered = true; receiverRegistered = true;
} else if (!enabled && receiverRegistered) { } else if (!enabled && receiverRegistered) {
context.unregisterReceiver(receiver); context.unregisterReceiver(receiver);

View File

@ -1724,8 +1724,9 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public boolean isTunnelingEnabled() { public boolean isTunnelingEnabled() {
verifyApplicationThread(); verifyApplicationThread();
for (RendererConfiguration config : playbackInfo.trackSelectorResult.rendererConfigurations) { for (@Nullable
if (config.tunneling) { RendererConfiguration config : playbackInfo.trackSelectorResult.rendererConfigurations) {
if (config != null && config.tunneling) {
return true; return true;
} }
} }

View File

@ -946,7 +946,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
livePlaybackSpeedControl.getAdjustedPlaybackSpeed( livePlaybackSpeedControl.getAdjustedPlaybackSpeed(
getCurrentLiveOffsetUs(), getTotalBufferedDurationUs()); getCurrentLiveOffsetUs(), getTotalBufferedDurationUs());
if (mediaClock.getPlaybackParameters().speed != adjustedSpeed) { if (mediaClock.getPlaybackParameters().speed != adjustedSpeed) {
mediaClock.setPlaybackParameters(playbackInfo.playbackParameters.withSpeed(adjustedSpeed)); setMediaClockPlaybackParameters(playbackInfo.playbackParameters.withSpeed(adjustedSpeed));
handlePlaybackParameters( handlePlaybackParameters(
playbackInfo.playbackParameters, playbackInfo.playbackParameters,
/* currentPlaybackSpeed= */ mediaClock.getPlaybackParameters().speed, /* currentPlaybackSpeed= */ mediaClock.getPlaybackParameters().speed,
@ -956,6 +956,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
private void setMediaClockPlaybackParameters(PlaybackParameters playbackParameters) {
// Previously sent speed updates from the media clock now become stale.
handler.removeMessages(MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL);
mediaClock.setPlaybackParameters(playbackParameters);
}
private void notifyTrackSelectionRebuffer() { private void notifyTrackSelectionRebuffer() {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
@ -1342,7 +1348,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) private void setPlaybackParametersInternal(PlaybackParameters playbackParameters)
throws ExoPlaybackException { throws ExoPlaybackException {
mediaClock.setPlaybackParameters(playbackParameters); setMediaClockPlaybackParameters(playbackParameters);
handlePlaybackParameters(mediaClock.getPlaybackParameters(), /* acknowledgeCommand= */ true); handlePlaybackParameters(mediaClock.getPlaybackParameters(), /* acknowledgeCommand= */ true);
} }
@ -1657,7 +1663,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
nextPendingMessageIndexHint = nextPendingMessageIndex; nextPendingMessageIndexHint = nextPendingMessageIndex;
} }
private void ensureStopped(Renderer renderer) throws ExoPlaybackException { private void ensureStopped(Renderer renderer) {
if (renderer.getState() == Renderer.STATE_STARTED) { if (renderer.getState() == Renderer.STATE_STARTED) {
renderer.stop(); renderer.stop();
} }
@ -1914,14 +1920,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodId newPeriodId, MediaPeriodId newPeriodId,
Timeline oldTimeline, Timeline oldTimeline,
MediaPeriodId oldPeriodId, MediaPeriodId oldPeriodId,
long positionForTargetOffsetOverrideUs) { long positionForTargetOffsetOverrideUs)
throws ExoPlaybackException {
if (!shouldUseLivePlaybackSpeedControl(newTimeline, newPeriodId)) { if (!shouldUseLivePlaybackSpeedControl(newTimeline, newPeriodId)) {
// Live playback speed control is unused for the current period, reset speed to user-defined // Live playback speed control is unused for the current period, reset speed to user-defined
// playback parameters or 1.0 for ad playback. // playback parameters or 1.0 for ad playback.
PlaybackParameters targetPlaybackParameters = PlaybackParameters targetPlaybackParameters =
newPeriodId.isAd() ? PlaybackParameters.DEFAULT : playbackInfo.playbackParameters; newPeriodId.isAd() ? PlaybackParameters.DEFAULT : playbackInfo.playbackParameters;
if (!mediaClock.getPlaybackParameters().equals(targetPlaybackParameters)) { if (!mediaClock.getPlaybackParameters().equals(targetPlaybackParameters)) {
mediaClock.setPlaybackParameters(targetPlaybackParameters); setMediaClockPlaybackParameters(targetPlaybackParameters);
handlePlaybackParameters(
playbackInfo.playbackParameters,
targetPlaybackParameters.speed,
/* updatePlaybackInfo= */ false,
/* acknowledgeCommand= */ false);
} }
return; return;
} }
@ -1970,7 +1982,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
return maxReadPositionUs; return maxReadPositionUs;
} }
private void updatePeriods() throws ExoPlaybackException, IOException { private void updatePeriods() throws ExoPlaybackException {
if (playbackInfo.timeline.isEmpty() || !mediaSourceList.isPrepared()) { if (playbackInfo.timeline.isEmpty() || !mediaSourceList.isPrepared()) {
// No periods available. // No periods available.
return; return;
@ -2012,7 +2024,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
private void maybeUpdateReadingPeriod() { private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
@Nullable MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); @Nullable MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (readingPeriodHolder == null) { if (readingPeriodHolder == null) {
return; return;

View File

@ -75,7 +75,7 @@ import androidx.media3.common.util.Util;
VolumeChangeReceiver receiver = new VolumeChangeReceiver(); VolumeChangeReceiver receiver = new VolumeChangeReceiver();
IntentFilter filter = new IntentFilter(VOLUME_CHANGED_ACTION); IntentFilter filter = new IntentFilter(VOLUME_CHANGED_ACTION);
try { try {
Util.registerReceiverNotExported(applicationContext, receiver, filter); applicationContext.registerReceiver(receiver, filter);
this.receiver = receiver; this.receiver = receiver;
} catch (RuntimeException e) { } catch (RuntimeException e) {
Log.w(TAG, "Error registering stream volume receiver", e); Log.w(TAG, "Error registering stream volume receiver", e);

View File

@ -88,8 +88,8 @@ public final class AudioCapabilities {
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static AudioCapabilities getCapabilities(Context context) { public static AudioCapabilities getCapabilities(Context context) {
Intent intent = Intent intent =
Util.registerReceiverNotExported( context.registerReceiver(
context, /* receiver= */ null, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); /* receiver= */ null, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
return getCapabilities(context, intent); return getCapabilities(context, intent);
} }

View File

@ -93,7 +93,9 @@ public final class AudioCapabilitiesReceiver {
@Nullable Intent stickyIntent = null; @Nullable Intent stickyIntent = null;
if (receiver != null) { if (receiver != null) {
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG); IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG);
stickyIntent = Util.registerReceiverNotExported(context, receiver, intentFilter, handler); stickyIntent =
context.registerReceiver(
receiver, intentFilter, /* broadcastPermission= */ null, handler);
} }
audioCapabilities = AudioCapabilities.getCapabilities(context, stickyIntent); audioCapabilities = AudioCapabilities.getCapabilities(context, stickyIntent);
return audioCapabilities; return audioCapabilities;

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.exoplayer.audio; package androidx.media3.exoplayer.audio;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -26,7 +27,6 @@ import android.os.SystemClock;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -146,6 +146,9 @@ import java.lang.reflect.Method;
/** The duration of time used to smooth over an adjustment between position sampling modes. */ /** The duration of time used to smooth over an adjustment between position sampling modes. */
private static final long MODE_SWITCH_SMOOTHING_DURATION_US = C.MICROS_PER_SECOND; private static final long MODE_SWITCH_SMOOTHING_DURATION_US = C.MICROS_PER_SECOND;
/** Minimum update interval for getting the raw playback head position, in milliseconds. */
private static final long RAW_PLAYBACK_HEAD_POSITION_UPDATE_INTERVAL_MS = 5;
private static final long FORCE_RESET_WORKAROUND_TIMEOUT_MS = 200; private static final long FORCE_RESET_WORKAROUND_TIMEOUT_MS = 200;
private static final int MAX_PLAYHEAD_OFFSET_COUNT = 10; private static final int MAX_PLAYHEAD_OFFSET_COUNT = 10;
@ -174,7 +177,8 @@ import java.lang.reflect.Method;
private boolean isOutputPcm; private boolean isOutputPcm;
private long lastLatencySampleTimeUs; private long lastLatencySampleTimeUs;
private long lastRawPlaybackHeadPosition; private long lastRawPlaybackHeadPositionSampleTimeMs;
private long rawPlaybackHeadPosition;
private long rawPlaybackHeadWrapCount; private long rawPlaybackHeadWrapCount;
private long passthroughWorkaroundPauseOffset; private long passthroughWorkaroundPauseOffset;
private int nextPlayheadOffsetIndex; private int nextPlayheadOffsetIndex;
@ -199,7 +203,7 @@ import java.lang.reflect.Method;
* @param listener A listener for position tracking events. * @param listener A listener for position tracking events.
*/ */
public AudioTrackPositionTracker(Listener listener) { public AudioTrackPositionTracker(Listener listener) {
this.listener = Assertions.checkNotNull(listener); this.listener = checkNotNull(listener);
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
try { try {
getLatencyMethod = AudioTrack.class.getMethod("getLatency", (Class<?>[]) null); getLatencyMethod = AudioTrack.class.getMethod("getLatency", (Class<?>[]) null);
@ -235,7 +239,7 @@ import java.lang.reflect.Method;
needsPassthroughWorkarounds = isPassthrough && needsPassthroughWorkarounds(outputEncoding); needsPassthroughWorkarounds = isPassthrough && needsPassthroughWorkarounds(outputEncoding);
isOutputPcm = Util.isEncodingLinearPcm(outputEncoding); isOutputPcm = Util.isEncodingLinearPcm(outputEncoding);
bufferSizeUs = isOutputPcm ? framesToDurationUs(bufferSize / outputPcmFrameSize) : C.TIME_UNSET; bufferSizeUs = isOutputPcm ? framesToDurationUs(bufferSize / outputPcmFrameSize) : C.TIME_UNSET;
lastRawPlaybackHeadPosition = 0; rawPlaybackHeadPosition = 0;
rawPlaybackHeadWrapCount = 0; rawPlaybackHeadWrapCount = 0;
passthroughWorkaroundPauseOffset = 0; passthroughWorkaroundPauseOffset = 0;
hasData = false; hasData = false;
@ -253,10 +257,11 @@ import java.lang.reflect.Method;
if (audioTimestampPoller != null) { if (audioTimestampPoller != null) {
audioTimestampPoller.reset(); audioTimestampPoller.reset();
} }
resetSyncParams();
} }
public long getCurrentPositionUs(boolean sourceEnded) { public long getCurrentPositionUs(boolean sourceEnded) {
if (Assertions.checkNotNull(this.audioTrack).getPlayState() == PLAYSTATE_PLAYING) { if (checkNotNull(this.audioTrack).getPlayState() == PLAYSTATE_PLAYING) {
maybeSampleSyncParams(); maybeSampleSyncParams();
} }
@ -264,7 +269,7 @@ import java.lang.reflect.Method;
// Otherwise, derive a smoothed position by sampling the track's frame position. // Otherwise, derive a smoothed position by sampling the track's frame position.
long systemTimeUs = System.nanoTime() / 1000; long systemTimeUs = System.nanoTime() / 1000;
long positionUs; long positionUs;
AudioTimestampPoller audioTimestampPoller = Assertions.checkNotNull(this.audioTimestampPoller); AudioTimestampPoller audioTimestampPoller = checkNotNull(this.audioTimestampPoller);
boolean useGetTimestampMode = audioTimestampPoller.hasAdvancingTimestamp(); boolean useGetTimestampMode = audioTimestampPoller.hasAdvancingTimestamp();
if (useGetTimestampMode) { if (useGetTimestampMode) {
// Calculate the speed-adjusted position using the timestamp (which may be in the future). // Calculate the speed-adjusted position using the timestamp (which may be in the future).
@ -282,7 +287,9 @@ import java.lang.reflect.Method;
// getPlaybackHeadPositionUs() only has a granularity of ~20 ms, so we base the position off // getPlaybackHeadPositionUs() only has a granularity of ~20 ms, so we base the position off
// the system clock (and a smoothed offset between it and the playhead position) so as to // the system clock (and a smoothed offset between it and the playhead position) so as to
// prevent jitter in the reported positions. // prevent jitter in the reported positions.
positionUs = systemTimeUs + smoothedPlayheadOffsetUs; positionUs =
Util.getMediaDurationForPlayoutDuration(
systemTimeUs + smoothedPlayheadOffsetUs, audioTrackPlaybackSpeed);
} }
if (!sourceEnded) { if (!sourceEnded) {
positionUs = max(0, positionUs - latencyUs); positionUs = max(0, positionUs - latencyUs);
@ -329,12 +336,12 @@ import java.lang.reflect.Method;
/** Starts position tracking. Must be called immediately before {@link AudioTrack#play()}. */ /** Starts position tracking. Must be called immediately before {@link AudioTrack#play()}. */
public void start() { public void start() {
Assertions.checkNotNull(audioTimestampPoller).reset(); checkNotNull(audioTimestampPoller).reset();
} }
/** Returns whether the audio track is in the playing state. */ /** Returns whether the audio track is in the playing state. */
public boolean isPlaying() { public boolean isPlaying() {
return Assertions.checkNotNull(audioTrack).getPlayState() == PLAYSTATE_PLAYING; return checkNotNull(audioTrack).getPlayState() == PLAYSTATE_PLAYING;
} }
/** /**
@ -345,7 +352,7 @@ import java.lang.reflect.Method;
* @return Whether the caller can write data to the track. * @return Whether the caller can write data to the track.
*/ */
public boolean mayHandleBuffer(long writtenFrames) { public boolean mayHandleBuffer(long writtenFrames) {
@PlayState int playState = Assertions.checkNotNull(audioTrack).getPlayState(); @PlayState int playState = checkNotNull(audioTrack).getPlayState();
if (needsPassthroughWorkarounds) { if (needsPassthroughWorkarounds) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its // An AC-3 audio track continues to play data written while it is paused. Stop writing so its
// buffer empties. See [Internal: b/18899620]. // buffer empties. See [Internal: b/18899620].
@ -426,7 +433,7 @@ import java.lang.reflect.Method;
if (stopTimestampUs == C.TIME_UNSET) { if (stopTimestampUs == C.TIME_UNSET) {
// The audio track is going to be paused, so reset the timestamp poller to ensure it doesn't // The audio track is going to be paused, so reset the timestamp poller to ensure it doesn't
// supply an advancing position. // supply an advancing position.
Assertions.checkNotNull(audioTimestampPoller).reset(); checkNotNull(audioTimestampPoller).reset();
return true; return true;
} }
// We've handled the end of the stream already, so there's no need to pause the track. // We've handled the end of the stream already, so there's no need to pause the track.
@ -444,15 +451,17 @@ import java.lang.reflect.Method;
} }
private void maybeSampleSyncParams() { private void maybeSampleSyncParams() {
long playbackPositionUs = getPlaybackHeadPositionUs();
if (playbackPositionUs == 0) {
// The AudioTrack hasn't output anything yet.
return;
}
long systemTimeUs = System.nanoTime() / 1000; long systemTimeUs = System.nanoTime() / 1000;
if (systemTimeUs - lastPlayheadSampleTimeUs >= MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US) { if (systemTimeUs - lastPlayheadSampleTimeUs >= MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US) {
long playbackPositionUs = getPlaybackHeadPositionUs();
if (playbackPositionUs == 0) {
// The AudioTrack hasn't output anything yet.
return;
}
// Take a new sample and update the smoothed offset between the system clock and the playhead. // Take a new sample and update the smoothed offset between the system clock and the playhead.
playheadOffsets[nextPlayheadOffsetIndex] = playbackPositionUs - systemTimeUs; playheadOffsets[nextPlayheadOffsetIndex] =
Util.getPlayoutDurationForMediaDuration(playbackPositionUs, audioTrackPlaybackSpeed)
- systemTimeUs;
nextPlayheadOffsetIndex = (nextPlayheadOffsetIndex + 1) % MAX_PLAYHEAD_OFFSET_COUNT; nextPlayheadOffsetIndex = (nextPlayheadOffsetIndex + 1) % MAX_PLAYHEAD_OFFSET_COUNT;
if (playheadOffsetCount < MAX_PLAYHEAD_OFFSET_COUNT) { if (playheadOffsetCount < MAX_PLAYHEAD_OFFSET_COUNT) {
playheadOffsetCount++; playheadOffsetCount++;
@ -470,12 +479,12 @@ import java.lang.reflect.Method;
return; return;
} }
maybePollAndCheckTimestamp(systemTimeUs, playbackPositionUs); maybePollAndCheckTimestamp(systemTimeUs);
maybeUpdateLatency(systemTimeUs); maybeUpdateLatency(systemTimeUs);
} }
private void maybePollAndCheckTimestamp(long systemTimeUs, long playbackPositionUs) { private void maybePollAndCheckTimestamp(long systemTimeUs) {
AudioTimestampPoller audioTimestampPoller = Assertions.checkNotNull(this.audioTimestampPoller); AudioTimestampPoller audioTimestampPoller = checkNotNull(this.audioTimestampPoller);
if (!audioTimestampPoller.maybePollTimestamp(systemTimeUs)) { if (!audioTimestampPoller.maybePollTimestamp(systemTimeUs)) {
return; return;
} }
@ -483,6 +492,7 @@ import java.lang.reflect.Method;
// Check the timestamp and accept/reject it. // Check the timestamp and accept/reject it.
long audioTimestampSystemTimeUs = audioTimestampPoller.getTimestampSystemTimeUs(); long audioTimestampSystemTimeUs = audioTimestampPoller.getTimestampSystemTimeUs();
long audioTimestampPositionFrames = audioTimestampPoller.getTimestampPositionFrames(); long audioTimestampPositionFrames = audioTimestampPoller.getTimestampPositionFrames();
long playbackPositionUs = getPlaybackHeadPositionUs();
if (Math.abs(audioTimestampSystemTimeUs - systemTimeUs) > MAX_AUDIO_TIMESTAMP_OFFSET_US) { if (Math.abs(audioTimestampSystemTimeUs - systemTimeUs) > MAX_AUDIO_TIMESTAMP_OFFSET_US) {
listener.onSystemTimeUsMismatch( listener.onSystemTimeUsMismatch(
audioTimestampPositionFrames, audioTimestampPositionFrames,
@ -511,8 +521,7 @@ import java.lang.reflect.Method;
// Compute the audio track latency, excluding the latency due to the buffer (leaving // Compute the audio track latency, excluding the latency due to the buffer (leaving
// latency due to the mixer and audio hardware driver). // latency due to the mixer and audio hardware driver).
latencyUs = latencyUs =
castNonNull((Integer) getLatencyMethod.invoke(Assertions.checkNotNull(audioTrack))) castNonNull((Integer) getLatencyMethod.invoke(checkNotNull(audioTrack))) * 1000L
* 1000L
- bufferSizeUs; - bufferSizeUs;
// Check that the latency is non-negative. // Check that the latency is non-negative.
latencyUs = max(latencyUs, 0); latencyUs = max(latencyUs, 0);
@ -550,7 +559,7 @@ import java.lang.reflect.Method;
*/ */
private boolean forceHasPendingData() { private boolean forceHasPendingData() {
return needsPassthroughWorkarounds return needsPassthroughWorkarounds
&& Assertions.checkNotNull(audioTrack).getPlayState() == AudioTrack.PLAYSTATE_PAUSED && checkNotNull(audioTrack).getPlayState() == AudioTrack.PLAYSTATE_PAUSED
&& getPlaybackHeadPosition() == 0; && getPlaybackHeadPosition() == 0;
} }
@ -576,34 +585,44 @@ import java.lang.reflect.Method;
* @return The playback head position, in frames. * @return The playback head position, in frames.
*/ */
private long getPlaybackHeadPosition() { private long getPlaybackHeadPosition() {
AudioTrack audioTrack = Assertions.checkNotNull(this.audioTrack); long currentTimeMs = SystemClock.elapsedRealtime();
if (stopTimestampUs != C.TIME_UNSET) { if (stopTimestampUs != C.TIME_UNSET) {
// Simulate the playback head position up to the total number of frames submitted. // Simulate the playback head position up to the total number of frames submitted.
long elapsedTimeSinceStopUs = (SystemClock.elapsedRealtime() * 1000) - stopTimestampUs; long elapsedTimeSinceStopUs = (currentTimeMs * 1000) - stopTimestampUs;
long framesSinceStop = (elapsedTimeSinceStopUs * outputSampleRate) / C.MICROS_PER_SECOND; long mediaTimeSinceStopUs =
Util.getMediaDurationForPlayoutDuration(elapsedTimeSinceStopUs, audioTrackPlaybackSpeed);
long framesSinceStop = (mediaTimeSinceStopUs * outputSampleRate) / C.MICROS_PER_SECOND;
return min(endPlaybackHeadPosition, stopPlaybackHeadPosition + framesSinceStop); return min(endPlaybackHeadPosition, stopPlaybackHeadPosition + framesSinceStop);
} }
if (currentTimeMs - lastRawPlaybackHeadPositionSampleTimeMs
>= RAW_PLAYBACK_HEAD_POSITION_UPDATE_INTERVAL_MS) {
updateRawPlaybackHeadPosition(currentTimeMs);
lastRawPlaybackHeadPositionSampleTimeMs = currentTimeMs;
}
return rawPlaybackHeadPosition + (rawPlaybackHeadWrapCount << 32);
}
private void updateRawPlaybackHeadPosition(long currentTimeMs) {
AudioTrack audioTrack = checkNotNull(this.audioTrack);
int state = audioTrack.getPlayState(); int state = audioTrack.getPlayState();
if (state == PLAYSTATE_STOPPED) { if (state == PLAYSTATE_STOPPED) {
// The audio track hasn't been started. // The audio track hasn't been started. Keep initial zero timestamp.
return 0; return;
} }
long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition(); long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition();
if (needsPassthroughWorkarounds) { if (needsPassthroughWorkarounds) {
// Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22 // Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22
// where the playback head position jumps back to zero on paused passthrough/direct audio // where the playback head position jumps back to zero on paused passthrough/direct audio
// tracks. See [Internal: b/19187573]. // tracks. See [Internal: b/19187573].
if (state == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { if (state == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) {
passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition; passthroughWorkaroundPauseOffset = this.rawPlaybackHeadPosition;
} }
rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset;
} }
if (Util.SDK_INT <= 29) { if (Util.SDK_INT <= 29) {
if (rawPlaybackHeadPosition == 0 if (rawPlaybackHeadPosition == 0
&& lastRawPlaybackHeadPosition > 0 && this.rawPlaybackHeadPosition > 0
&& state == PLAYSTATE_PLAYING) { && state == PLAYSTATE_PLAYING) {
// If connecting a Bluetooth audio device fails, the AudioTrack may be left in a state // If connecting a Bluetooth audio device fails, the AudioTrack may be left in a state
// where its Java API is in the playing state, but the native track is stopped. When this // where its Java API is in the playing state, but the native track is stopped. When this
@ -611,19 +630,18 @@ import java.lang.reflect.Method;
// playback head position and force the track to be reset after // playback head position and force the track to be reset after
// {@link #FORCE_RESET_WORKAROUND_TIMEOUT_MS} has elapsed. // {@link #FORCE_RESET_WORKAROUND_TIMEOUT_MS} has elapsed.
if (forceResetWorkaroundTimeMs == C.TIME_UNSET) { if (forceResetWorkaroundTimeMs == C.TIME_UNSET) {
forceResetWorkaroundTimeMs = SystemClock.elapsedRealtime(); forceResetWorkaroundTimeMs = currentTimeMs;
} }
return lastRawPlaybackHeadPosition; return;
} else { } else {
forceResetWorkaroundTimeMs = C.TIME_UNSET; forceResetWorkaroundTimeMs = C.TIME_UNSET;
} }
} }
if (lastRawPlaybackHeadPosition > rawPlaybackHeadPosition) { if (this.rawPlaybackHeadPosition > rawPlaybackHeadPosition) {
// The value must have wrapped around. // The value must have wrapped around.
rawPlaybackHeadWrapCount++; rawPlaybackHeadWrapCount++;
} }
lastRawPlaybackHeadPosition = rawPlaybackHeadPosition; this.rawPlaybackHeadPosition = rawPlaybackHeadPosition;
return rawPlaybackHeadPosition + (rawPlaybackHeadWrapCount << 32);
} }
} }

View File

@ -191,11 +191,13 @@ import java.nio.ByteBuffer;
@Override @Override
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
bufferEnqueuer.maybeThrowException();
return asynchronousMediaCodecCallback.dequeueInputBufferIndex(); return asynchronousMediaCodecCallback.dequeueInputBufferIndex();
} }
@Override @Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
bufferEnqueuer.maybeThrowException();
return asynchronousMediaCodecCallback.dequeueOutputBufferIndex(bufferInfo); return asynchronousMediaCodecCallback.dequeueOutputBufferIndex(bufferInfo);
} }

View File

@ -162,7 +162,8 @@ class AsynchronousMediaCodecBufferEnqueuer {
blockUntilHandlerThreadIsIdle(); blockUntilHandlerThreadIsIdle();
} }
private void maybeThrowException() { /** Throw any exception that occurred on the enqueuer's background queueing thread. */
public void maybeThrowException() {
@Nullable RuntimeException exception = pendingRuntimeException.getAndSet(null); @Nullable RuntimeException exception = pendingRuntimeException.getAndSet(null);
if (exception != null) { if (exception != null) {
throw exception; throw exception;

View File

@ -263,11 +263,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// else, pendingOutputFormat may already be non-null following a previous flush, and remains // else, pendingOutputFormat may already be non-null following a previous flush, and remains
// set in this case. // set in this case.
// mediaCodecException is not reset to null. If the codec has raised an error, then it remains
// in FAILED_STATE even after flushing.
availableInputBuffers.clear(); availableInputBuffers.clear();
availableOutputBuffers.clear(); availableOutputBuffers.clear();
bufferInfos.clear(); bufferInfos.clear();
formats.clear(); formats.clear();
mediaCodecException = null;
} }
@GuardedBy("lock") @GuardedBy("lock")

View File

@ -857,7 +857,7 @@ public final class MediaCodecInfo {
* @param name The name of the codec. * @param name The name of the codec.
* @return Whether to enable the workaround. * @return Whether to enable the workaround.
*/ */
private static final boolean needsRotatedVerticalResolutionWorkaround(String name) { private static boolean needsRotatedVerticalResolutionWorkaround(String name) {
if ("OMX.MTK.VIDEO.DECODER.HEVC".equals(name) && "mcv5a".equals(Util.DEVICE)) { if ("OMX.MTK.VIDEO.DECODER.HEVC".equals(name) && "mcv5a".equals(Util.DEVICE)) {
// See https://github.com/google/ExoPlayer/issues/6612. // See https://github.com/google/ExoPlayer/issues/6612.
return false; return false;
@ -876,6 +876,17 @@ public final class MediaCodecInfo {
&& ("sailfish".equals(Util.DEVICE) || "marlin".equals(Util.DEVICE)); && ("sailfish".equals(Util.DEVICE) || "marlin".equals(Util.DEVICE));
} }
/** Whether the device is known to have wrong {@link PerformancePoint} declarations. */
private static boolean needsIgnorePerformancePointsWorkaround() {
// See https://github.com/google/ExoPlayer/issues/10898 and [internal ref: b/267324685].
return /* Chromecast with Google TV */ Util.DEVICE.equals("sabrina")
|| Util.DEVICE.equals("boreal")
/* Lenovo Tablet M10 FHD Plus */
|| Util.MODEL.startsWith("Lenovo TB-X605")
|| Util.MODEL.startsWith("Lenovo TB-X606")
|| Util.MODEL.startsWith("Lenovo TB-X616");
}
/** Possible outcomes of evaluating PerformancePoint coverage */ /** Possible outcomes of evaluating PerformancePoint coverage */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -900,7 +911,9 @@ public final class MediaCodecInfo {
VideoCapabilities videoCapabilities, int width, int height, double frameRate) { VideoCapabilities videoCapabilities, int width, int height, double frameRate) {
List<PerformancePoint> performancePointList = List<PerformancePoint> performancePointList =
videoCapabilities.getSupportedPerformancePoints(); videoCapabilities.getSupportedPerformancePoints();
if (performancePointList == null || performancePointList.isEmpty()) { if (performancePointList == null
|| performancePointList.isEmpty()
|| needsIgnorePerformancePointsWorkaround()) {
return COVERAGE_RESULT_NO_EMPTY_LIST; return COVERAGE_RESULT_NO_EMPTY_LIST;
} }

View File

@ -208,10 +208,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000; private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000;
// Generally there is zero or one pending output stream offset. We track more offsets to allow for
// pending output streams that have fewer frames than the codec latency.
private static final int MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT = 10;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@ -303,12 +299,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final DecoderInputBuffer buffer; private final DecoderInputBuffer buffer;
private final DecoderInputBuffer bypassSampleBuffer; private final DecoderInputBuffer bypassSampleBuffer;
private final BatchBuffer bypassBatchBuffer; private final BatchBuffer bypassBatchBuffer;
private final TimedValueQueue<Format> formatQueue;
private final ArrayList<Long> decodeOnlyPresentationTimestamps; private final ArrayList<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo; private final MediaCodec.BufferInfo outputBufferInfo;
private final long[] pendingOutputStreamStartPositionsUs; private final ArrayDeque<OutputStreamInfo> pendingOutputStreamChanges;
private final long[] pendingOutputStreamOffsetsUs;
private final long[] pendingOutputStreamSwitchTimesUs;
@Nullable private Format inputFormat; @Nullable private Format inputFormat;
@Nullable private Format outputFormat; @Nullable private Format outputFormat;
@ -363,9 +356,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean pendingOutputEndOfStream; private boolean pendingOutputEndOfStream;
@Nullable private ExoPlaybackException pendingPlaybackException; @Nullable private ExoPlaybackException pendingPlaybackException;
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
private long outputStreamStartPositionUs; private OutputStreamInfo outputStreamInfo;
private long outputStreamOffsetUs; private long lastProcessedOutputBufferTimeUs;
private int pendingOutputStreamOffsetCount; private boolean needToNotifyOutputFormatChangeAfterStreamChange;
/** /**
* @param trackType The {@link C.TrackType track type} that the renderer handles. * @param trackType The {@link C.TrackType track type} that the renderer handles.
@ -392,17 +385,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
bypassBatchBuffer = new BatchBuffer(); bypassBatchBuffer = new BatchBuffer();
formatQueue = new TimedValueQueue<>();
decodeOnlyPresentationTimestamps = new ArrayList<>(); decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo(); outputBufferInfo = new MediaCodec.BufferInfo();
currentPlaybackSpeed = 1f; currentPlaybackSpeed = 1f;
targetPlaybackSpeed = 1f; targetPlaybackSpeed = 1f;
renderTimeLimitMs = C.TIME_UNSET; renderTimeLimitMs = C.TIME_UNSET;
pendingOutputStreamStartPositionsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; pendingOutputStreamChanges = new ArrayDeque<>();
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; setOutputStreamInfo(OutputStreamInfo.UNSET);
pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
outputStreamStartPositionUs = C.TIME_UNSET;
setOutputStreamOffsetUs(C.TIME_UNSET);
// MediaCodec outputs audio buffers in native endian: // MediaCodec outputs audio buffers in native endian:
// https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers // https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers
// and code called from MediaCodecAudioRenderer.processOutputBuffer expects this endianness. // and code called from MediaCodecAudioRenderer.processOutputBuffer expects this endianness.
@ -419,6 +408,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecHotswapDeadlineMs = C.TIME_UNSET; codecHotswapDeadlineMs = C.TIME_UNSET;
largestQueuedPresentationTimeUs = C.TIME_UNSET; largestQueuedPresentationTimeUs = C.TIME_UNSET;
lastBufferInStreamPresentationTimeUs = C.TIME_UNSET; lastBufferInStreamPresentationTimeUs = C.TIME_UNSET;
lastProcessedOutputBufferTimeUs = C.TIME_UNSET;
codecDrainState = DRAIN_STATE_NONE; codecDrainState = DRAIN_STATE_NONE;
codecDrainAction = DRAIN_ACTION_NONE; codecDrainAction = DRAIN_ACTION_NONE;
} }
@ -606,13 +596,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected final void updateOutputFormatForTime(long presentationTimeUs) protected final void updateOutputFormatForTime(long presentationTimeUs)
throws ExoPlaybackException { throws ExoPlaybackException {
boolean outputFormatChanged = false; boolean outputFormatChanged = false;
@Nullable Format format = formatQueue.pollFloor(presentationTimeUs); @Nullable Format format = outputStreamInfo.formatQueue.pollFloor(presentationTimeUs);
if (format == null && codecOutputMediaFormatChanged) { if (format == null
// If the codec's output MediaFormat has changed then there should be a corresponding Format && needToNotifyOutputFormatChangeAfterStreamChange
// change, which we've not found. Check the Format queue in case the corresponding && codecOutputMediaFormat != null) {
// presentation timestamp is greater than presentationTimeUs, which can happen for some codecs // After a stream change or after the initial start, there should be an input format change,
// [Internal ref: b/162719047]. // which we've not found. Check the Format queue in case the corresponding presentation
format = formatQueue.pollFirst(); // timestamp is greater than presentationTimeUs, which can happen for some codecs
// [Internal ref: b/162719047 and https://github.com/google/ExoPlayer/issues/8594].
format = outputStreamInfo.formatQueue.pollFirst();
} }
if (format != null) { if (format != null) {
outputFormat = format; outputFormat = format;
@ -621,6 +613,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (outputFormatChanged || (codecOutputMediaFormatChanged && outputFormat != null)) { if (outputFormatChanged || (codecOutputMediaFormatChanged && outputFormat != null)) {
onOutputFormatChanged(outputFormat, codecOutputMediaFormat); onOutputFormatChanged(outputFormat, codecOutputMediaFormat);
codecOutputMediaFormatChanged = false; codecOutputMediaFormatChanged = false;
needToNotifyOutputFormatChangeAfterStreamChange = false;
} }
} }
@ -648,23 +641,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
if (this.outputStreamOffsetUs == C.TIME_UNSET) { if (outputStreamInfo.streamOffsetUs == C.TIME_UNSET
checkState(this.outputStreamStartPositionUs == C.TIME_UNSET); || (pendingOutputStreamChanges.isEmpty()
this.outputStreamStartPositionUs = startPositionUs; && lastProcessedOutputBufferTimeUs != C.TIME_UNSET
setOutputStreamOffsetUs(offsetUs); && lastProcessedOutputBufferTimeUs >= largestQueuedPresentationTimeUs)) {
// This is the first stream, or the previous has been fully output already.
setOutputStreamInfo(
new OutputStreamInfo(
/* previousStreamLastBufferTimeUs= */ C.TIME_UNSET, startPositionUs, offsetUs));
} else { } else {
if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) { pendingOutputStreamChanges.add(
Log.w( new OutputStreamInfo(largestQueuedPresentationTimeUs, startPositionUs, offsetUs));
TAG,
"Too many stream changes, so dropping offset: "
+ pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]);
} else {
pendingOutputStreamOffsetCount++;
}
pendingOutputStreamStartPositionsUs[pendingOutputStreamOffsetCount - 1] = startPositionUs;
pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs;
pendingOutputStreamSwitchTimesUs[pendingOutputStreamOffsetCount - 1] =
largestQueuedPresentationTimeUs;
} }
} }
@ -683,16 +670,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// If there is a format change on the input side still pending propagation to the output, we // If there is a format change on the input side still pending propagation to the output, we
// need to queue a format next time a buffer is read. This is because we may not read a new // need to queue a format next time a buffer is read. This is because we may not read a new
// input format after the position reset. // input format after the position reset.
if (formatQueue.size() > 0) { if (outputStreamInfo.formatQueue.size() > 0) {
waitingForFirstSampleInFormat = true; waitingForFirstSampleInFormat = true;
} }
formatQueue.clear(); outputStreamInfo.formatQueue.clear();
if (pendingOutputStreamOffsetCount != 0) { pendingOutputStreamChanges.clear();
setOutputStreamOffsetUs(pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]);
outputStreamStartPositionUs =
pendingOutputStreamStartPositionsUs[pendingOutputStreamOffsetCount - 1];
pendingOutputStreamOffsetCount = 0;
}
} }
@Override @Override
@ -706,9 +688,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
protected void onDisabled() { protected void onDisabled() {
inputFormat = null; inputFormat = null;
outputStreamStartPositionUs = C.TIME_UNSET; setOutputStreamInfo(OutputStreamInfo.UNSET);
setOutputStreamOffsetUs(C.TIME_UNSET); pendingOutputStreamChanges.clear();
pendingOutputStreamOffsetCount = 0;
flushOrReleaseCodec(); flushOrReleaseCodec();
} }
@ -897,6 +878,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decodeOnlyPresentationTimestamps.clear(); decodeOnlyPresentationTimestamps.clear();
largestQueuedPresentationTimeUs = C.TIME_UNSET; largestQueuedPresentationTimeUs = C.TIME_UNSET;
lastBufferInStreamPresentationTimeUs = C.TIME_UNSET; lastBufferInStreamPresentationTimeUs = C.TIME_UNSET;
lastProcessedOutputBufferTimeUs = C.TIME_UNSET;
if (c2Mp3TimestampTracker != null) { if (c2Mp3TimestampTracker != null) {
c2Mp3TimestampTracker.reset(); c2Mp3TimestampTracker.reset();
} }
@ -1353,7 +1335,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decodeOnlyPresentationTimestamps.add(presentationTimeUs); decodeOnlyPresentationTimestamps.add(presentationTimeUs);
} }
if (waitingForFirstSampleInFormat) { if (waitingForFirstSampleInFormat) {
formatQueue.add(presentationTimeUs, inputFormat); if (!pendingOutputStreamChanges.isEmpty()) {
pendingOutputStreamChanges.peekLast().formatQueue.add(presentationTimeUs, inputFormat);
} else {
outputStreamInfo.formatQueue.add(presentationTimeUs, inputFormat);
}
waitingForFirstSampleInFormat = false; waitingForFirstSampleInFormat = false;
} }
largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs); largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs);
@ -1593,29 +1579,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
@CallSuper @CallSuper
protected void onProcessedOutputBuffer(long presentationTimeUs) { protected void onProcessedOutputBuffer(long presentationTimeUs) {
while (pendingOutputStreamOffsetCount != 0 lastProcessedOutputBufferTimeUs = presentationTimeUs;
&& presentationTimeUs >= pendingOutputStreamSwitchTimesUs[0]) { if (!pendingOutputStreamChanges.isEmpty()
outputStreamStartPositionUs = pendingOutputStreamStartPositionsUs[0]; && presentationTimeUs >= pendingOutputStreamChanges.peek().previousStreamLastBufferTimeUs) {
setOutputStreamOffsetUs(pendingOutputStreamOffsetsUs[0]); setOutputStreamInfo(pendingOutputStreamChanges.poll());
pendingOutputStreamOffsetCount--;
System.arraycopy(
pendingOutputStreamStartPositionsUs,
/* srcPos= */ 1,
pendingOutputStreamStartPositionsUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
System.arraycopy(
pendingOutputStreamOffsetsUs,
/* srcPos= */ 1,
pendingOutputStreamOffsetsUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
System.arraycopy(
pendingOutputStreamSwitchTimesUs,
/* srcPos= */ 1,
pendingOutputStreamSwitchTimesUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
onProcessedStreamChange(); onProcessedStreamChange();
} }
} }
@ -2062,13 +2029,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* boolean, Format)} to get the playback position with respect to the media. * boolean, Format)} to get the playback position with respect to the media.
*/ */
protected final long getOutputStreamOffsetUs() { protected final long getOutputStreamOffsetUs() {
return outputStreamOffsetUs; return outputStreamInfo.streamOffsetUs;
} }
private void setOutputStreamOffsetUs(long outputStreamOffsetUs) { private void setOutputStreamInfo(OutputStreamInfo outputStreamInfo) {
this.outputStreamOffsetUs = outputStreamOffsetUs; this.outputStreamInfo = outputStreamInfo;
if (outputStreamOffsetUs != C.TIME_UNSET) { if (outputStreamInfo.streamOffsetUs != C.TIME_UNSET) {
onOutputStreamOffsetUsChanged(outputStreamOffsetUs); needToNotifyOutputFormatChangeAfterStreamChange = true;
onOutputStreamOffsetUsChanged(outputStreamInfo.streamOffsetUs);
} }
} }
@ -2515,6 +2483,28 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name); && "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
} }
private static final class OutputStreamInfo {
public static final OutputStreamInfo UNSET =
new OutputStreamInfo(
/* previousStreamLastBufferTimeUs= */ C.TIME_UNSET,
/* startPositionUs= */ C.TIME_UNSET,
/* streamOffsetUs= */ C.TIME_UNSET);
public final long previousStreamLastBufferTimeUs;
public final long startPositionUs;
public final long streamOffsetUs;
public final TimedValueQueue<Format> formatQueue;
public OutputStreamInfo(
long previousStreamLastBufferTimeUs, long startPositionUs, long streamOffsetUs) {
this.previousStreamLastBufferTimeUs = previousStreamLastBufferTimeUs;
this.startPositionUs = startPositionUs;
this.streamOffsetUs = streamOffsetUs;
this.formatQueue = new TimedValueQueue<>();
}
}
@RequiresApi(31) @RequiresApi(31)
private static final class Api31 { private static final class Api31 {
private Api31() {} private Api31() {}

View File

@ -252,7 +252,7 @@ public final class MediaCodecUtil {
return getVp9ProfileAndLevel(format.codecs, parts); return getVp9ProfileAndLevel(format.codecs, parts);
case CODEC_ID_HEV1: case CODEC_ID_HEV1:
case CODEC_ID_HVC1: case CODEC_ID_HVC1:
return getHevcProfileAndLevel(format.codecs, parts); return getHevcProfileAndLevel(format.codecs, parts, format.colorInfo);
case CODEC_ID_AV01: case CODEC_ID_AV01:
return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo); return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo);
case CODEC_ID_MP4A: case CODEC_ID_MP4A:
@ -731,7 +731,8 @@ public final class MediaCodecUtil {
} }
@Nullable @Nullable
private static Pair<Integer, Integer> getHevcProfileAndLevel(String codec, String[] parts) { private static Pair<Integer, Integer> getHevcProfileAndLevel(
String codec, String[] parts, @Nullable ColorInfo colorInfo) {
if (parts.length < 4) { if (parts.length < 4) {
// The codec has fewer parts than required by the HEVC codec string format. // The codec has fewer parts than required by the HEVC codec string format.
Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec);
@ -748,7 +749,15 @@ public final class MediaCodecUtil {
if ("1".equals(profileString)) { if ("1".equals(profileString)) {
profile = CodecProfileLevel.HEVCProfileMain; profile = CodecProfileLevel.HEVCProfileMain;
} else if ("2".equals(profileString)) { } else if ("2".equals(profileString)) {
profile = CodecProfileLevel.HEVCProfileMain10; if (colorInfo != null && colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084) {
profile = CodecProfileLevel.HEVCProfileMain10HDR10;
} else {
// For all other cases, we map to the Main10 profile. Note that this includes HLG
// HDR. On Android 13+, the platform guarantees that a decoder that advertises
// HEVCProfileMain10 will be able to decode HLG. This is not guaranteed for older
// Android versions, but we still map to Main10 for backwards compatibility.
profile = CodecProfileLevel.HEVCProfileMain10;
}
} else { } else {
Log.w(TAG, "Unknown HEVC profile string: " + profileString); Log.w(TAG, "Unknown HEVC profile string: " + profileString);
return null; return null;

View File

@ -105,7 +105,7 @@ public class DefaultDownloaderFactory implements DownloaderFactory {
return constructor.newInstance(mediaItem, cacheDataSourceFactory, executor); return constructor.newInstance(mediaItem, cacheDataSourceFactory, executor);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException( throw new IllegalStateException(
"Failed to instantiate downloader for content type " + contentType); "Failed to instantiate downloader for content type " + contentType, e);
} }
} }

View File

@ -75,8 +75,9 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
} }
} }
public static final long DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_MS = 20 * C.MILLIS_PER_SECOND;
private static final int BUFFER_SIZE_BYTES = 128 * 1024; private static final int BUFFER_SIZE_BYTES = 128 * 1024;
private static final long MAX_MERGED_SEGMENT_START_TIME_DIFF_US = 20 * C.MICROS_PER_SECOND;
private final DataSpec manifestDataSpec; private final DataSpec manifestDataSpec;
private final Parser<M> manifestParser; private final Parser<M> manifestParser;
@ -86,6 +87,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
private final CacheKeyFactory cacheKeyFactory; private final CacheKeyFactory cacheKeyFactory;
@Nullable private final PriorityTaskManager priorityTaskManager; @Nullable private final PriorityTaskManager priorityTaskManager;
private final Executor executor; private final Executor executor;
private final long maxMergedSegmentStartTimeDiffUs;
/** /**
* The currently active runnables. * The currently active runnables.
@ -99,6 +101,24 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
private volatile boolean isCanceled; private volatile boolean isCanceled;
/**
* @deprecated Use {@link SegmentDownloader#SegmentDownloader(MediaItem, Parser,
* CacheDataSource.Factory, Executor, long)} instead.
*/
@Deprecated
public SegmentDownloader(
MediaItem mediaItem,
Parser<M> manifestParser,
CacheDataSource.Factory cacheDataSourceFactory,
Executor executor) {
this(
mediaItem,
manifestParser,
cacheDataSourceFactory,
executor,
DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_MS);
}
/** /**
* @param mediaItem The {@link MediaItem} to be downloaded. * @param mediaItem The {@link MediaItem} to be downloaded.
* @param manifestParser A parser for manifests belonging to the media to be downloaded. * @param manifestParser A parser for manifests belonging to the media to be downloaded.
@ -107,12 +127,16 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
* @param executor An {@link Executor} used to make requests for the media being downloaded. * @param executor An {@link Executor} used to make requests for the media being downloaded.
* Providing an {@link Executor} that uses multiple threads will speed up the download by * Providing an {@link Executor} that uses multiple threads will speed up the download by
* allowing parts of it to be executed in parallel. * allowing parts of it to be executed in parallel.
* @param maxMergedSegmentStartTimeDiffMs The maximum difference of the start time of two
* segments, up to which the segments (of the same URI) should be merged into a single
* download segment, in milliseconds.
*/ */
public SegmentDownloader( public SegmentDownloader(
MediaItem mediaItem, MediaItem mediaItem,
Parser<M> manifestParser, Parser<M> manifestParser,
CacheDataSource.Factory cacheDataSourceFactory, CacheDataSource.Factory cacheDataSourceFactory,
Executor executor) { Executor executor,
long maxMergedSegmentStartTimeDiffMs) {
checkNotNull(mediaItem.localConfiguration); checkNotNull(mediaItem.localConfiguration);
this.manifestDataSpec = getCompressibleDataSpec(mediaItem.localConfiguration.uri); this.manifestDataSpec = getCompressibleDataSpec(mediaItem.localConfiguration.uri);
this.manifestParser = manifestParser; this.manifestParser = manifestParser;
@ -123,6 +147,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
cacheKeyFactory = cacheDataSourceFactory.getCacheKeyFactory(); cacheKeyFactory = cacheDataSourceFactory.getCacheKeyFactory();
priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager(); priorityTaskManager = cacheDataSourceFactory.getUpstreamPriorityTaskManager();
activeRunnables = new ArrayList<>(); activeRunnables = new ArrayList<>();
maxMergedSegmentStartTimeDiffUs = Util.msToUs(maxMergedSegmentStartTimeDiffMs);
} }
@Override @Override
@ -145,7 +170,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
// Sort the segments so that we download media in the right order from the start of the // Sort the segments so that we download media in the right order from the start of the
// content, and merge segments where possible to minimize the number of server round trips. // content, and merge segments where possible to minimize the number of server round trips.
Collections.sort(segments); Collections.sort(segments);
mergeSegments(segments, cacheKeyFactory); mergeSegments(segments, cacheKeyFactory, maxMergedSegmentStartTimeDiffUs);
// Scan the segments, removing any that are fully downloaded. // Scan the segments, removing any that are fully downloaded.
int totalSegments = segments.size(); int totalSegments = segments.size();
@ -416,7 +441,8 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
} }
} }
private static void mergeSegments(List<Segment> segments, CacheKeyFactory keyFactory) { private static void mergeSegments(
List<Segment> segments, CacheKeyFactory keyFactory, long maxMergedSegmentStartTimeDiffUs) {
HashMap<String, Integer> lastIndexByCacheKey = new HashMap<>(); HashMap<String, Integer> lastIndexByCacheKey = new HashMap<>();
int nextOutIndex = 0; int nextOutIndex = 0;
for (int i = 0; i < segments.size(); i++) { for (int i = 0; i < segments.size(); i++) {
@ -425,7 +451,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
@Nullable Integer lastIndex = lastIndexByCacheKey.get(cacheKey); @Nullable Integer lastIndex = lastIndexByCacheKey.get(cacheKey);
@Nullable Segment lastSegment = lastIndex == null ? null : segments.get(lastIndex); @Nullable Segment lastSegment = lastIndex == null ? null : segments.get(lastIndex);
if (lastSegment == null if (lastSegment == null
|| segment.startTimeUs > lastSegment.startTimeUs + MAX_MERGED_SEGMENT_START_TIME_DIFF_US || segment.startTimeUs > lastSegment.startTimeUs + maxMergedSegmentStartTimeDiffUs
|| !canMergeSegments(lastSegment.dataSpec, segment.dataSpec)) { || !canMergeSegments(lastSegment.dataSpec, segment.dataSpec)) {
lastIndexByCacheKey.put(cacheKey, nextOutIndex); lastIndexByCacheKey.put(cacheKey, nextOutIndex);
segments.set(nextOutIndex, segment); segments.set(nextOutIndex, segment);

View File

@ -184,8 +184,8 @@ public final class Requirements implements Parcelable {
private boolean isDeviceCharging(Context context) { private boolean isDeviceCharging(Context context) {
@Nullable @Nullable
Intent batteryStatus = Intent batteryStatus =
Util.registerReceiverNotExported( context.registerReceiver(
context, /* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); /* receiver= */ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batteryStatus == null) { if (batteryStatus == null) {
return false; return false;
} }
@ -203,8 +203,8 @@ public final class Requirements implements Parcelable {
} }
private boolean isStorageNotLow(Context context) { private boolean isStorageNotLow(Context context) {
return Util.registerReceiverNotExported( return context.registerReceiver(
context, /* receiver= */ null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)) /* receiver= */ null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW))
== null; == null;
} }

View File

@ -111,7 +111,7 @@ public final class RequirementsWatcher {
filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
} }
receiver = new DeviceStatusChangeReceiver(); receiver = new DeviceStatusChangeReceiver();
Util.registerReceiverNotExported(context, receiver, filter, handler); context.registerReceiver(receiver, filter, /* broadcastPermission= */ null, handler);
return notMetRequirements; return notMetRequirements;
} }

View File

@ -30,8 +30,12 @@ import java.util.Comparator;
* rate observations. This is an alternative to sliding mean and exponential averaging which suffer * rate observations. This is an alternative to sliding mean and exponential averaging which suffer
* from susceptibility to outliers and slow adaptation to step functions. * from susceptibility to outliers and slow adaptation to step functions.
* *
* @see <a href="http://en.wikipedia.org/wiki/Moving_average">Wiki: Moving average</a> * <p>See the following Wikipedia articles:
* @see <a href="http://en.wikipedia.org/wiki/Selection_algorithm">Wiki: Selection algorithm</a> *
* <ul>
* <li><a href="http://en.wikipedia.org/wiki/Moving_average">Moving average</a>
* <li><a href="http://en.wikipedia.org/wiki/Selection_algorithm">Selection algorithm</a>
* </ul>
*/ */
@UnstableApi @UnstableApi
public class SlidingPercentile { public class SlidingPercentile {

View File

@ -153,6 +153,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private long lastRenderRealtimeUs; private long lastRenderRealtimeUs;
private long totalVideoFrameProcessingOffsetUs; private long totalVideoFrameProcessingOffsetUs;
private int videoFrameProcessingOffsetCount; private int videoFrameProcessingOffsetCount;
private long lastFrameReleaseTimeNs;
private int currentWidth; private int currentWidth;
private int currentHeight; private int currentHeight;
@ -1128,9 +1129,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
if (Util.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
// Let the underlying framework time the release. // Let the underlying framework time the release.
if (earlyUs < 50000) { if (earlyUs < 50000) {
notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format); if (adjustedReleaseTimeNs == lastFrameReleaseTimeNs) {
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs); // This frame should be displayed on the same vsync with the previous released frame. We
// are likely rendering frames at a rate higher than the screen refresh rate. Skip
// this buffer so that it's returned to MediaCodec sooner otherwise MediaCodec may not
// be able to keep decoding with this rate [b/263454203].
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
} else {
notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format);
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs);
}
updateVideoFrameProcessingOffsetCounters(earlyUs); updateVideoFrameProcessingOffsetCounters(earlyUs);
lastFrameReleaseTimeNs = adjustedReleaseTimeNs;
return true; return true;
} }
} else { } else {

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Aflaai</string> <string name="exo_download_description">Aflaai</string>
<string name="exo_download_notification_channel_name">Aflaaie</string> <string name="exo_download_notification_channel_name">Aflaaie</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">አውርድ</string> <string name="exo_download_description">አውርድ</string>
<string name="exo_download_notification_channel_name">የወረዱ</string> <string name="exo_download_notification_channel_name">የወረዱ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">تنزيل</string> <string name="exo_download_description">تنزيل</string>
<string name="exo_download_notification_channel_name">عمليات التنزيل</string> <string name="exo_download_notification_channel_name">عمليات التنزيل</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Endirin</string> <string name="exo_download_description">Endirin</string>
<string name="exo_download_notification_channel_name">Endirmələr</string> <string name="exo_download_notification_channel_name">Endirmələr</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Preuzmi</string> <string name="exo_download_description">Preuzmi</string>
<string name="exo_download_notification_channel_name">Preuzimanja</string> <string name="exo_download_notification_channel_name">Preuzimanja</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Спампаваць</string> <string name="exo_download_description">Спампаваць</string>
<string name="exo_download_notification_channel_name">Спампоўкі</string> <string name="exo_download_notification_channel_name">Спампоўкі</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Изтегляне</string> <string name="exo_download_description">Изтегляне</string>
<string name="exo_download_notification_channel_name">Изтегляния</string> <string name="exo_download_notification_channel_name">Изтегляния</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ডাউনলোড করুন</string> <string name="exo_download_description">ডাউনলোড করুন</string>
<string name="exo_download_notification_channel_name">ডাউনলোড</string> <string name="exo_download_notification_channel_name">ডাউনলোড</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Preuzmi</string> <string name="exo_download_description">Preuzmi</string>
<string name="exo_download_notification_channel_name">Preuzimanja</string> <string name="exo_download_notification_channel_name">Preuzimanja</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Baixa</string> <string name="exo_download_description">Baixa</string>
<string name="exo_download_notification_channel_name">Baixades</string> <string name="exo_download_notification_channel_name">Baixades</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Stáhnout</string> <string name="exo_download_description">Stáhnout</string>
<string name="exo_download_notification_channel_name">Stahování</string> <string name="exo_download_notification_channel_name">Stahování</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Download</string> <string name="exo_download_description">Download</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Herunterladen</string> <string name="exo_download_description">Herunterladen</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Λήψη</string> <string name="exo_download_description">Λήψη</string>
<string name="exo_download_notification_channel_name">Λήψεις</string> <string name="exo_download_notification_channel_name">Λήψεις</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Download</string> <string name="exo_download_description">Download</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Download</string> <string name="exo_download_description">Download</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Download</string> <string name="exo_download_description">Download</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Descargar</string> <string name="exo_download_description">Descargar</string>
<string name="exo_download_notification_channel_name">Descargas</string> <string name="exo_download_notification_channel_name">Descargas</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Descargar</string> <string name="exo_download_description">Descargar</string>
<string name="exo_download_notification_channel_name">Descargas</string> <string name="exo_download_notification_channel_name">Descargas</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Allalaadimine</string> <string name="exo_download_description">Allalaadimine</string>
<string name="exo_download_notification_channel_name">Allalaadimised</string> <string name="exo_download_notification_channel_name">Allalaadimised</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Deskargak</string> <string name="exo_download_description">Deskargak</string>
<string name="exo_download_notification_channel_name">Deskargak</string> <string name="exo_download_notification_channel_name">Deskargak</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">بارگیری</string> <string name="exo_download_description">بارگیری</string>
<string name="exo_download_notification_channel_name">بارگیری‌ها</string> <string name="exo_download_notification_channel_name">بارگیری‌ها</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Lataa</string> <string name="exo_download_description">Lataa</string>
<string name="exo_download_notification_channel_name">Lataukset</string> <string name="exo_download_notification_channel_name">Lataukset</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Télécharger</string> <string name="exo_download_description">Télécharger</string>
<string name="exo_download_notification_channel_name">Téléchargements</string> <string name="exo_download_notification_channel_name">Téléchargements</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Télécharger</string> <string name="exo_download_description">Télécharger</string>
<string name="exo_download_notification_channel_name">Téléchargements</string> <string name="exo_download_notification_channel_name">Téléchargements</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Descargar</string> <string name="exo_download_description">Descargar</string>
<string name="exo_download_notification_channel_name">Descargas</string> <string name="exo_download_notification_channel_name">Descargas</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ડાઉનલોડ કરો</string> <string name="exo_download_description">ડાઉનલોડ કરો</string>
<string name="exo_download_notification_channel_name">ડાઉનલોડ</string> <string name="exo_download_notification_channel_name">ડાઉનલોડ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">डाउनलोड करें</string> <string name="exo_download_description">डाउनलोड करें</string>
<string name="exo_download_notification_channel_name">डाउनलोड की गई मीडिया फ़ाइलें</string> <string name="exo_download_notification_channel_name">डाउनलोड की गई मीडिया फ़ाइलें</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Preuzmi</string> <string name="exo_download_description">Preuzmi</string>
<string name="exo_download_notification_channel_name">Preuzimanja</string> <string name="exo_download_notification_channel_name">Preuzimanja</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Letöltés</string> <string name="exo_download_description">Letöltés</string>
<string name="exo_download_notification_channel_name">Letöltések</string> <string name="exo_download_notification_channel_name">Letöltések</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Ներբեռնել</string> <string name="exo_download_description">Ներբեռնել</string>
<string name="exo_download_notification_channel_name">Ներբեռնումներ</string> <string name="exo_download_notification_channel_name">Ներբեռնումներ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Download</string> <string name="exo_download_description">Download</string>
<string name="exo_download_notification_channel_name">Download</string> <string name="exo_download_notification_channel_name">Download</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Sækja</string> <string name="exo_download_description">Sækja</string>
<string name="exo_download_notification_channel_name">Niðurhal</string> <string name="exo_download_notification_channel_name">Niðurhal</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Scarica</string> <string name="exo_download_description">Scarica</string>
<string name="exo_download_notification_channel_name">Download</string> <string name="exo_download_notification_channel_name">Download</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">הורדה</string> <string name="exo_download_description">הורדה</string>
<string name="exo_download_notification_channel_name">הורדות</string> <string name="exo_download_notification_channel_name">הורדות</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ダウンロード</string> <string name="exo_download_description">ダウンロード</string>
<string name="exo_download_notification_channel_name">ダウンロード</string> <string name="exo_download_notification_channel_name">ダウンロード</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ჩამოტვირთვა</string> <string name="exo_download_description">ჩამოტვირთვა</string>
<string name="exo_download_notification_channel_name">ჩამოტვირთვები</string> <string name="exo_download_notification_channel_name">ჩამოტვირთვები</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Жүктеп алу</string> <string name="exo_download_description">Жүктеп алу</string>
<string name="exo_download_notification_channel_name">Жүктеп алынғандар</string> <string name="exo_download_notification_channel_name">Жүктеп алынғандар</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ទាញយក</string> <string name="exo_download_description">ទាញយក</string>
<string name="exo_download_notification_channel_name">ទាញយក</string> <string name="exo_download_notification_channel_name">ទាញយក</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ಡೌನ್‌ಲೋಡ್‌</string> <string name="exo_download_description">ಡೌನ್‌ಲೋಡ್‌</string>
<string name="exo_download_notification_channel_name">ಡೌನ್‌ಲೋಡ್‌ಗಳು</string> <string name="exo_download_notification_channel_name">ಡೌನ್‌ಲೋಡ್‌ಗಳು</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">다운로드</string> <string name="exo_download_description">다운로드</string>
<string name="exo_download_notification_channel_name">다운로드</string> <string name="exo_download_notification_channel_name">다운로드</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Жүктөп алуу</string> <string name="exo_download_description">Жүктөп алуу</string>
<string name="exo_download_notification_channel_name">Жүктөлүп алынгандар</string> <string name="exo_download_notification_channel_name">Жүктөлүп алынгандар</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ດາວໂຫລດ</string> <string name="exo_download_description">ດາວໂຫລດ</string>
<string name="exo_download_notification_channel_name">ດາວໂຫລດ</string> <string name="exo_download_notification_channel_name">ດາວໂຫລດ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Atsisiųsti</string> <string name="exo_download_description">Atsisiųsti</string>
<string name="exo_download_notification_channel_name">Atsisiuntimai</string> <string name="exo_download_notification_channel_name">Atsisiuntimai</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Lejupielādēt</string> <string name="exo_download_description">Lejupielādēt</string>
<string name="exo_download_notification_channel_name">Lejupielādes</string> <string name="exo_download_notification_channel_name">Lejupielādes</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Преземи</string> <string name="exo_download_description">Преземи</string>
<string name="exo_download_notification_channel_name">Преземања</string> <string name="exo_download_notification_channel_name">Преземања</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ഡൗൺലോഡ്</string> <string name="exo_download_description">ഡൗൺലോഡ്</string>
<string name="exo_download_notification_channel_name">ഡൗൺലോഡുകൾ</string> <string name="exo_download_notification_channel_name">ഡൗൺലോഡുകൾ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Татах</string> <string name="exo_download_description">Татах</string>
<string name="exo_download_notification_channel_name">Татaлт</string> <string name="exo_download_notification_channel_name">Татaлт</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">डाउनलोड करा</string> <string name="exo_download_description">डाउनलोड करा</string>
<string name="exo_download_notification_channel_name">डाउनलोड</string> <string name="exo_download_notification_channel_name">डाउनलोड</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Muat turun</string> <string name="exo_download_description">Muat turun</string>
<string name="exo_download_notification_channel_name">Muat turun</string> <string name="exo_download_notification_channel_name">Muat turun</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ဒေါင်းလုဒ် လုပ်ရန်</string> <string name="exo_download_description">ဒေါင်းလုဒ် လုပ်ရန်</string>
<string name="exo_download_notification_channel_name">ဒေါင်းလုဒ်များ</string> <string name="exo_download_notification_channel_name">ဒေါင်းလုဒ်များ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Last ned</string> <string name="exo_download_description">Last ned</string>
<string name="exo_download_notification_channel_name">Nedlastinger</string> <string name="exo_download_notification_channel_name">Nedlastinger</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">डाउनलोड गर्नुहोस्</string> <string name="exo_download_description">डाउनलोड गर्नुहोस्</string>
<string name="exo_download_notification_channel_name">डाउनलोडहरू</string> <string name="exo_download_notification_channel_name">डाउनलोडहरू</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Downloaden</string> <string name="exo_download_description">Downloaden</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ਡਾਊਨਲੋਡ ਕਰੋ</string> <string name="exo_download_description">ਡਾਊਨਲੋਡ ਕਰੋ</string>
<string name="exo_download_notification_channel_name">ਡਾਊਨਲੋਡ</string> <string name="exo_download_notification_channel_name">ਡਾਊਨਲੋਡ</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Pobierz</string> <string name="exo_download_description">Pobierz</string>
<string name="exo_download_notification_channel_name">Pobieranie</string> <string name="exo_download_notification_channel_name">Pobieranie</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Transferir</string> <string name="exo_download_description">Transferir</string>
<string name="exo_download_notification_channel_name">Transferências</string> <string name="exo_download_notification_channel_name">Transferências</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Fazer o download</string> <string name="exo_download_description">Fazer o download</string>
<string name="exo_download_notification_channel_name">Downloads</string> <string name="exo_download_notification_channel_name">Downloads</string>

View File

@ -1,20 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Descărcați</string> <string name="exo_download_description">Descarcă</string>
<string name="exo_download_notification_channel_name">Descărcări</string> <string name="exo_download_notification_channel_name">Descărcări</string>
<string name="exo_download_downloading">Se descarcă</string> <string name="exo_download_downloading">Se descarcă</string>
<string name="exo_download_completed">Descărcarea a fost finalizată</string> <string name="exo_download_completed">Descărcarea a fost finalizată</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Скачать</string> <string name="exo_download_description">Скачать</string>
<string name="exo_download_notification_channel_name">Скачивания</string> <string name="exo_download_notification_channel_name">Скачивания</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">බාගන්න</string> <string name="exo_download_description">බාගන්න</string>
<string name="exo_download_notification_channel_name">බාගැනීම්</string> <string name="exo_download_notification_channel_name">බාගැනීම්</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Stiahnuť</string> <string name="exo_download_description">Stiahnuť</string>
<string name="exo_download_notification_channel_name">Stiahnuté</string> <string name="exo_download_notification_channel_name">Stiahnuté</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Prenos</string> <string name="exo_download_description">Prenos</string>
<string name="exo_download_notification_channel_name">Prenosi</string> <string name="exo_download_notification_channel_name">Prenosi</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Shkarko</string> <string name="exo_download_description">Shkarko</string>
<string name="exo_download_notification_channel_name">Shkarkimet</string> <string name="exo_download_notification_channel_name">Shkarkimet</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Преузми</string> <string name="exo_download_description">Преузми</string>
<string name="exo_download_notification_channel_name">Преузимања</string> <string name="exo_download_notification_channel_name">Преузимања</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Ladda ned</string> <string name="exo_download_description">Ladda ned</string>
<string name="exo_download_notification_channel_name">Nedladdningar</string> <string name="exo_download_notification_channel_name">Nedladdningar</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">Pakua</string> <string name="exo_download_description">Pakua</string>
<string name="exo_download_notification_channel_name">Vipakuliwa</string> <string name="exo_download_notification_channel_name">Vipakuliwa</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">பதிவிறக்கும் பட்டன்</string> <string name="exo_download_description">பதிவிறக்கும் பட்டன்</string>
<string name="exo_download_notification_channel_name">பதிவிறக்கங்கள்</string> <string name="exo_download_notification_channel_name">பதிவிறக்கங்கள்</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">డౌన్‌లోడ్ చేయి</string> <string name="exo_download_description">డౌన్‌లోడ్ చేయి</string>
<string name="exo_download_notification_channel_name">డౌన్‌లోడ్‌లు</string> <string name="exo_download_notification_channel_name">డౌన్‌లోడ్‌లు</string>

View File

@ -1,18 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="exo_download_description">ดาวน์โหลด</string> <string name="exo_download_description">ดาวน์โหลด</string>
<string name="exo_download_notification_channel_name">ดาวน์โหลด</string> <string name="exo_download_notification_channel_name">ดาวน์โหลด</string>

Some files were not shown because too many files have changed in this diff Show More