Commit d709df8e by Justin Yorke

Merge remote-tracking branch 'upstream/dev-v2' into dev-v2

parents 3f3191bb d9d13630
Showing with 806 additions and 1493 deletions
...@@ -2,19 +2,19 @@ ...@@ -2,19 +2,19 @@
### dev-v2 (not yet released) ### ### dev-v2 (not yet released) ###
* Optimize seeking in FMP4 by enabling seeking to the nearest sync sample within * Coming soon...
a fragment. This benefits standalone FMP4 playbacks, DASH and SmoothStreaming.
* Moved initial bitrate estimate from `AdaptiveTrackSelection` to ### 2.8.0 ###
`DefaultBandwidthMeter`.
* Updated default max buffer length in `DefaultLoadControl`. * Downloading:
* Added `AnalyticsListener` interface which can be registered in * Add `DownloadService`, `DownloadManager` and related classes
`SimpleExoPlayer` to receive detailed meta data for each ExoPlayer event. ([#2643](https://github.com/google/ExoPlayer/issues/2643)). Information on
* UI components: using these components to download progressive formats can be found
* Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update [here](https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95).
([#3736](https://github.com/google/ExoPlayer/issues/3736)). To see how to download DASH, HLS and SmoothStreaming media, take a look at
* Add PlayerNotificationManager. the app.
* Downloading: Add `DownloadService`, `DownloadManager` and * Updated main demo app to support downloading DASH, HLS, SmoothStreaming and
related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)). progressive media.
* MediaSources: * MediaSources:
* Allow reusing media sources after they have been released and * Allow reusing media sources after they have been released and
also in parallel to allow adding them multiple times to a concatenation. also in parallel to allow adding them multiple times to a concatenation.
...@@ -31,9 +31,23 @@ ...@@ -31,9 +31,23 @@
* Support live stream clipping with `ClippingMediaSource`. * Support live stream clipping with `ClippingMediaSource`.
* Allow setting tags for all media sources in their factories. The tag of the * Allow setting tags for all media sources in their factories. The tag of the
current window can be retrieved with `ExoPlayer.getCurrentTag`. current window can be retrieved with `ExoPlayer.getCurrentTag`.
* IMA: Allow setting the ad media load timeout * UI components:
([#3691](https://github.com/google/ExoPlayer/issues/3691)). * Add support for displaying error messages and a buffering spinner in
`PlayerView`.
* Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update
([#3736](https://github.com/google/ExoPlayer/issues/3736)).
* Add `PlayerNotificationManager` for displaying notifications reflecting the
player state.
* Add `TrackSelectionView` for selecting tracks with `DefaultTrackSelector`.
* Add `TrackNameProvider` for converting track `Format`s to textual
descriptions, and `DefaultTrackNameProvider` as a default implementation.
* Track selection:
* Reworked `MappingTrackSelector` and `DefaultTrackSelector`.
* `DefaultTrackSelector.Parameters` now implements `Parcelable`.
* Added UI components for track selection (see above).
* Audio: * Audio:
* Support extracting data from AMR container formats, including both narrow
and wide band ([#2527](https://github.com/google/ExoPlayer/issues/2527)).
* FLAC: * FLAC:
* Sniff FLAC files correctly if they have ID3 headers * Sniff FLAC files correctly if they have ID3 headers
([#4055](https://github.com/google/ExoPlayer/issues/4055)). ([#4055](https://github.com/google/ExoPlayer/issues/4055)).
...@@ -48,14 +62,17 @@ ...@@ -48,14 +62,17 @@
* Fix an issue where playback of TrueHD streams would get stuck after seeking * Fix an issue where playback of TrueHD streams would get stuck after seeking
due to not finding a syncframe due to not finding a syncframe
((#3845)[https://github.com/google/ExoPlayer/issues/3845]). ((#3845)[https://github.com/google/ExoPlayer/issues/3845]).
* Fix an issue with eac3-joc playback where a codec would fail to configure
((#4165)[https://github.com/google/ExoPlayer/issues/4165]).
* Handle non-empty end-of-stream buffers, to fix gapless playback of streams * Handle non-empty end-of-stream buffers, to fix gapless playback of streams
with encoder padding when the decoder returns a non-empty final buffer. with encoder padding when the decoder returns a non-empty final buffer.
* Allow trimming more than one sample when applying an elst audio edit via * Allow trimming more than one sample when applying an elst audio edit via
gapless playback info. gapless playback info.
* Allow overriding skipping/scaling with custom `AudioProcessor`s
((#3142)[https://github.com/google/ExoPlayer/issues/3142]).
* Caching: * Caching:
* Add release method to Cache interface. * Add release method to the `Cache` interface, and prevent multiple instances
* Prevent multiple instances of SimpleCache in the same folder. of `SimpleCache` using the same folder at the same time.
Previous instance must be released.
* Cache redirect URLs * Cache redirect URLs
([#2360](https://github.com/google/ExoPlayer/issues/2360)). ([#2360](https://github.com/google/ExoPlayer/issues/2360)).
* DRM: * DRM:
...@@ -65,17 +82,41 @@ ...@@ -65,17 +82,41 @@
([#4022][https://github.com/google/ExoPlayer/issues/4022]). ([#4022][https://github.com/google/ExoPlayer/issues/4022]).
* Fix handling of 307/308 redirects when making license requests * Fix handling of 307/308 redirects when making license requests
([#4108](https://github.com/google/ExoPlayer/issues/4108)). ([#4108](https://github.com/google/ExoPlayer/issues/4108)).
* HLS: Fix playlist loading error propagation when the current selection does * HLS:
* Fix playlist loading error propagation when the current selection does
not include all of the playlist's variants. not include all of the playlist's variants.
* Fix SAMPLE-AES-CENC and SAMPLE-AES-CTR EXT-X-KEY methods
([#4145](https://github.com/google/ExoPlayer/issues/4145)).
* Preeptively declare an ID3 track in chunkless preparation
([#4016](https://github.com/google/ExoPlayer/issues/4016)).
* Add support for multiple #EXT-X-MAP tags in a media playlist
([#4164](https://github.com/google/ExoPlayer/issues/4182)).
* Fix seeking in live streams
([#4187](https://github.com/google/ExoPlayer/issues/4187)).
* IMA:
* Allow setting the ad media load timeout
([#3691](https://github.com/google/ExoPlayer/issues/3691)).
* Expose ad load errors via `MediaSourceEventListener` on `AdsMediaSource`,
and allow setting an ad event listener on `ImaAdsLoader`. Deprecate the
`AdsMediaSource.EventListener`.
* Add `AnalyticsListener` interface which can be registered in
`SimpleExoPlayer` to receive detailed metadata for each ExoPlayer event.
* Optimize seeking in FMP4 by enabling seeking to the nearest sync sample within
a fragment. This benefits standalone FMP4 playbacks, DASH and SmoothStreaming.
* Updated default max buffer length in `DefaultLoadControl`.
* Fix ClearKey decryption error if the key contains a forward slash * Fix ClearKey decryption error if the key contains a forward slash
([#4075](https://github.com/google/ExoPlayer/issues/4075)). ([#4075](https://github.com/google/ExoPlayer/issues/4075)).
* Fix crash when switching surface on Huawei P9 Lite * Fix crash when switching surface on Huawei P9 Lite
([#4084](https://github.com/google/ExoPlayer/issues/4084)), and Philips QM163E ([#4084](https://github.com/google/ExoPlayer/issues/4084)), and Philips QM163E
([#4104](https://github.com/google/ExoPlayer/issues/4104)). ([#4104](https://github.com/google/ExoPlayer/issues/4104)).
* Support ZLIB compressed PGS subtitles. * Support ZLIB compressed PGS subtitles.
* Added `getPlaybackError` to `Player` interface.
* Moved initial bitrate estimate from `AdaptiveTrackSelection` to
`DefaultBandwidthMeter`.
* Removed default renderer time offset of 60000000 from internal player. The * Removed default renderer time offset of 60000000 from internal player. The
actual renderer timestamp offset can be obtained by listening to actual renderer timestamp offset can be obtained by listening to
`BaseRenderer.onStreamChanged`. `BaseRenderer.onStreamChanged`.
* Added dependencies on checkerframework annotations for static code analysis.
### 2.7.3 ### ### 2.7.3 ###
...@@ -240,6 +281,7 @@ ...@@ -240,6 +281,7 @@
([#3792](https://github.com/google/ExoPlayer/issues/3792). ([#3792](https://github.com/google/ExoPlayer/issues/3792).
* Support 14-bit mode and little endianness in DTS PES packets * Support 14-bit mode and little endianness in DTS PES packets
([#3340](https://github.com/google/ExoPlayer/issues/3340)). ([#3340](https://github.com/google/ExoPlayer/issues/3340)).
* Demo app: Add ability to download not DRM protected content.
### 2.6.1 ### ### 2.6.1 ###
......
<?xml version="1.0"?> <!-- Copyright (C) 2018 The Android Open Source Project
<!--
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
...@@ -14,8 +12,8 @@ ...@@ -14,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.
--> -->
<resources> <lint>
<string name="exo_media_action_repeat_all_description">"Ponovite sve"</string> <issue id="InvalidPackage">
<string name="exo_media_action_repeat_off_description">"Ne ponavljaju"</string> <ignore path="**/checker-qual-*.jar"/>
<string name="exo_media_action_repeat_one_description">"Ponovite jedan"</string> </issue>
</resources> </lint>
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. // ExoPlayer version and version code.
releaseVersion = '2.7.3' releaseVersion = '2.8.0'
releaseVersionCode = 2703 releaseVersionCode = 2800
// Important: ExoPlayer specifies a minSdkVersion of 14 because various // Important: ExoPlayer specifies a minSdkVersion of 14 because various
// components provided by the library may be of use on older devices. // components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided // However, please note that the core media playback functionality provided
...@@ -31,6 +31,8 @@ project.ext { ...@@ -31,6 +31,8 @@ project.ext {
junitVersion = '4.12' junitVersion = '4.12'
truthVersion = '0.39' truthVersion = '0.39'
robolectricVersion = '3.7.1' robolectricVersion = '3.7.1'
autoValueVersion = '1.6'
checkerframeworkVersion = '2.5.0'
modulePrefix = ':' modulePrefix = ':'
if (gradle.ext.has('exoplayerModulePrefix')) { if (gradle.ext.has('exoplayerModulePrefix')) {
modulePrefix += gradle.ext.exoplayerModulePrefix modulePrefix += gradle.ext.exoplayerModulePrefix
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-feature android:name="android.software.leanback" android:required="false"/> <uses-feature android:name="android.software.leanback" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-sdk/> <uses-sdk/>
...@@ -73,6 +75,18 @@ ...@@ -73,6 +75,18 @@
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name="com.google.android.exoplayer2.demo.DemoDownloadService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.exoplayer.downloadService.action.INIT"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
<service android:name="com.google.android.exoplayer2.scheduler.PlatformScheduler$PlatformSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
</application> </application>
</manifest> </manifest>
...@@ -16,6 +16,13 @@ ...@@ -16,6 +16,13 @@
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import android.app.Application; import android.app.Application;
import com.google.android.exoplayer2.offline.DownloadAction.Deserializer;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.ProgressiveDownloadAction;
import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction;
import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction;
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
...@@ -35,10 +42,24 @@ import java.io.File; ...@@ -35,10 +42,24 @@ import java.io.File;
*/ */
public class DemoApplication extends Application { public class DemoApplication extends Application {
private static final String DOWNLOAD_CACHE_FOLDER = "downloads"; private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2;
private static final Deserializer[] DOWNLOAD_DESERIALIZERS =
new Deserializer[] {
DashDownloadAction.DESERIALIZER,
HlsDownloadAction.DESERIALIZER,
SsDownloadAction.DESERIALIZER,
ProgressiveDownloadAction.DESERIALIZER
};
protected String userAgent; protected String userAgent;
private File downloadDirectory;
private Cache downloadCache; private Cache downloadCache;
private DownloadManager downloadManager;
private DownloadTracker downloadTracker;
@Override @Override
public void onCreate() { public void onCreate() {
...@@ -50,7 +71,7 @@ public class DemoApplication extends Application { ...@@ -50,7 +71,7 @@ public class DemoApplication extends Application {
public DataSource.Factory buildDataSourceFactory(TransferListener<? super DataSource> listener) { public DataSource.Factory buildDataSourceFactory(TransferListener<? super DataSource> listener) {
DefaultDataSourceFactory upstreamFactory = DefaultDataSourceFactory upstreamFactory =
new DefaultDataSourceFactory(this, listener, buildHttpDataSourceFactory(listener)); new DefaultDataSourceFactory(this, listener, buildHttpDataSourceFactory(listener));
return createReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache());
} }
/** Returns a {@link HttpDataSource.Factory}. */ /** Returns a {@link HttpDataSource.Factory}. */
...@@ -59,31 +80,69 @@ public class DemoApplication extends Application { ...@@ -59,31 +80,69 @@ public class DemoApplication extends Application {
return new DefaultHttpDataSourceFactory(userAgent, listener); return new DefaultHttpDataSourceFactory(userAgent, listener);
} }
/** Returns the download {@link Cache}. */ /** Returns whether extension renderers should be used. */
public Cache getDownloadCache() { public boolean useExtensionRenderers() {
if (downloadCache == null) { return "withExtensions".equals(BuildConfig.FLAVOR);
File dir = getExternalFilesDir(null);
if (dir == null) {
dir = getFilesDir();
} }
File downloadCacheFolder = new File(dir, DOWNLOAD_CACHE_FOLDER);
downloadCache = new SimpleCache(downloadCacheFolder, new NoOpCacheEvictor()); public DownloadManager getDownloadManager() {
initDownloadManager();
return downloadManager;
}
public DownloadTracker getDownloadTracker() {
initDownloadManager();
return downloadTracker;
}
private synchronized void initDownloadManager() {
if (downloadManager == null) {
DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(
getDownloadCache(), buildHttpDataSourceFactory(/* listener= */ null));
downloadManager =
new DownloadManager(
downloaderConstructorHelper,
MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT,
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
DOWNLOAD_DESERIALIZERS);
downloadTracker =
new DownloadTracker(
/* context= */ this,
buildDataSourceFactory(/* listener= */ null),
new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE),
DOWNLOAD_DESERIALIZERS);
downloadManager.addListener(downloadTracker);
}
}
private synchronized Cache getDownloadCache() {
if (downloadCache == null) {
File downloadContentDirectory = new File(getDownloadDirectory(), DOWNLOAD_CONTENT_DIRECTORY);
downloadCache = new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor());
} }
return downloadCache; return downloadCache;
} }
public boolean useExtensionRenderers() { private File getDownloadDirectory() {
return "withExtensions".equals(BuildConfig.FLAVOR); if (downloadDirectory == null) {
downloadDirectory = getExternalFilesDir(null);
if (downloadDirectory == null) {
downloadDirectory = getFilesDir();
}
}
return downloadDirectory;
} }
private static CacheDataSourceFactory createReadOnlyCacheDataSource( private static CacheDataSourceFactory buildReadOnlyCacheDataSource(
DefaultDataSourceFactory upstreamFactory, Cache cache) { DefaultDataSourceFactory upstreamFactory, Cache cache) {
return new CacheDataSourceFactory( return new CacheDataSourceFactory(
cache, cache,
upstreamFactory, upstreamFactory,
new FileDataSourceFactory(), new FileDataSourceFactory(),
/*cacheWriteDataSinkFactory=*/ null, /* cacheWriteDataSinkFactory= */ null,
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
/*eventListener=*/ null); /* eventListener= */ null);
} }
} }
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.demo;
import android.app.Notification;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadManager.TaskState;
import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.scheduler.PlatformScheduler;
import com.google.android.exoplayer2.ui.DownloadNotificationUtil;
import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util;
/** A service for downloading media. */
public class DemoDownloadService extends DownloadService {
private static final String CHANNEL_ID = "download_channel";
private static final int JOB_ID = 1;
private static final int FOREGROUND_NOTIFICATION_ID = 1;
public DemoDownloadService() {
super(
FOREGROUND_NOTIFICATION_ID,
DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
CHANNEL_ID,
R.string.exo_download_notification_channel_name);
}
@Override
protected DownloadManager getDownloadManager() {
return ((DemoApplication) getApplication()).getDownloadManager();
}
@Override
protected PlatformScheduler getScheduler() {
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
}
@Override
protected Notification getForegroundNotification(TaskState[] taskStates) {
return DownloadNotificationUtil.buildProgressNotification(
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
/* message= */ null,
taskStates);
}
@Override
protected void onTaskStateChanged(TaskState taskState) {
if (taskState.action.isRemoveAction) {
return;
}
Notification notification = null;
if (taskState.state == TaskState.STATE_COMPLETED) {
notification =
DownloadNotificationUtil.buildDownloadCompletedNotification(
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
Util.fromUtf8Bytes(taskState.action.data));
} else if (taskState.state == TaskState.STATE_FAILED) {
notification =
DownloadNotificationUtil.buildDownloadFailedNotification(
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
Util.fromUtf8Bytes(taskState.action.data));
}
int notificationId = FOREGROUND_NOTIFICATION_ID + 1 + taskState.taskId;
NotificationUtil.setNotification(this, notificationId, notification);
}
}
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.demo;
import android.text.TextUtils;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Locale;
/**
* Utility methods for demo application.
*/
/* package */ final class DemoUtil {
/**
* Builds a track name for display.
*
* @param format {@link Format} of the track.
* @return a generated name specific to the track.
*/
public static String buildTrackName(Format format) {
String trackName;
if (MimeTypes.isVideo(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(
buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} else if (MimeTypes.isAudio(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator(
buildLanguageString(format), buildAudioPropertyString(format)),
buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} else {
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
}
return trackName.length() == 0 ? "unknown" : trackName;
}
private static String buildResolutionString(Format format) {
return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE
? "" : format.width + "x" + format.height;
}
private static String buildAudioPropertyString(Format format) {
return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE
? "" : format.channelCount + "ch, " + format.sampleRate + "Hz";
}
private static String buildLanguageString(Format format) {
return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? ""
: format.language;
}
private static String buildBitrateString(Format format) {
return format.bitrate == Format.NO_VALUE ? ""
: String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f);
}
private static String joinWithSeparator(String first, String second) {
return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second);
}
private static String buildTrackIdString(Format format) {
return format.id == null ? "" : ("id:" + format.id);
}
private static String buildSampleMimeTypeString(Format format) {
return format.sampleMimeType == null ? "" : format.sampleMimeType;
}
private DemoUtil() {}
}
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.demo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.util.Arrays;
/**
* Helper class for displaying track selection dialogs.
*/
/* package */ final class TrackSelectionHelper implements View.OnClickListener,
DialogInterface.OnClickListener {
private final DefaultTrackSelector selector;
private final TrackSelection.Factory trackSelectionFactory;
private MappedTrackInfo trackInfo;
private int rendererIndex;
private TrackGroupArray trackGroups;
private boolean[] trackGroupsAdaptive;
private boolean isDisabled;
private SelectionOverride override;
private CheckedTextView disableView;
private CheckedTextView defaultView;
private CheckedTextView[][] trackViews;
/**
* @param selector The track selector.
* @param trackSelectionFactory A factory for overriding {@link TrackSelection}s.
*/
public TrackSelectionHelper(
DefaultTrackSelector selector, TrackSelection.Factory trackSelectionFactory) {
this.selector = selector;
this.trackSelectionFactory = trackSelectionFactory;
}
/**
* Shows the selection dialog for a given renderer.
*
* @param activity The parent activity.
* @param title The dialog's title.
* @param trackInfo The current track information.
* @param rendererIndex The index of the renderer.
*/
public void showSelectionDialog(Activity activity, CharSequence title, MappedTrackInfo trackInfo,
int rendererIndex) {
this.trackInfo = trackInfo;
this.rendererIndex = rendererIndex;
trackGroups = trackInfo.getTrackGroups(rendererIndex);
trackGroupsAdaptive = new boolean[trackGroups.length];
for (int i = 0; i < trackGroups.length; i++) {
trackGroupsAdaptive[i] =
trackInfo.getAdaptiveSupport(rendererIndex, i, false)
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED
&& trackGroups.get(i).length > 1;
}
isDisabled = selector.getRendererDisabled(rendererIndex);
override = selector.getSelectionOverride(rendererIndex, trackGroups);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(title)
.setView(buildView(builder.getContext()))
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, null)
.create()
.show();
}
@SuppressLint("InflateParams")
private View buildView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.track_selection_dialog, null);
ViewGroup root = view.findViewById(R.id.root);
TypedArray attributeArray = context.getTheme().obtainStyledAttributes(
new int[] {android.R.attr.selectableItemBackground});
int selectableItemBackgroundResourceId = attributeArray.getResourceId(0, 0);
attributeArray.recycle();
// View for disabling the renderer.
disableView = (CheckedTextView) inflater.inflate(
android.R.layout.simple_list_item_single_choice, root, false);
disableView.setBackgroundResource(selectableItemBackgroundResourceId);
disableView.setText(R.string.selection_disabled);
disableView.setFocusable(true);
disableView.setOnClickListener(this);
root.addView(disableView);
// View for clearing the override to allow the selector to use its default selection logic.
defaultView = (CheckedTextView) inflater.inflate(
android.R.layout.simple_list_item_single_choice, root, false);
defaultView.setBackgroundResource(selectableItemBackgroundResourceId);
defaultView.setText(R.string.selection_default);
defaultView.setFocusable(true);
defaultView.setOnClickListener(this);
root.addView(inflater.inflate(R.layout.list_divider, root, false));
root.addView(defaultView);
// Per-track views.
trackViews = new CheckedTextView[trackGroups.length][];
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex);
boolean groupIsAdaptive = trackGroupsAdaptive[groupIndex];
trackViews[groupIndex] = new CheckedTextView[group.length];
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
if (trackIndex == 0) {
root.addView(inflater.inflate(R.layout.list_divider, root, false));
}
int trackViewLayoutId = groupIsAdaptive ? android.R.layout.simple_list_item_multiple_choice
: android.R.layout.simple_list_item_single_choice;
CheckedTextView trackView = (CheckedTextView) inflater.inflate(
trackViewLayoutId, root, false);
trackView.setBackgroundResource(selectableItemBackgroundResourceId);
trackView.setText(DemoUtil.buildTrackName(group.getFormat(trackIndex)));
if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)
== RendererCapabilities.FORMAT_HANDLED) {
trackView.setFocusable(true);
trackView.setTag(Pair.create(groupIndex, trackIndex));
trackView.setOnClickListener(this);
} else {
trackView.setFocusable(false);
trackView.setEnabled(false);
}
trackViews[groupIndex][trackIndex] = trackView;
root.addView(trackView);
}
}
updateViews();
return view;
}
private void updateViews() {
disableView.setChecked(isDisabled);
defaultView.setChecked(!isDisabled && override == null);
for (int i = 0; i < trackViews.length; i++) {
for (int j = 0; j < trackViews[i].length; j++) {
trackViews[i][j].setChecked(override != null && override.groupIndex == i
&& override.containsTrack(j));
}
}
}
// DialogInterface.OnClickListener
@Override
public void onClick(DialogInterface dialog, int which) {
selector.setRendererDisabled(rendererIndex, isDisabled);
if (override != null) {
selector.setSelectionOverride(rendererIndex, trackGroups, override);
} else {
selector.clearSelectionOverrides(rendererIndex);
}
}
// View.OnClickListener
@Override
public void onClick(View view) {
if (view == disableView) {
isDisabled = true;
override = null;
} else if (view == defaultView) {
isDisabled = false;
override = null;
} else {
isDisabled = false;
@SuppressWarnings("unchecked")
Pair<Integer, Integer> tag = (Pair<Integer, Integer>) view.getTag();
int groupIndex = tag.first;
int trackIndex = tag.second;
if (!trackGroupsAdaptive[groupIndex] || override == null
|| override.groupIndex != groupIndex) {
setOverride(groupIndex, trackIndex);
} else {
// The group being modified is adaptive and we already have a non-null override.
boolean isEnabled = ((CheckedTextView) view).isChecked();
int overrideLength = override.length;
if (isEnabled) {
// Remove the track from the override.
if (overrideLength == 1) {
// The last track is being removed, so the override becomes empty.
override = null;
isDisabled = true;
} else {
setOverride(groupIndex, getTracksRemoving(override, trackIndex));
}
} else {
// Add the track to the override.
setOverride(groupIndex, getTracksAdding(override, trackIndex));
}
}
}
// Update the views with the new state.
updateViews();
}
private void setOverride(int group, int... tracks) {
override = new SelectionOverride(trackSelectionFactory, group, tracks);
}
// Track array manipulation.
private static int[] getTracksAdding(SelectionOverride override, int addedTrack) {
int[] tracks = override.tracks;
tracks = Arrays.copyOf(tracks, tracks.length + 1);
tracks[tracks.length - 1] = addedTrack;
return tracks;
}
private static int[] getTracksRemoving(SelectionOverride override, int removedTrack) {
int[] tracks = new int[override.length - 1];
int trackCount = 0;
for (int i = 0; i < tracks.length + 1; i++) {
int track = override.tracks[i];
if (track != removedTrack) {
tracks[trackCount++] = track;
}
}
return tracks;
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@+id/sample_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
<ImageButton android:id="@+id/download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/exo_download_description"
android:background="@android:color/transparent"/>
</LinearLayout>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!-- Copyright (C) 2018 The Android Open Source Project
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
...@@ -14,8 +13,7 @@ ...@@ -14,8 +13,7 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<resources> <ListView xmlns:android="http://schemas.android.com/apk/res/android"
<string name="exo_media_action_repeat_all_description">"Repetir todo"</string> android:id="@+id/representation_list"
<string name="exo_media_action_repeat_off_description">"Non repetir"</string> android:layout_width="match_parent"
<string name="exo_media_action_repeat_one_description">"Repetir un"</string> android:layout_height="match_parent"/>
</resources>
...@@ -17,18 +17,10 @@ ...@@ -17,18 +17,10 @@
<string name="application_name">ExoPlayer</string> <string name="application_name">ExoPlayer</string>
<string name="video">Video</string>
<string name="audio">Audio</string>
<string name="text">Text</string>
<string name="selection_disabled">Disabled</string>
<string name="selection_default">Default</string>
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string> <string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
<string name="error_generic">Playback failed</string>
<string name="error_unrecognized_abr_algorithm">Unrecognized ABR algorithm</string> <string name="error_unrecognized_abr_algorithm">Unrecognized ABR algorithm</string>
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string> <string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
...@@ -55,4 +47,14 @@ ...@@ -55,4 +47,14 @@
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string> <string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
<string name="download_start_error">Failed to start download</string>
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>
<string name="download_drm_unsupported">This demo app does not support downloading protected content</string>
<string name="download_scheme_unsupported">This demo app only supports downloading http streams</string>
<string name="download_ads_unsupported">IMA does not support offline ads</string>
</resources> </resources>
...@@ -19,6 +19,7 @@ import android.support.annotation.NonNull; ...@@ -19,6 +19,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
...@@ -308,6 +309,11 @@ public final class CastPlayer implements Player { ...@@ -308,6 +309,11 @@ public final class CastPlayer implements Player {
} }
@Override @Override
public ExoPlaybackException getPlaybackError() {
return null;
}
@Override
public void setPlayWhenReady(boolean playWhenReady) { public void setPlayWhenReady(boolean playWhenReady) {
if (remoteMediaClient == null) { if (remoteMediaClient == null) {
return; return;
......
...@@ -280,6 +280,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -280,6 +280,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
new SocketTimeoutException(), dataSpec, getStatus(currentUrlRequest)); new SocketTimeoutException(), dataSpec, getStatus(currentUrlRequest));
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new OpenException(new InterruptedIOException(e), dataSpec, Status.INVALID); throw new OpenException(new InterruptedIOException(e), dataSpec, Status.INVALID);
} }
...@@ -352,17 +353,18 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou ...@@ -352,17 +353,18 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
if (!operation.block(readTimeoutMs)) { if (!operation.block(readTimeoutMs)) {
throw new SocketTimeoutException(); throw new SocketTimeoutException();
} }
} catch (InterruptedException | SocketTimeoutException e) { } catch (InterruptedException e) {
// If we're timing out or getting interrupted, the operation is still ongoing. // The operation is ongoing so replace readBuffer to avoid it being written to by this
// So we'll need to replace readBuffer to avoid the possibility of it being written to by // operation during a subsequent request.
// this operation during a subsequent request.
readBuffer = null; readBuffer = null;
Thread.currentThread().interrupt();
throw new HttpDataSourceException( throw new HttpDataSourceException(
e instanceof InterruptedException new InterruptedIOException(e), currentDataSpec, HttpDataSourceException.TYPE_READ);
? new InterruptedIOException((InterruptedException) e) } catch (SocketTimeoutException e) {
: (SocketTimeoutException) e, // The operation is ongoing so replace readBuffer to avoid it being written to by this
currentDataSpec, // operation during a subsequent request.
HttpDataSourceException.TYPE_READ); readBuffer = null;
throw new HttpDataSourceException(e, currentDataSpec, HttpDataSourceException.TYPE_READ);
} }
if (exception != null) { if (exception != null) {
......
...@@ -21,6 +21,7 @@ import android.util.Log; ...@@ -21,6 +21,7 @@ import android.util.Log;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
...@@ -86,7 +87,7 @@ public final class CronetEngineWrapper { ...@@ -86,7 +87,7 @@ public final class CronetEngineWrapper {
public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) { public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) {
CronetEngine cronetEngine = null; CronetEngine cronetEngine = null;
@CronetEngineSource int cronetEngineSource = SOURCE_UNAVAILABLE; @CronetEngineSource int cronetEngineSource = SOURCE_UNAVAILABLE;
List<CronetProvider> cronetProviders = CronetProvider.getAllProviders(context); List<CronetProvider> cronetProviders = new ArrayList<>(CronetProvider.getAllProviders(context));
// Remove disabled and fallback Cronet providers from list // Remove disabled and fallback Cronet providers from list
for (int i = cronetProviders.size() - 1; i >= 0; i--) { for (int i = cronetProviders.size() - 1; i >= 0; i--) {
if (!cronetProviders.get(i).isEnabled() if (!cronetProviders.get(i).isEnabled()
......
...@@ -74,7 +74,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -74,7 +74,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
*/ */
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioSink audioSink, boolean enableFloatOutput) { AudioSink audioSink, boolean enableFloatOutput) {
super(eventHandler, eventListener, null, false, audioSink); super(
eventHandler,
eventListener,
/* drmSessionManager= */ null,
/* playClearSamplesWithoutKeys= */ false,
audioSink);
this.enableFloatOutput = enableFloatOutput; this.enableFloatOutput = enableFloatOutput;
} }
......
...@@ -34,6 +34,7 @@ dependencies { ...@@ -34,6 +34,7 @@ dependencies {
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
androidTestImplementation project(modulePrefix + 'testutils') androidTestImplementation project(modulePrefix + 'testutils')
testImplementation project(modulePrefix + 'testutils-robolectric')
} }
ext { ext {
......
...@@ -79,7 +79,7 @@ public final class FlacExtractor implements Extractor { ...@@ -79,7 +79,7 @@ public final class FlacExtractor implements Extractor {
private static final byte[] FLAC_SIGNATURE = {'f', 'L', 'a', 'C', 0, 0, 0, 0x22}; private static final byte[] FLAC_SIGNATURE = {'f', 'L', 'a', 'C', 0, 0, 0, 0x22};
private final Id3Peeker id3Peeker; private final Id3Peeker id3Peeker;
private final @Flags int flags; private final boolean isId3MetadataDisabled;
private FlacDecoderJni decoderJni; private FlacDecoderJni decoderJni;
...@@ -90,7 +90,6 @@ public final class FlacExtractor implements Extractor { ...@@ -90,7 +90,6 @@ public final class FlacExtractor implements Extractor {
private ByteBuffer outputByteBuffer; private ByteBuffer outputByteBuffer;
private Metadata id3Metadata; private Metadata id3Metadata;
private long id3SectionSize;
private boolean metadataParsed; private boolean metadataParsed;
...@@ -105,8 +104,8 @@ public final class FlacExtractor implements Extractor { ...@@ -105,8 +104,8 @@ public final class FlacExtractor implements Extractor {
* @param flags Flags that control the extractor's behavior. * @param flags Flags that control the extractor's behavior.
*/ */
public FlacExtractor(int flags) { public FlacExtractor(int flags) {
this.flags = flags;
id3Peeker = new Id3Peeker(); id3Peeker = new Id3Peeker();
isId3MetadataDisabled = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
} }
@Override @Override
...@@ -125,24 +124,16 @@ public final class FlacExtractor implements Extractor { ...@@ -125,24 +124,16 @@ public final class FlacExtractor implements Extractor {
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
if (input.getPosition() == 0) { if (input.getPosition() == 0) {
id3Metadata = peekId3Data(input); id3Metadata = peekId3Data(input);
id3SectionSize = input.getPeekPosition();
} }
boolean isFlacFormat = peekFlacSignature(input); return peekFlacSignature(input);
if (isFlacFormat) {
// If this is FLAC format, we should skip the whole ID3 section.
skipFullyId3Section(input);
}
return isFlacFormat;
} }
@Override @Override
public int read(final ExtractorInput input, PositionHolder seekPosition) public int read(final ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (input.getPosition() == 0) { if (input.getPosition() == 0 && !isId3MetadataDisabled && id3Metadata == null) {
id3Metadata = peekId3Data(input); id3Metadata = peekId3Data(input);
id3SectionSize = input.getPeekPosition();
} }
skipFullyId3Section(input);
decoderJni.setData(input); decoderJni.setData(input);
...@@ -155,7 +146,7 @@ public final class FlacExtractor implements Extractor { ...@@ -155,7 +146,7 @@ public final class FlacExtractor implements Extractor {
} }
} catch (IOException e) { } catch (IOException e) {
decoderJni.reset(0); decoderJni.reset(0);
input.setRetryPosition(id3SectionSize, e); input.setRetryPosition(0, e);
throw e; // never executes throw e; // never executes
} }
metadataParsed = true; metadataParsed = true;
...@@ -163,7 +154,7 @@ public final class FlacExtractor implements Extractor { ...@@ -163,7 +154,7 @@ public final class FlacExtractor implements Extractor {
boolean isSeekable = decoderJni.getSeekPosition(0) != -1; boolean isSeekable = decoderJni.getSeekPosition(0) != -1;
extractorOutput.seekMap( extractorOutput.seekMap(
isSeekable isSeekable
? new FlacSeekMap(streamInfo.durationUs(), decoderJni, id3SectionSize) ? new FlacSeekMap(streamInfo.durationUs(), decoderJni)
: new SeekMap.Unseekable(streamInfo.durationUs(), 0)); : new SeekMap.Unseekable(streamInfo.durationUs(), 0));
Format mediaFormat = Format mediaFormat =
Format.createAudioSampleFormat( Format.createAudioSampleFormat(
...@@ -181,7 +172,7 @@ public final class FlacExtractor implements Extractor { ...@@ -181,7 +172,7 @@ public final class FlacExtractor implements Extractor {
/* drmInitData= */ null, /* drmInitData= */ null,
/* selectionFlags= */ 0, /* selectionFlags= */ 0,
/* language= */ null, /* language= */ null,
(flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : id3Metadata); isId3MetadataDisabled ? null : id3Metadata);
trackOutput.format(mediaFormat); trackOutput.format(mediaFormat);
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize()); outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
...@@ -196,7 +187,7 @@ public final class FlacExtractor implements Extractor { ...@@ -196,7 +187,7 @@ public final class FlacExtractor implements Extractor {
} catch (IOException e) { } catch (IOException e) {
if (lastDecodePosition >= 0) { if (lastDecodePosition >= 0) {
decoderJni.reset(lastDecodePosition); decoderJni.reset(lastDecodePosition);
input.setRetryPosition(id3SectionSize + lastDecodePosition, e); input.setRetryPosition(lastDecodePosition, e);
} }
throw e; throw e;
} }
...@@ -212,12 +203,11 @@ public final class FlacExtractor implements Extractor { ...@@ -212,12 +203,11 @@ public final class FlacExtractor implements Extractor {
@Override @Override
public void seek(long position, long timeUs) { public void seek(long position, long timeUs) {
if (position <= id3SectionSize) { if (position == 0) {
metadataParsed = false; metadataParsed = false;
} }
long flacStreamPosition = Math.max(0, position - id3SectionSize);
if (decoderJni != null) { if (decoderJni != null) {
decoderJni.reset(flacStreamPosition); decoderJni.reset(position);
} }
} }
...@@ -238,9 +228,8 @@ public final class FlacExtractor implements Extractor { ...@@ -238,9 +228,8 @@ public final class FlacExtractor implements Extractor {
@Nullable @Nullable
private Metadata peekId3Data(ExtractorInput input) throws IOException, InterruptedException { private Metadata peekId3Data(ExtractorInput input) throws IOException, InterruptedException {
input.resetPeekPosition(); input.resetPeekPosition();
boolean disableId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
Id3Decoder.FramePredicate id3FramePredicate = Id3Decoder.FramePredicate id3FramePredicate =
disableId3Frames ? Id3Decoder.NO_FRAMES_PREDICATE : null; isId3MetadataDisabled ? Id3Decoder.NO_FRAMES_PREDICATE : null;
return id3Peeker.peekId3Data(input, id3FramePredicate); return id3Peeker.peekId3Data(input, id3FramePredicate);
} }
...@@ -255,22 +244,14 @@ public final class FlacExtractor implements Extractor { ...@@ -255,22 +244,14 @@ public final class FlacExtractor implements Extractor {
return Arrays.equals(header, FLAC_SIGNATURE); return Arrays.equals(header, FLAC_SIGNATURE);
} }
/** Skips input until we have passed the whole Id3 section. */
private void skipFullyId3Section(ExtractorInput input) throws IOException, InterruptedException {
int bytesToSkip = Math.max(0, (int) (id3SectionSize - input.getPosition()));
input.skipFully(bytesToSkip);
}
private static final class FlacSeekMap implements SeekMap { private static final class FlacSeekMap implements SeekMap {
private final long durationUs; private final long durationUs;
private final FlacDecoderJni decoderJni; private final FlacDecoderJni decoderJni;
private final long id3SectionSize;
public FlacSeekMap(long durationUs, FlacDecoderJni decoderJni, long id3SectionSize) { public FlacSeekMap(long durationUs, FlacDecoderJni decoderJni) {
this.durationUs = durationUs; this.durationUs = durationUs;
this.decoderJni = decoderJni; this.decoderJni = decoderJni;
this.id3SectionSize = id3SectionSize;
} }
@Override @Override
...@@ -281,8 +262,7 @@ public final class FlacExtractor implements Extractor { ...@@ -281,8 +262,7 @@ public final class FlacExtractor implements Extractor {
@Override @Override
public SeekPoints getSeekPoints(long timeUs) { public SeekPoints getSeekPoints(long timeUs) {
// TODO: Access the seek table via JNI to return two seek points when appropriate. // TODO: Access the seek table via JNI to return two seek points when appropriate.
return new SeekPoints( return new SeekPoints(new SeekPoint(timeUs, decoderJni.getSeekPosition(timeUs)));
new SeekPoint(timeUs, id3SectionSize + decoderJni.getSeekPosition(timeUs)));
} }
@Override @Override
......
...@@ -52,6 +52,8 @@ import com.google.android.exoplayer2.Timeline; ...@@ -52,6 +52,8 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdPlaybackState.AdState; import com.google.android.exoplayer2.source.ads.AdPlaybackState.AdState;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -80,6 +82,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -80,6 +82,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private final Context context; private final Context context;
private @Nullable ImaSdkSettings imaSdkSettings; private @Nullable ImaSdkSettings imaSdkSettings;
private @Nullable AdEventListener adEventListener;
private int vastLoadTimeoutMs; private int vastLoadTimeoutMs;
private int mediaLoadTimeoutMs; private int mediaLoadTimeoutMs;
...@@ -109,6 +112,18 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -109,6 +112,18 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
/** /**
* Sets a listener for ad events that will be passed to {@link
* AdsManager#addAdEventListener(AdEventListener)}.
*
* @param adEventListener The ad event listener.
* @return This builder, for convenience.
*/
public Builder setAdEventListener(AdEventListener adEventListener) {
this.adEventListener = Assertions.checkNotNull(adEventListener);
return this;
}
/**
* Sets the VAST load timeout, in milliseconds. * Sets the VAST load timeout, in milliseconds.
* *
* @param vastLoadTimeoutMs The VAST load timeout, in milliseconds. * @param vastLoadTimeoutMs The VAST load timeout, in milliseconds.
...@@ -144,7 +159,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -144,7 +159,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
*/ */
public ImaAdsLoader buildForAdTag(Uri adTagUri) { public ImaAdsLoader buildForAdTag(Uri adTagUri) {
return new ImaAdsLoader( return new ImaAdsLoader(
context, adTagUri, imaSdkSettings, null, vastLoadTimeoutMs, mediaLoadTimeoutMs); context,
adTagUri,
imaSdkSettings,
null,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
adEventListener);
} }
/** /**
...@@ -156,7 +177,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -156,7 +177,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
*/ */
public ImaAdsLoader buildForAdsResponse(String adsResponse) { public ImaAdsLoader buildForAdsResponse(String adsResponse) {
return new ImaAdsLoader( return new ImaAdsLoader(
context, null, imaSdkSettings, adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs); context,
null,
imaSdkSettings,
adsResponse,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
adEventListener);
} }
} }
...@@ -214,6 +241,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -214,6 +241,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private final @Nullable String adsResponse; private final @Nullable String adsResponse;
private final int vastLoadTimeoutMs; private final int vastLoadTimeoutMs;
private final int mediaLoadTimeoutMs; private final int mediaLoadTimeoutMs;
private final @Nullable AdEventListener adEventListener;
private final Timeline.Period period; private final Timeline.Period period;
private final List<VideoAdPlayerCallback> adCallbacks; private final List<VideoAdPlayerCallback> adCallbacks;
private final ImaSdkFactory imaSdkFactory; private final ImaSdkFactory imaSdkFactory;
...@@ -229,7 +257,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -229,7 +257,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private VideoProgressUpdate lastAdProgress; private VideoProgressUpdate lastAdProgress;
private AdsManager adsManager; private AdsManager adsManager;
private AdErrorEvent pendingAdErrorEvent; private AdLoadException pendingAdLoadError;
private Timeline timeline; private Timeline timeline;
private long contentDurationMs; private long contentDurationMs;
private int podIndexOffset; private int podIndexOffset;
...@@ -308,7 +336,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -308,7 +336,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
/* imaSdkSettings= */ null, /* imaSdkSettings= */ null,
/* adsResponse= */ null, /* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET); /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
/* adEventListener= */ null);
} }
/** /**
...@@ -330,7 +359,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -330,7 +359,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
imaSdkSettings, imaSdkSettings,
/* adsResponse= */ null, /* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET); /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
/* adEventListener= */ null);
} }
private ImaAdsLoader( private ImaAdsLoader(
...@@ -339,12 +369,14 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -339,12 +369,14 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
@Nullable ImaSdkSettings imaSdkSettings, @Nullable ImaSdkSettings imaSdkSettings,
@Nullable String adsResponse, @Nullable String adsResponse,
int vastLoadTimeoutMs, int vastLoadTimeoutMs,
int mediaLoadTimeoutMs) { int mediaLoadTimeoutMs,
@Nullable AdEventListener adEventListener) {
Assertions.checkArgument(adTagUri != null || adsResponse != null); Assertions.checkArgument(adTagUri != null || adsResponse != null);
this.adTagUri = adTagUri; this.adTagUri = adTagUri;
this.adsResponse = adsResponse; this.adsResponse = adsResponse;
this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.vastLoadTimeoutMs = vastLoadTimeoutMs;
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
this.adEventListener = adEventListener;
period = new Timeline.Period(); period = new Timeline.Period();
adCallbacks = new ArrayList<>(1); adCallbacks = new ArrayList<>(1);
imaSdkFactory = ImaSdkFactory.getInstance(); imaSdkFactory = ImaSdkFactory.getInstance();
...@@ -500,6 +532,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -500,6 +532,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
this.adsManager = adsManager; this.adsManager = adsManager;
adsManager.addAdErrorListener(this); adsManager.addAdErrorListener(this);
adsManager.addAdEventListener(this); adsManager.addAdEventListener(this);
if (adEventListener != null) {
adsManager.addAdEventListener(adEventListener);
}
if (player != null) { if (player != null) {
// If a player is attached already, start playback immediately. // If a player is attached already, start playback immediately.
try { try {
...@@ -544,13 +579,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -544,13 +579,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
updateAdPlaybackState(); updateAdPlaybackState();
} else if (isAdGroupLoadError(error)) { } else if (isAdGroupLoadError(error)) {
try { try {
handleAdGroupLoadError(); handleAdGroupLoadError(error);
} catch (Exception e) { } catch (Exception e) {
maybeNotifyInternalError("onAdError", e); maybeNotifyInternalError("onAdError", e);
} }
} }
if (pendingAdErrorEvent == null) { if (pendingAdLoadError == null) {
pendingAdErrorEvent = adErrorEvent; pendingAdLoadError = AdLoadException.createForAllAds(error);
} }
maybeNotifyPendingAdLoadError(); maybeNotifyPendingAdLoadError();
} }
...@@ -937,9 +972,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -937,9 +972,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
break; break;
case LOG: case LOG:
Map<String, String> adData = adEvent.getAdData(); Map<String, String> adData = adEvent.getAdData();
Log.i(TAG, "Log AdEvent: " + adData); String message = "AdEvent: " + adData;
Log.i(TAG, message);
if ("adLoadError".equals(adData.get("type"))) { if ("adLoadError".equals(adData.get("type"))) {
handleAdGroupLoadError(); handleAdGroupLoadError(new IOException(message));
} }
break; break;
case ALL_ADS_COMPLETED: case ALL_ADS_COMPLETED:
...@@ -1011,7 +1047,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -1011,7 +1047,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
} }
private void handleAdGroupLoadError() { private void handleAdGroupLoadError(Exception error) {
int adGroupIndex = int adGroupIndex =
this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex;
if (adGroupIndex == C.INDEX_UNSET) { if (adGroupIndex == C.INDEX_UNSET) {
...@@ -1033,6 +1069,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -1033,6 +1069,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
} }
updateAdPlaybackState(); updateAdPlaybackState();
if (pendingAdLoadError == null) {
pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex);
}
} }
private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) {
...@@ -1111,21 +1150,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -1111,21 +1150,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
private void maybeNotifyPendingAdLoadError() { private void maybeNotifyPendingAdLoadError() {
if (pendingAdErrorEvent != null) { if (pendingAdLoadError != null && eventListener != null) {
if (eventListener != null) { eventListener.onAdLoadError(pendingAdLoadError, new DataSpec(adTagUri));
eventListener.onAdLoadError( pendingAdLoadError = null;
new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError()));
}
pendingAdErrorEvent = null;
} }
} }
private void maybeNotifyInternalError(String name, Exception cause) { private void maybeNotifyInternalError(String name, Exception cause) {
String message = "Internal error in " + name; String message = "Internal error in " + name;
Log.e(TAG, message, cause); Log.e(TAG, message, cause);
if (eventListener != null) {
eventListener.onInternalAdLoadError(new RuntimeException(message, cause));
}
// We can't recover from an unexpected error in general, so skip all remaining ads. // We can't recover from an unexpected error in general, so skip all remaining ads.
if (adPlaybackState == null) { if (adPlaybackState == null) {
adPlaybackState = new AdPlaybackState(); adPlaybackState = new AdPlaybackState();
...@@ -1135,6 +1168,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A ...@@ -1135,6 +1168,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} }
} }
updateAdPlaybackState(); updateAdPlaybackState();
if (eventListener != null) {
eventListener.onAdLoadError(
AdLoadException.createForUnexpected(new RuntimeException(message, cause)),
new DataSpec(adTagUri));
}
} }
private static long[] getAdGroupTimesUs(List<Float> cuePoints) { private static long[] getAdGroupTimesUs(List<Float> cuePoints) {
......
...@@ -15,8 +15,6 @@ ...@@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer2.ext.jobdispatcher; package com.google.android.exoplayer2.ext.jobdispatcher;
import android.app.Notification;
import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
...@@ -34,13 +32,8 @@ import com.google.android.exoplayer2.scheduler.Scheduler; ...@@ -34,13 +32,8 @@ import com.google.android.exoplayer2.scheduler.Scheduler;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** /**
* A {@link Scheduler} which uses {@link com.firebase.jobdispatcher.FirebaseJobDispatcher} to * A {@link Scheduler} that uses {@link FirebaseJobDispatcher}. To use this scheduler, you must add
* schedule a {@link Service} to be started when its requirements are met. The started service must * {@link JobDispatcherSchedulerService} to your manifest:
* call {@link Service#startForeground(int, Notification)} to make itself a foreground service upon
* being started, as documented by {@link Service#startForegroundService(Intent)}.
*
* <p>To use {@link JobDispatcherScheduler} application needs to have RECEIVE_BOOT_COMPLETED
* permission and you need to define JobDispatcherSchedulerService in your manifest:
* *
* <pre>{@literal * <pre>{@literal
* <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> * <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
...@@ -54,18 +47,6 @@ import com.google.android.exoplayer2.util.Util; ...@@ -54,18 +47,6 @@ import com.google.android.exoplayer2.util.Util;
* </service> * </service>
* }</pre> * }</pre>
* *
* The service to be scheduled must be defined in the manifest with an intent-filter:
*
* <pre>{@literal
* <service android:name="MyJobService"
* android:exported="false">
* <intent-filter>
* <action android:name="MyJobService.action"/>
* <category android:name="android.intent.category.DEFAULT"/>
* </intent-filter>
* </service>
* }</pre>
*
* <p>This Scheduler uses Google Play services but does not do any availability checks. Any uses * <p>This Scheduler uses Google Play services but does not do any availability checks. Any uses
* should be guarded with a call to {@code * should be guarded with a call to {@code
* GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)} * GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)}
...@@ -76,44 +57,37 @@ import com.google.android.exoplayer2.util.Util; ...@@ -76,44 +57,37 @@ import com.google.android.exoplayer2.util.Util;
public final class JobDispatcherScheduler implements Scheduler { public final class JobDispatcherScheduler implements Scheduler {
private static final String TAG = "JobDispatcherScheduler"; private static final String TAG = "JobDispatcherScheduler";
private static final String SERVICE_ACTION = "SERVICE_ACTION"; private static final String KEY_SERVICE_ACTION = "service_action";
private static final String SERVICE_PACKAGE = "SERVICE_PACKAGE"; private static final String KEY_SERVICE_PACKAGE = "service_package";
private static final String REQUIREMENTS = "REQUIREMENTS"; private static final String KEY_REQUIREMENTS = "requirements";
private final String jobTag; private final String jobTag;
private final Job job;
private final FirebaseJobDispatcher jobDispatcher; private final FirebaseJobDispatcher jobDispatcher;
/** /**
* @param context Used to create a {@link FirebaseJobDispatcher} service. * @param context A context.
* @param requirements The requirements to execute the job. * @param jobTag A tag for jobs scheduled by this instance. If the same tag was used by a previous
* @param jobTag Unique tag for the job. Using the same tag as a previous job can cause that job * instance, anything scheduled by the previous instance will be canceled by this instance if
* to be replaced or canceled. * {@link #schedule(Requirements, String, String)} or {@link #cancel()} are called.
* @param serviceAction The action which the service will be started with.
* @param servicePackage The package of the service which contains the logic of the job.
*/ */
public JobDispatcherScheduler( public JobDispatcherScheduler(Context context, String jobTag) {
Context context, this.jobDispatcher =
Requirements requirements, new FirebaseJobDispatcher(new GooglePlayDriver(context.getApplicationContext()));
String jobTag,
String serviceAction,
String servicePackage) {
this.jobDispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context));
this.jobTag = jobTag; this.jobTag = jobTag;
this.job = buildJob(jobDispatcher, requirements, jobTag, serviceAction, servicePackage);
} }
@Override @Override
public boolean schedule() { public boolean schedule(Requirements requirements, String serviceAction, String servicePackage) {
Job job = buildJob(jobDispatcher, requirements, jobTag, serviceAction, servicePackage);
int result = jobDispatcher.schedule(job); int result = jobDispatcher.schedule(job);
logd("Scheduling JobDispatcher job: " + jobTag + " result: " + result); logd("Scheduling job: " + jobTag + " result: " + result);
return result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS; return result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS;
} }
@Override @Override
public boolean cancel() { public boolean cancel() {
int result = jobDispatcher.cancel(jobTag); int result = jobDispatcher.cancel(jobTag);
logd("Canceling JobDispatcher job: " + jobTag + " result: " + result); logd("Canceling job: " + jobTag + " result: " + result);
return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS; return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS;
} }
...@@ -151,13 +125,12 @@ public final class JobDispatcherScheduler implements Scheduler { ...@@ -151,13 +125,12 @@ public final class JobDispatcherScheduler implements Scheduler {
} }
builder.setLifetime(Lifetime.FOREVER).setReplaceCurrent(true); builder.setLifetime(Lifetime.FOREVER).setReplaceCurrent(true);
// Extras, work duration.
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(SERVICE_ACTION, serviceAction); extras.putString(KEY_SERVICE_ACTION, serviceAction);
extras.putString(SERVICE_PACKAGE, servicePackage); extras.putString(KEY_SERVICE_PACKAGE, servicePackage);
extras.putInt(REQUIREMENTS, requirements.getRequirementsData()); extras.putInt(KEY_REQUIREMENTS, requirements.getRequirementsData());
builder.setExtras(extras); builder.setExtras(extras);
return builder.build(); return builder.build();
} }
...@@ -167,26 +140,22 @@ public final class JobDispatcherScheduler implements Scheduler { ...@@ -167,26 +140,22 @@ public final class JobDispatcherScheduler implements Scheduler {
} }
} }
/** A {@link JobService} to start a service if the requirements are met. */ /** A {@link JobService} that starts the target service if the requirements are met. */
public static final class JobDispatcherSchedulerService extends JobService { public static final class JobDispatcherSchedulerService extends JobService {
@Override @Override
public boolean onStartJob(JobParameters params) { public boolean onStartJob(JobParameters params) {
logd("JobDispatcherSchedulerService is started"); logd("JobDispatcherSchedulerService is started");
Bundle extras = params.getExtras(); Bundle extras = params.getExtras();
Requirements requirements = new Requirements(extras.getInt(REQUIREMENTS)); Requirements requirements = new Requirements(extras.getInt(KEY_REQUIREMENTS));
if (requirements.checkRequirements(this)) { if (requirements.checkRequirements(this)) {
logd("requirements are met"); logd("Requirements are met");
String serviceAction = extras.getString(SERVICE_ACTION); String serviceAction = extras.getString(KEY_SERVICE_ACTION);
String servicePackage = extras.getString(SERVICE_PACKAGE); String servicePackage = extras.getString(KEY_SERVICE_PACKAGE);
Intent intent = new Intent(serviceAction).setPackage(servicePackage); Intent intent = new Intent(serviceAction).setPackage(servicePackage);
logd("starting service action: " + serviceAction + " package: " + servicePackage); logd("Starting service action: " + serviceAction + " package: " + servicePackage);
if (Util.SDK_INT >= 26) { Util.startForegroundService(this, intent);
startForegroundService(intent);
} else {
startService(intent);
}
} else { } else {
logd("requirements are not met"); logd("Requirements are not met");
jobFinished(params, /* needsReschedule */ true); jobFinished(params, /* needsReschedule */ true);
} }
return false; return false;
......
...@@ -53,7 +53,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { ...@@ -53,7 +53,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
private @Nullable PlaybackPreparer playbackPreparer; private @Nullable PlaybackPreparer playbackPreparer;
private ControlDispatcher controlDispatcher; private ControlDispatcher controlDispatcher;
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private SurfaceHolderGlueHost surfaceHolderGlueHost; private SurfaceHolderGlueHost surfaceHolderGlueHost;
private boolean hasSurface; private boolean hasSurface;
private boolean lastNotifiedPreparedState; private boolean lastNotifiedPreparedState;
...@@ -110,7 +110,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter { ...@@ -110,7 +110,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
* @param errorMessageProvider The {@link ErrorMessageProvider}. * @param errorMessageProvider The {@link ErrorMessageProvider}.
*/ */
public void setErrorMessageProvider( public void setErrorMessageProvider(
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) { @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider; this.errorMessageProvider = errorMessageProvider;
} }
......
...@@ -334,12 +334,11 @@ public final class MediaSessionConnector { ...@@ -334,12 +334,11 @@ public final class MediaSessionConnector {
private Player player; private Player player;
private CustomActionProvider[] customActionProviders; private CustomActionProvider[] customActionProviders;
private Map<String, CustomActionProvider> customActionMap; private Map<String, CustomActionProvider> customActionMap;
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private PlaybackPreparer playbackPreparer; private PlaybackPreparer playbackPreparer;
private QueueNavigator queueNavigator; private QueueNavigator queueNavigator;
private QueueEditor queueEditor; private QueueEditor queueEditor;
private RatingCallback ratingCallback; private RatingCallback ratingCallback;
private ExoPlaybackException playbackException;
/** /**
* Creates an instance. Must be called on the same thread that is used to construct the player * Creates an instance. Must be called on the same thread that is used to construct the player
...@@ -403,16 +402,18 @@ public final class MediaSessionConnector { ...@@ -403,16 +402,18 @@ public final class MediaSessionConnector {
/** /**
* Sets the player to be connected to the media session. * Sets the player to be connected to the media session.
* <p> *
* The order in which any {@link CustomActionProvider}s are passed determines the order of the * <p>The order in which any {@link CustomActionProvider}s are passed determines the order of the
* actions published with the playback state of the session. * actions published with the playback state of the session.
* *
* @param player The player to be connected to the {@code MediaSession}. * @param player The player to be connected to the {@code MediaSession}.
* @param playbackPreparer An optional {@link PlaybackPreparer} for preparing the player. * @param playbackPreparer An optional {@link PlaybackPreparer} for preparing the player.
* @param customActionProviders An optional {@link CustomActionProvider}s to publish and handle * @param customActionProviders Optional {@link CustomActionProvider}s to publish and handle
* custom actions. * custom actions.
*/ */
public void setPlayer(Player player, PlaybackPreparer playbackPreparer, public void setPlayer(
Player player,
@Nullable PlaybackPreparer playbackPreparer,
CustomActionProvider... customActionProviders) { CustomActionProvider... customActionProviders) {
if (this.player != null) { if (this.player != null) {
this.player.removeListener(exoPlayerEventListener); this.player.removeListener(exoPlayerEventListener);
...@@ -435,13 +436,16 @@ public final class MediaSessionConnector { ...@@ -435,13 +436,16 @@ public final class MediaSessionConnector {
} }
/** /**
* Sets the {@link ErrorMessageProvider}. * Sets the optional {@link ErrorMessageProvider}.
* *
* @param errorMessageProvider The error message provider. * @param errorMessageProvider The error message provider.
*/ */
public void setErrorMessageProvider( public void setErrorMessageProvider(
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) { @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
if (this.errorMessageProvider != errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider; this.errorMessageProvider = errorMessageProvider;
updateMediaSessionPlaybackState();
}
} }
/** /**
...@@ -451,10 +455,12 @@ public final class MediaSessionConnector { ...@@ -451,10 +455,12 @@ public final class MediaSessionConnector {
* @param queueNavigator The queue navigator. * @param queueNavigator The queue navigator.
*/ */
public void setQueueNavigator(QueueNavigator queueNavigator) { public void setQueueNavigator(QueueNavigator queueNavigator) {
if (this.queueNavigator != queueNavigator) {
unregisterCommandReceiver(this.queueNavigator); unregisterCommandReceiver(this.queueNavigator);
this.queueNavigator = queueNavigator; this.queueNavigator = queueNavigator;
registerCommandReceiver(queueNavigator); registerCommandReceiver(queueNavigator);
} }
}
/** /**
* Sets the {@link QueueEditor} to handle queue edits sent by the media controller. * Sets the {@link QueueEditor} to handle queue edits sent by the media controller.
...@@ -462,11 +468,13 @@ public final class MediaSessionConnector { ...@@ -462,11 +468,13 @@ public final class MediaSessionConnector {
* @param queueEditor The queue editor. * @param queueEditor The queue editor.
*/ */
public void setQueueEditor(QueueEditor queueEditor) { public void setQueueEditor(QueueEditor queueEditor) {
if (this.queueEditor != queueEditor) {
unregisterCommandReceiver(this.queueEditor); unregisterCommandReceiver(this.queueEditor);
this.queueEditor = queueEditor; this.queueEditor = queueEditor;
registerCommandReceiver(queueEditor); registerCommandReceiver(queueEditor);
mediaSession.setFlags(queueEditor == null ? BASE_MEDIA_SESSION_FLAGS mediaSession.setFlags(
: EDITOR_MEDIA_SESSION_FLAGS); queueEditor == null ? BASE_MEDIA_SESSION_FLAGS : EDITOR_MEDIA_SESSION_FLAGS);
}
} }
/** /**
...@@ -475,10 +483,12 @@ public final class MediaSessionConnector { ...@@ -475,10 +483,12 @@ public final class MediaSessionConnector {
* @param ratingCallback The rating callback. * @param ratingCallback The rating callback.
*/ */
public void setRatingCallback(RatingCallback ratingCallback) { public void setRatingCallback(RatingCallback ratingCallback) {
if (this.ratingCallback != ratingCallback) {
unregisterCommandReceiver(this.ratingCallback); unregisterCommandReceiver(this.ratingCallback);
this.ratingCallback = ratingCallback; this.ratingCallback = ratingCallback;
registerCommandReceiver(this.ratingCallback); registerCommandReceiver(this.ratingCallback);
} }
}
private void registerCommandReceiver(CommandReceiver commandReceiver) { private void registerCommandReceiver(CommandReceiver commandReceiver) {
if (commandReceiver != null && commandReceiver.getCommands() != null) { if (commandReceiver != null && commandReceiver.getCommands() != null) {
...@@ -514,17 +524,17 @@ public final class MediaSessionConnector { ...@@ -514,17 +524,17 @@ public final class MediaSessionConnector {
} }
customActionMap = Collections.unmodifiableMap(currentActions); customActionMap = Collections.unmodifiableMap(currentActions);
int sessionPlaybackState = playbackException != null ? PlaybackStateCompat.STATE_ERROR int playbackState = player.getPlaybackState();
ExoPlaybackException playbackError =
playbackState == Player.STATE_IDLE ? player.getPlaybackError() : null;
int sessionPlaybackState =
playbackError != null
? PlaybackStateCompat.STATE_ERROR
: mapPlaybackState(player.getPlaybackState(), player.getPlayWhenReady()); : mapPlaybackState(player.getPlaybackState(), player.getPlayWhenReady());
if (playbackException != null) { if (playbackError != null && errorMessageProvider != null) {
if (errorMessageProvider != null) { Pair<Integer, String> message = errorMessageProvider.getErrorMessage(playbackError);
Pair<Integer, String> message = errorMessageProvider.getErrorMessage(playbackException);
builder.setErrorMessage(message.first, message.second); builder.setErrorMessage(message.first, message.second);
} }
if (player.getPlaybackState() != Player.STATE_IDLE) {
playbackException = null;
}
}
long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player) long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player)
: MediaSessionCompat.QueueItem.UNKNOWN_ID; : MediaSessionCompat.QueueItem.UNKNOWN_ID;
Bundle extras = new Bundle(); Bundle extras = new Bundle();
...@@ -700,12 +710,6 @@ public final class MediaSessionConnector { ...@@ -700,12 +710,6 @@ public final class MediaSessionConnector {
} }
@Override @Override
public void onPlayerError(ExoPlaybackException error) {
playbackException = error;
updateMediaSessionPlaybackState();
}
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
if (currentWindowIndex != player.getCurrentWindowIndex()) { if (currentWindowIndex != player.getCurrentWindowIndex()) {
if (queueNavigator != null) { if (queueNavigator != null) {
......
...@@ -73,10 +73,11 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu ...@@ -73,10 +73,11 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
/** /**
* Gets the {@link MediaDescriptionCompat} for a given timeline window index. * Gets the {@link MediaDescriptionCompat} for a given timeline window index.
* *
* @param player The current player.
* @param windowIndex The timeline window index for which to provide a description. * @param windowIndex The timeline window index for which to provide a description.
* @return A {@link MediaDescriptionCompat}. * @return A {@link MediaDescriptionCompat}.
*/ */
public abstract MediaDescriptionCompat getMediaDescription(int windowIndex); public abstract MediaDescriptionCompat getMediaDescription(Player player, int windowIndex);
@Override @Override
public long getSupportedQueueNavigatorActions(Player player) { public long getSupportedQueueNavigatorActions(Player player) {
...@@ -185,7 +186,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu ...@@ -185,7 +186,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
windowCount - queueSize); windowCount - queueSize);
List<MediaSessionCompat.QueueItem> queue = new ArrayList<>(); List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
for (int i = startIndex; i < startIndex + queueSize; i++) { for (int i = startIndex; i < startIndex + queueSize; i++) {
queue.add(new MediaSessionCompat.QueueItem(getMediaDescription(i), i)); queue.add(new MediaSessionCompat.QueueItem(getMediaDescription(player, i), i));
} }
mediaSession.setQueue(queue); mediaSession.setQueue(queue);
activeQueueItemId = currentWindowIndex; activeQueueItemId = currentWindowIndex;
......
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Herhaal alles"</string> <string name="exo_media_action_repeat_off_description">Herhaal niks</string>
<string name="exo_media_action_repeat_off_description">"Herhaal niks"</string> <string name="exo_media_action_repeat_one_description">Herhaal een</string>
<string name="exo_media_action_repeat_one_description">"Herhaal een"</string> <string name="exo_media_action_repeat_all_description">Herhaal alles</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"ሁሉንም ድገም"</string> <string name="exo_media_action_repeat_off_description">ምንም አትድገም</string>
<string name="exo_media_action_repeat_off_description">"ምንም አትድገም"</string> <string name="exo_media_action_repeat_one_description">አንድ ድገም</string>
<string name="exo_media_action_repeat_one_description">"አንዱን ድገም"</string> <string name="exo_media_action_repeat_all_description">ሁሉንም ድገም</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"تكرار الكل"</string> <string name="exo_media_action_repeat_off_description">عدم التكرار</string>
<string name="exo_media_action_repeat_off_description">"عدم التكرار"</string> <string name="exo_media_action_repeat_one_description">تكرار مقطع صوتي واحد</string>
<string name="exo_media_action_repeat_one_description">"تكرار مقطع واحد"</string> <string name="exo_media_action_repeat_all_description">تكرار الكل</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Bütün təkrarlayın"</string>
<string name="exo_media_action_repeat_one_description">"Təkrar bir"</string>
<string name="exo_media_action_repeat_off_description">"Heç bir təkrar"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Heç biri təkrarlanmasın</string>
<string name="exo_media_action_repeat_one_description">Biri təkrarlansın</string>
<string name="exo_media_action_repeat_all_description">Hamısı təkrarlansın</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Ponovi sve"</string> <string name="exo_media_action_repeat_off_description">Ne ponavljaj nijednu</string>
<string name="exo_media_action_repeat_off_description">"Ne ponavljaj nijednu"</string> <string name="exo_media_action_repeat_one_description">Ponovi jednu</string>
<string name="exo_media_action_repeat_one_description">"Ponovi jednu"</string> <string name="exo_media_action_repeat_all_description">Ponovi sve</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Паўтарыць усё"</string>
<string name="exo_media_action_repeat_off_description">"Паўтараць ні"</string>
<string name="exo_media_action_repeat_one_description">"Паўтарыць адзін"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Не паўтараць нічога</string>
<string name="exo_media_action_repeat_one_description">Паўтарыць адзін элемент</string>
<string name="exo_media_action_repeat_all_description">Паўтарыць усе</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Повтаряне на всички"</string> <string name="exo_media_action_repeat_off_description">Без повтаряне</string>
<string name="exo_media_action_repeat_off_description">"Без повтаряне"</string> <string name="exo_media_action_repeat_one_description">Повтаряне на един елемент</string>
<string name="exo_media_action_repeat_one_description">"Повтаряне на един елемент"</string> <string name="exo_media_action_repeat_all_description">Повтаряне на всички</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"সবগুলির পুনরাবৃত্তি করুন"</string>
<string name="exo_media_action_repeat_off_description">"একটিরও পুনরাবৃত্তি করবেন না"</string>
<string name="exo_media_action_repeat_one_description">"একটির পুনরাবৃত্তি করুন"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">কোনও আইটেম আবার চালাবেন না</string>
<string name="exo_media_action_repeat_one_description">একটি আইটেম আবার চালান</string>
<string name="exo_media_action_repeat_all_description">সবগুলি আইটেম আবার চালান</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Ne ponavljaj</string>
<string name="exo_media_action_repeat_one_description">Ponovi jedno</string>
<string name="exo_media_action_repeat_all_description">Ponovi sve</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Repeteix-ho tot"</string> <string name="exo_media_action_repeat_off_description">No en repeteixis cap</string>
<string name="exo_media_action_repeat_off_description">"No en repeteixis cap"</string> <string name="exo_media_action_repeat_one_description">Repeteix una</string>
<string name="exo_media_action_repeat_one_description">"Repeteix-ne un"</string> <string name="exo_media_action_repeat_all_description">Repeteix tot</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Opakovat vše"</string> <string name="exo_media_action_repeat_off_description">Neopakovat</string>
<string name="exo_media_action_repeat_off_description">"Neopakovat"</string> <string name="exo_media_action_repeat_one_description">Opakovat jednu</string>
<string name="exo_media_action_repeat_one_description">"Opakovat jednu položku"</string> <string name="exo_media_action_repeat_all_description">Opakovat vše</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Gentag alle"</string> <string name="exo_media_action_repeat_off_description">Gentag ingen</string>
<string name="exo_media_action_repeat_off_description">"Gentag ingen"</string> <string name="exo_media_action_repeat_one_description">Gentag én</string>
<string name="exo_media_action_repeat_one_description">"Gentag en"</string> <string name="exo_media_action_repeat_all_description">Gentag alle</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Alle wiederholen"</string> <string name="exo_media_action_repeat_off_description">Keinen wiederholen</string>
<string name="exo_media_action_repeat_off_description">"Keinen Titel wiederholen"</string> <string name="exo_media_action_repeat_one_description">Einen wiederholen</string>
<string name="exo_media_action_repeat_one_description">"Einen Titel wiederholen"</string> <string name="exo_media_action_repeat_all_description">Alle wiederholen</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Επανάληψη όλων"</string> <string name="exo_media_action_repeat_off_description">Καμία επανάληψη</string>
<string name="exo_media_action_repeat_off_description">"Καμία επανάληψη"</string> <string name="exo_media_action_repeat_one_description">Επανάληψη ενός κομματιού</string>
<string name="exo_media_action_repeat_one_description">"Επανάληψη ενός στοιχείου"</string> <string name="exo_media_action_repeat_all_description">Επανάληψη όλων</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Repeat all"</string> <string name="exo_media_action_repeat_off_description">Repeat none</string>
<string name="exo_media_action_repeat_off_description">"Repeat none"</string> <string name="exo_media_action_repeat_one_description">Repeat one</string>
<string name="exo_media_action_repeat_one_description">"Repeat one"</string> <string name="exo_media_action_repeat_all_description">Repeat all</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Repeat all"</string> <string name="exo_media_action_repeat_off_description">Repeat none</string>
<string name="exo_media_action_repeat_off_description">"Repeat none"</string> <string name="exo_media_action_repeat_one_description">Repeat one</string>
<string name="exo_media_action_repeat_one_description">"Repeat one"</string> <string name="exo_media_action_repeat_all_description">Repeat all</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Repeat all"</string> <string name="exo_media_action_repeat_off_description">Repeat none</string>
<string name="exo_media_action_repeat_off_description">"Repeat none"</string> <string name="exo_media_action_repeat_one_description">Repeat one</string>
<string name="exo_media_action_repeat_one_description">"Repeat one"</string> <string name="exo_media_action_repeat_all_description">Repeat all</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Repetir todo"</string> <string name="exo_media_action_repeat_off_description">No repetir</string>
<string name="exo_media_action_repeat_off_description">"No repetir"</string> <string name="exo_media_action_repeat_one_description">Repetir uno</string>
<string name="exo_media_action_repeat_one_description">"Repetir uno"</string> <string name="exo_media_action_repeat_all_description">Repetir todo</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Repetir todo"</string> <string name="exo_media_action_repeat_off_description">No repetir</string>
<string name="exo_media_action_repeat_off_description">"No repetir"</string> <string name="exo_media_action_repeat_one_description">Repetir uno</string>
<string name="exo_media_action_repeat_one_description">"Repetir uno"</string> <string name="exo_media_action_repeat_all_description">Repetir todo</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Korda kõike"</string>
<string name="exo_media_action_repeat_off_description">"Ära korda midagi"</string>
<string name="exo_media_action_repeat_one_description">"Korda ühte"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Ära korda ühtegi</string>
<string name="exo_media_action_repeat_one_description">Korda ühte</string>
<string name="exo_media_action_repeat_all_description">Korda kõiki</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Errepikatu guztiak"</string>
<string name="exo_media_action_repeat_off_description">"Ez errepikatu"</string>
<string name="exo_media_action_repeat_one_description">"Errepikatu bat"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Ez errepikatu</string>
<string name="exo_media_action_repeat_one_description">Errepikatu bat</string>
<string name="exo_media_action_repeat_all_description">Errepikatu guztiak</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"تکرار همه"</string> <string name="exo_media_action_repeat_off_description">تکرار هیچ‌کدام</string>
<string name="exo_media_action_repeat_off_description">"تکرار هیچ‌کدام"</string> <string name="exo_media_action_repeat_one_description">یکبار تکرار</string>
<string name="exo_media_action_repeat_one_description">"یک‌بار تکرار"</string> <string name="exo_media_action_repeat_all_description">تکرار همه</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Toista kaikki"</string> <string name="exo_media_action_repeat_off_description">Ei uudelleentoistoa</string>
<string name="exo_media_action_repeat_off_description">"Toista ei mitään"</string> <string name="exo_media_action_repeat_one_description">Toista yksi uudelleen</string>
<string name="exo_media_action_repeat_one_description">"Toista yksi"</string> <string name="exo_media_action_repeat_all_description">Toista kaikki uudelleen</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Tout lire en boucle"</string> <string name="exo_media_action_repeat_off_description">Ne rien lire en boucle</string>
<string name="exo_media_action_repeat_off_description">"Aucune répétition"</string> <string name="exo_media_action_repeat_one_description">Lire une chanson en boucle</string>
<string name="exo_media_action_repeat_one_description">"Répéter un élément"</string> <string name="exo_media_action_repeat_all_description">Tout lire en boucle</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Tout lire en boucle"</string> <string name="exo_media_action_repeat_off_description">Ne rien lire en boucle</string>
<string name="exo_media_action_repeat_off_description">"Ne rien lire en boucle"</string> <string name="exo_media_action_repeat_one_description">Lire un titre en boucle</string>
<string name="exo_media_action_repeat_one_description">"Lire en boucle un élément"</string> <string name="exo_media_action_repeat_all_description">Tout lire en boucle</string>
</resources> </resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Non repetir</string>
<string name="exo_media_action_repeat_one_description">Repetir unha pista</string>
<string name="exo_media_action_repeat_all_description">Repetir todas as pistas</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"બધા પુનરાવર્તન કરો"</string>
<string name="exo_media_action_repeat_off_description">"કંઈ પુનરાવર્તન કરો"</string>
<string name="exo_media_action_repeat_one_description">"એક પુનરાવર્તન કરો"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">કોઈ રિપીટ કરતા નહીં</string>
<string name="exo_media_action_repeat_one_description">એક રિપીટ કરો</string>
<string name="exo_media_action_repeat_all_description">બધાને રિપીટ કરો</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"सभी को दोहराएं"</string> <string name="exo_media_action_repeat_off_description">किसी को न दोहराएं</string>
<string name="exo_media_action_repeat_off_description">"कुछ भी न दोहराएं"</string> <string name="exo_media_action_repeat_one_description">एक को दोहराएं</string>
<string name="exo_media_action_repeat_one_description">"एक दोहराएं"</string> <string name="exo_media_action_repeat_all_description">सभी को दोहराएं</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Ponovi sve"</string> <string name="exo_media_action_repeat_off_description">Bez ponavljanja</string>
<string name="exo_media_action_repeat_off_description">"Bez ponavljanja"</string> <string name="exo_media_action_repeat_one_description">Ponovi jedno</string>
<string name="exo_media_action_repeat_one_description">"Ponovi jedno"</string> <string name="exo_media_action_repeat_all_description">Ponovi sve</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Összes ismétlése"</string> <string name="exo_media_action_repeat_off_description">Nincs ismétlés</string>
<string name="exo_media_action_repeat_off_description">"Nincs ismétlés"</string> <string name="exo_media_action_repeat_one_description">Egy szám ismétlése</string>
<string name="exo_media_action_repeat_one_description">"Egy ismétlése"</string> <string name="exo_media_action_repeat_all_description">Összes szám ismétlése</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"կրկնել այն ամենը"</string>
<string name="exo_media_action_repeat_off_description">"Չկրկնել"</string>
<string name="exo_media_action_repeat_one_description">"Կրկնել մեկը"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Չկրկնել</string>
<string name="exo_media_action_repeat_one_description">Կրկնել մեկը</string>
<string name="exo_media_action_repeat_all_description">Կրկնել բոլորը</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Ulangi Semua"</string> <string name="exo_media_action_repeat_off_description">Jangan ulangi</string>
<string name="exo_media_action_repeat_off_description">"Jangan Ulangi"</string> <string name="exo_media_action_repeat_one_description">Ulangi 1</string>
<string name="exo_media_action_repeat_one_description">"Ulangi Satu"</string> <string name="exo_media_action_repeat_all_description">Ulangi semua</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Endurtaka allt"</string>
<string name="exo_media_action_repeat_off_description">"Endurtaka ekkert"</string>
<string name="exo_media_action_repeat_one_description">"Endurtaka eitt"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Endurtaka ekkert</string>
<string name="exo_media_action_repeat_one_description">Endurtaka eitt</string>
<string name="exo_media_action_repeat_all_description">Endurtaka allt</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Ripeti tutti"</string> <string name="exo_media_action_repeat_off_description">Non ripetere nulla</string>
<string name="exo_media_action_repeat_off_description">"Non ripetere nessuno"</string> <string name="exo_media_action_repeat_one_description">Ripeti uno</string>
<string name="exo_media_action_repeat_one_description">"Ripeti uno"</string> <string name="exo_media_action_repeat_all_description">Ripeti tutto</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"חזור על הכל"</string> <string name="exo_media_action_repeat_off_description">אל תחזור על אף פריט</string>
<string name="exo_media_action_repeat_off_description">"אל תחזור על כלום"</string> <string name="exo_media_action_repeat_one_description">חזור על פריט אחד</string>
<string name="exo_media_action_repeat_one_description">"חזור על פריט אחד"</string> <string name="exo_media_action_repeat_all_description">חזור על הכול</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"全曲を繰り返し"</string> <string name="exo_media_action_repeat_off_description">リピートなし</string>
<string name="exo_media_action_repeat_off_description">"繰り返しなし"</string> <string name="exo_media_action_repeat_one_description">1 曲をリピート</string>
<string name="exo_media_action_repeat_one_description">"1曲を繰り返し"</string> <string name="exo_media_action_repeat_all_description">全曲をリピート</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"გამეორება ყველა"</string>
<string name="exo_media_action_repeat_off_description">"გაიმეორეთ არცერთი"</string>
<string name="exo_media_action_repeat_one_description">"გაიმეორეთ ერთი"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">არცერთის გამეორება</string>
<string name="exo_media_action_repeat_one_description">ერთის გამეორება</string>
<string name="exo_media_action_repeat_all_description">ყველას გამეორება</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Барлығын қайталау"</string>
<string name="exo_media_action_repeat_off_description">"Ешқайсысын қайталамау"</string>
<string name="exo_media_action_repeat_one_description">"Біреуін қайталау"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Ешқайсысын қайталамау</string>
<string name="exo_media_action_repeat_one_description">Біреуін қайталау</string>
<string name="exo_media_action_repeat_all_description">Барлығын қайталау</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"ធ្វើ​ម្ដង​ទៀត​ទាំងអស់"</string>
<string name="exo_media_action_repeat_off_description">"មិន​ធ្វើ​ឡើង​វិញ"</string>
<string name="exo_media_action_repeat_one_description">"ធ្វើ​​ឡើងវិញ​ម្ដង"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">មិន​លេង​ឡើងវិញ</string>
<string name="exo_media_action_repeat_one_description">លេង​ឡើង​វិញ​ម្ដង</string>
<string name="exo_media_action_repeat_all_description">លេង​ឡើងវិញ​ទាំងអស់</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"ಎಲ್ಲವನ್ನು ಪುನರಾವರ್ತಿಸಿ"</string>
<string name="exo_media_action_repeat_off_description">"ಯಾವುದನ್ನೂ ಪುನರಾವರ್ತಿಸಬೇಡಿ"</string>
<string name="exo_media_action_repeat_one_description">"ಒಂದನ್ನು ಪುನರಾವರ್ತಿಸಿ"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">ಯಾವುದನ್ನೂ ಪುನರಾವರ್ತಿಸಬೇಡಿ</string>
<string name="exo_media_action_repeat_one_description">ಒಂದನ್ನು ಪುನರಾವರ್ತಿಸಿ</string>
<string name="exo_media_action_repeat_all_description">ಎಲ್ಲವನ್ನು ಪುನರಾವರ್ತಿಸಿ</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"전체 반복"</string> <string name="exo_media_action_repeat_off_description">반복 안함</string>
<string name="exo_media_action_repeat_off_description">"반복 안함"</string> <string name="exo_media_action_repeat_one_description">현재 미디어 반복</string>
<string name="exo_media_action_repeat_one_description">"한 항목 반복"</string> <string name="exo_media_action_repeat_all_description">모두 반복</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Баарын кайталоо"</string>
<string name="exo_media_action_repeat_off_description">"Эч бирин кайталабоо"</string>
<string name="exo_media_action_repeat_one_description">"Бирөөнү кайталоо"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Кайталанбасын</string>
<string name="exo_media_action_repeat_one_description">Бирөөнү кайталоо</string>
<string name="exo_media_action_repeat_all_description">Баарын кайталоо</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"ຫຼິ້ນ​ຊ້ຳ​ທັງ​ໝົດ"</string>
<string name="exo_media_action_repeat_off_description">"​ບໍ່ຫຼິ້ນ​ຊ້ຳ"</string>
<string name="exo_media_action_repeat_one_description">"ຫຼິ້ນ​ຊ້ຳ"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">ບໍ່ຫຼິ້ນຊ້ຳ</string>
<string name="exo_media_action_repeat_one_description">ຫຼິ້ນຊໍ້າ</string>
<string name="exo_media_action_repeat_all_description">ຫຼິ້ນຊ້ຳທັງໝົດ</string>
</resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Kartoti viską"</string> <string name="exo_media_action_repeat_off_description">Nekartoti nieko</string>
<string name="exo_media_action_repeat_off_description">"Nekartoti nieko"</string> <string name="exo_media_action_repeat_one_description">Kartoti vieną</string>
<string name="exo_media_action_repeat_one_description">"Kartoti vieną"</string> <string name="exo_media_action_repeat_all_description">Kartoti viską</string>
</resources> </resources>
<?xml version="1.0"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2016 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> <resources>
<string name="exo_media_action_repeat_all_description">"Atkārtot visu"</string> <string name="exo_media_action_repeat_off_description">Neatkārtot nevienu</string>
<string name="exo_media_action_repeat_off_description">"Neatkārtot nevienu"</string> <string name="exo_media_action_repeat_one_description">Atkārtot vienu</string>
<string name="exo_media_action_repeat_one_description">"Atkārtot vienu"</string> <string name="exo_media_action_repeat_all_description">Atkārtot visu</string>
</resources> </resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Повтори ги сите"</string>
<string name="exo_media_action_repeat_off_description">"Не повторувај ниту една"</string>
<string name="exo_media_action_repeat_one_description">"Повтори една"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Не повторувај ниту една</string>
<string name="exo_media_action_repeat_one_description">Повтори една</string>
<string name="exo_media_action_repeat_all_description">Повтори ги сите</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"എല്ലാം ആവർത്തിക്കുക"</string>
<string name="exo_media_action_repeat_off_description">"ഒന്നും ആവർത്തിക്കരുത്"</string>
<string name="exo_media_action_repeat_one_description">"ഒന്ന് ആവർത്തിക്കുക"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">ഒന്നും ആവർത്തിക്കരുത്</string>
<string name="exo_media_action_repeat_one_description">ഒരെണ്ണം ആവർത്തിക്കുക</string>
<string name="exo_media_action_repeat_all_description">എല്ലാം ആവർത്തിക്കുക</string>
</resources>
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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>
<string name="exo_media_action_repeat_all_description">"Бүгдийг давтах"</string>
<string name="exo_media_action_repeat_off_description">"Алийг нь ч давтахгүй"</string>
<string name="exo_media_action_repeat_one_description">"Нэгийг давтах"</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="exo_media_action_repeat_off_description">Алийг нь ч дахин тоглуулахгүй</string>
<string name="exo_media_action_repeat_one_description">Одоогийн тоглуулж буй медиаг дахин тоглуулах</string>
<string name="exo_media_action_repeat_all_description">Бүгдийг нь дахин тоглуулах</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment