Merge remote-tracking branch 'upstream/dev-v2' into dev-v2
@ -2,19 +2,19 @@
|
||||
|
||||
### dev-v2 (not yet released) ###
|
||||
|
||||
* Optimize seeking in FMP4 by enabling seeking to the nearest sync sample within
|
||||
a fragment. This benefits standalone FMP4 playbacks, DASH and SmoothStreaming.
|
||||
* Moved initial bitrate estimate from `AdaptiveTrackSelection` to
|
||||
`DefaultBandwidthMeter`.
|
||||
* Updated default max buffer length in `DefaultLoadControl`.
|
||||
* Added `AnalyticsListener` interface which can be registered in
|
||||
`SimpleExoPlayer` to receive detailed meta data for each ExoPlayer event.
|
||||
* UI components:
|
||||
* Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update
|
||||
([#3736](https://github.com/google/ExoPlayer/issues/3736)).
|
||||
* Add PlayerNotificationManager.
|
||||
* Downloading: Add `DownloadService`, `DownloadManager` and
|
||||
related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)).
|
||||
* Coming soon...
|
||||
|
||||
### 2.8.0 ###
|
||||
|
||||
* Downloading:
|
||||
* Add `DownloadService`, `DownloadManager` and related classes
|
||||
([#2643](https://github.com/google/ExoPlayer/issues/2643)). Information on
|
||||
using these components to download progressive formats can be found
|
||||
[here](https://medium.com/google-exoplayer/downloading-streams-6d259eec7f95).
|
||||
To see how to download DASH, HLS and SmoothStreaming media, take a look at
|
||||
the app.
|
||||
* Updated main demo app to support downloading DASH, HLS, SmoothStreaming and
|
||||
progressive media.
|
||||
* MediaSources:
|
||||
* Allow reusing media sources after they have been released and
|
||||
also in parallel to allow adding them multiple times to a concatenation.
|
||||
@ -31,9 +31,23 @@
|
||||
* Support live stream clipping with `ClippingMediaSource`.
|
||||
* Allow setting tags for all media sources in their factories. The tag of the
|
||||
current window can be retrieved with `ExoPlayer.getCurrentTag`.
|
||||
* IMA: Allow setting the ad media load timeout
|
||||
([#3691](https://github.com/google/ExoPlayer/issues/3691)).
|
||||
* UI components:
|
||||
* 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:
|
||||
* Support extracting data from AMR container formats, including both narrow
|
||||
and wide band ([#2527](https://github.com/google/ExoPlayer/issues/2527)).
|
||||
* FLAC:
|
||||
* Sniff FLAC files correctly if they have ID3 headers
|
||||
([#4055](https://github.com/google/ExoPlayer/issues/4055)).
|
||||
@ -48,14 +62,17 @@
|
||||
* Fix an issue where playback of TrueHD streams would get stuck after seeking
|
||||
due to not finding a syncframe
|
||||
((#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
|
||||
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
|
||||
gapless playback info.
|
||||
* Allow overriding skipping/scaling with custom `AudioProcessor`s
|
||||
((#3142)[https://github.com/google/ExoPlayer/issues/3142]).
|
||||
* Caching:
|
||||
* Add release method to Cache interface.
|
||||
* Prevent multiple instances of SimpleCache in the same folder.
|
||||
Previous instance must be released.
|
||||
* Add release method to the `Cache` interface, and prevent multiple instances
|
||||
of `SimpleCache` using the same folder at the same time.
|
||||
* Cache redirect URLs
|
||||
([#2360](https://github.com/google/ExoPlayer/issues/2360)).
|
||||
* DRM:
|
||||
@ -65,17 +82,41 @@
|
||||
([#4022][https://github.com/google/ExoPlayer/issues/4022]).
|
||||
* Fix handling of 307/308 redirects when making license requests
|
||||
([#4108](https://github.com/google/ExoPlayer/issues/4108)).
|
||||
* HLS: Fix playlist loading error propagation when the current selection does
|
||||
not include all of the playlist's variants.
|
||||
* HLS:
|
||||
* Fix playlist loading error propagation when the current selection does
|
||||
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
|
||||
([#4075](https://github.com/google/ExoPlayer/issues/4075)).
|
||||
* Fix crash when switching surface on Huawei P9 Lite
|
||||
([#4084](https://github.com/google/ExoPlayer/issues/4084)), and Philips QM163E
|
||||
([#4104](https://github.com/google/ExoPlayer/issues/4104)).
|
||||
* 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
|
||||
actual renderer timestamp offset can be obtained by listening to
|
||||
`BaseRenderer.onStreamChanged`.
|
||||
* Added dependencies on checkerframework annotations for static code analysis.
|
||||
|
||||
### 2.7.3 ###
|
||||
|
||||
@ -240,6 +281,7 @@
|
||||
([#3792](https://github.com/google/ExoPlayer/issues/3792).
|
||||
* Support 14-bit mode and little endianness in DTS PES packets
|
||||
([#3340](https://github.com/google/ExoPlayer/issues/3340)).
|
||||
* Demo app: Add ability to download not DRM protected content.
|
||||
|
||||
### 2.6.1 ###
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
<!-- 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.
|
||||
@ -14,8 +12,8 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="exo_media_action_repeat_all_description">"Ulang semua"</string>
|
||||
<string name="exo_media_action_repeat_off_description">"Tiada ulangan"</string>
|
||||
<string name="exo_media_action_repeat_one_description">"Ulangan"</string>
|
||||
</resources>
|
||||
<lint>
|
||||
<issue id="InvalidPackage">
|
||||
<ignore path="**/checker-qual-*.jar"/>
|
||||
</issue>
|
||||
</lint>
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
project.ext {
|
||||
// ExoPlayer version and version code.
|
||||
releaseVersion = '2.7.3'
|
||||
releaseVersionCode = 2703
|
||||
releaseVersion = '2.8.0'
|
||||
releaseVersionCode = 2800
|
||||
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
|
||||
// components provided by the library may be of use on older devices.
|
||||
// However, please note that the core media playback functionality provided
|
||||
@ -31,6 +31,8 @@ project.ext {
|
||||
junitVersion = '4.12'
|
||||
truthVersion = '0.39'
|
||||
robolectricVersion = '3.7.1'
|
||||
autoValueVersion = '1.6'
|
||||
checkerframeworkVersion = '2.5.0'
|
||||
modulePrefix = ':'
|
||||
if (gradle.ext.has('exoplayerModulePrefix')) {
|
||||
modulePrefix += gradle.ext.exoplayerModulePrefix
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<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.hardware.touchscreen" android:required="false"/>
|
||||
<uses-sdk/>
|
||||
@ -73,6 +75,18 @@
|
||||
</intent-filter>
|
||||
</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>
|
||||
|
||||
</manifest>
|
||||
|
@ -16,6 +16,13 @@
|
||||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
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.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
@ -35,10 +42,24 @@ import java.io.File;
|
||||
*/
|
||||
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;
|
||||
|
||||
private File downloadDirectory;
|
||||
private Cache downloadCache;
|
||||
private DownloadManager downloadManager;
|
||||
private DownloadTracker downloadTracker;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
@ -50,7 +71,7 @@ public class DemoApplication extends Application {
|
||||
public DataSource.Factory buildDataSourceFactory(TransferListener<? super DataSource> listener) {
|
||||
DefaultDataSourceFactory upstreamFactory =
|
||||
new DefaultDataSourceFactory(this, listener, buildHttpDataSourceFactory(listener));
|
||||
return createReadOnlyCacheDataSource(upstreamFactory, getDownloadCache());
|
||||
return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache());
|
||||
}
|
||||
|
||||
/** Returns a {@link HttpDataSource.Factory}. */
|
||||
@ -59,31 +80,69 @@ public class DemoApplication extends Application {
|
||||
return new DefaultHttpDataSourceFactory(userAgent, listener);
|
||||
}
|
||||
|
||||
/** Returns the download {@link Cache}. */
|
||||
public Cache getDownloadCache() {
|
||||
if (downloadCache == null) {
|
||||
File dir = getExternalFilesDir(null);
|
||||
if (dir == null) {
|
||||
dir = getFilesDir();
|
||||
}
|
||||
File downloadCacheFolder = new File(dir, DOWNLOAD_CACHE_FOLDER);
|
||||
downloadCache = new SimpleCache(downloadCacheFolder, new NoOpCacheEvictor());
|
||||
}
|
||||
return downloadCache;
|
||||
}
|
||||
|
||||
/** Returns whether extension renderers should be used. */
|
||||
public boolean useExtensionRenderers() {
|
||||
return "withExtensions".equals(BuildConfig.FLAVOR);
|
||||
}
|
||||
|
||||
private static CacheDataSourceFactory createReadOnlyCacheDataSource(
|
||||
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;
|
||||
}
|
||||
|
||||
private File getDownloadDirectory() {
|
||||
if (downloadDirectory == null) {
|
||||
downloadDirectory = getExternalFilesDir(null);
|
||||
if (downloadDirectory == null) {
|
||||
downloadDirectory = getFilesDir();
|
||||
}
|
||||
}
|
||||
return downloadDirectory;
|
||||
}
|
||||
|
||||
private static CacheDataSourceFactory buildReadOnlyCacheDataSource(
|
||||
DefaultDataSourceFactory upstreamFactory, Cache cache) {
|
||||
return new CacheDataSourceFactory(
|
||||
cache,
|
||||
upstreamFactory,
|
||||
new FileDataSourceFactory(),
|
||||
/*cacheWriteDataSinkFactory=*/ null,
|
||||
/* cacheWriteDataSinkFactory= */ null,
|
||||
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
|
||||
/*eventListener=*/ null);
|
||||
/* eventListener= */ null);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* 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() {}
|
||||
}
|
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.offline.ActionFile;
|
||||
import com.google.android.exoplayer2.offline.DownloadAction;
|
||||
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||
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.offline.ProgressiveDownloadHelper;
|
||||
import com.google.android.exoplayer2.offline.SegmentDownloadAction;
|
||||
import com.google.android.exoplayer2.offline.TrackKey;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.dash.offline.DashDownloadHelper;
|
||||
import com.google.android.exoplayer2.source.hls.offline.HlsDownloadHelper;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadHelper;
|
||||
import com.google.android.exoplayer2.ui.DefaultTrackNameProvider;
|
||||
import com.google.android.exoplayer2.ui.TrackNameProvider;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Tracks media that has been downloaded.
|
||||
*
|
||||
* <p>Tracked downloads are persisted using an {@link ActionFile}, however in a real application
|
||||
* it's expected that state will be stored directly in the application's media database, so that it
|
||||
* can be queried efficiently together with other information about the media.
|
||||
*/
|
||||
public class DownloadTracker implements DownloadManager.Listener {
|
||||
|
||||
/** Listens for changes in the tracked downloads. */
|
||||
public interface Listener {
|
||||
|
||||
/** Called when the tracked downloads changed. */
|
||||
void onDownloadsChanged();
|
||||
}
|
||||
|
||||
private static final String TAG = "DownloadTracker";
|
||||
|
||||
private final Context context;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final TrackNameProvider trackNameProvider;
|
||||
private final CopyOnWriteArraySet<Listener> listeners;
|
||||
private final HashMap<Uri, DownloadAction> trackedDownloadStates;
|
||||
private final ActionFile actionFile;
|
||||
private final Handler actionFileWriteHandler;
|
||||
|
||||
public DownloadTracker(
|
||||
Context context,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
File actionFile,
|
||||
DownloadAction.Deserializer[] deserializers) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.actionFile = new ActionFile(actionFile);
|
||||
trackNameProvider = new DefaultTrackNameProvider(context.getResources());
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
trackedDownloadStates = new HashMap<>();
|
||||
HandlerThread actionFileWriteThread = new HandlerThread("DownloadTracker");
|
||||
actionFileWriteThread.start();
|
||||
actionFileWriteHandler = new Handler(actionFileWriteThread.getLooper());
|
||||
loadTrackedActions(deserializers);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean isDownloaded(Uri uri) {
|
||||
return trackedDownloadStates.containsKey(uri);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K> List<K> getOfflineStreamKeys(Uri uri) {
|
||||
if (!trackedDownloadStates.containsKey(uri)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
DownloadAction action = trackedDownloadStates.get(uri);
|
||||
if (action instanceof SegmentDownloadAction) {
|
||||
return ((SegmentDownloadAction) action).keys;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void toggleDownload(Activity activity, String name, Uri uri, String extension) {
|
||||
if (isDownloaded(uri)) {
|
||||
DownloadAction removeAction =
|
||||
getDownloadHelper(uri, extension).getRemoveAction(Util.getUtf8Bytes(name));
|
||||
startServiceWithAction(removeAction);
|
||||
} else {
|
||||
StartDownloadDialogHelper helper =
|
||||
new StartDownloadDialogHelper(activity, getDownloadHelper(uri, extension), name);
|
||||
helper.prepare();
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadManager.Listener
|
||||
|
||||
@Override
|
||||
public void onInitialized(DownloadManager downloadManager) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStateChanged(DownloadManager downloadManager, TaskState taskState) {
|
||||
DownloadAction action = taskState.action;
|
||||
Uri uri = action.uri;
|
||||
if ((action.isRemoveAction && taskState.state == TaskState.STATE_COMPLETED)
|
||||
|| (!action.isRemoveAction && taskState.state == TaskState.STATE_FAILED)) {
|
||||
// A download has been removed, or has failed. Stop tracking it.
|
||||
if (trackedDownloadStates.remove(uri) != null) {
|
||||
handleTrackedDownloadStatesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdle(DownloadManager downloadManager) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
private void loadTrackedActions(DownloadAction.Deserializer[] deserializers) {
|
||||
try {
|
||||
DownloadAction[] allActions = actionFile.load(deserializers);
|
||||
for (DownloadAction action : allActions) {
|
||||
trackedDownloadStates.put(action.uri, action);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to load tracked actions", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTrackedDownloadStatesChanged() {
|
||||
for (Listener listener : listeners) {
|
||||
listener.onDownloadsChanged();
|
||||
}
|
||||
final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]);
|
||||
actionFileWriteHandler.post(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
actionFile.store(actions);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to store tracked actions", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startDownload(DownloadAction action) {
|
||||
if (trackedDownloadStates.containsKey(action.uri)) {
|
||||
// This content is already being downloaded. Do nothing.
|
||||
return;
|
||||
}
|
||||
trackedDownloadStates.put(action.uri, action);
|
||||
handleTrackedDownloadStatesChanged();
|
||||
startServiceWithAction(action);
|
||||
}
|
||||
|
||||
private void startServiceWithAction(DownloadAction action) {
|
||||
DownloadService.startWithAction(context, DemoDownloadService.class, action, false);
|
||||
}
|
||||
|
||||
private DownloadHelper getDownloadHelper(Uri uri, String extension) {
|
||||
int type = Util.inferContentType(uri, extension);
|
||||
switch (type) {
|
||||
case C.TYPE_DASH:
|
||||
return new DashDownloadHelper(uri, dataSourceFactory);
|
||||
case C.TYPE_SS:
|
||||
return new SsDownloadHelper(uri, dataSourceFactory);
|
||||
case C.TYPE_HLS:
|
||||
return new HlsDownloadHelper(uri, dataSourceFactory);
|
||||
case C.TYPE_OTHER:
|
||||
return new ProgressiveDownloadHelper(uri);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private final class StartDownloadDialogHelper
|
||||
implements DownloadHelper.Callback, DialogInterface.OnClickListener {
|
||||
|
||||
private final DownloadHelper downloadHelper;
|
||||
private final String name;
|
||||
|
||||
private final AlertDialog.Builder builder;
|
||||
private final View dialogView;
|
||||
private final List<TrackKey> trackKeys;
|
||||
private final ArrayAdapter<String> trackTitles;
|
||||
private final ListView representationList;
|
||||
|
||||
public StartDownloadDialogHelper(
|
||||
Activity activity, DownloadHelper downloadHelper, String name) {
|
||||
this.downloadHelper = downloadHelper;
|
||||
this.name = name;
|
||||
builder =
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.exo_download_description)
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
// Inflate with the builder's context to ensure the correct style is used.
|
||||
LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
||||
dialogView = dialogInflater.inflate(R.layout.start_download_dialog, null);
|
||||
|
||||
trackKeys = new ArrayList<>();
|
||||
trackTitles =
|
||||
new ArrayAdapter<>(
|
||||
builder.getContext(), android.R.layout.simple_list_item_multiple_choice);
|
||||
representationList = dialogView.findViewById(R.id.representation_list);
|
||||
representationList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
representationList.setAdapter(trackTitles);
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
downloadHelper.prepare(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepared(DownloadHelper helper) {
|
||||
for (int i = 0; i < downloadHelper.getPeriodCount(); i++) {
|
||||
TrackGroupArray trackGroups = downloadHelper.getTrackGroups(i);
|
||||
for (int j = 0; j < trackGroups.length; j++) {
|
||||
TrackGroup trackGroup = trackGroups.get(j);
|
||||
for (int k = 0; k < trackGroup.length; k++) {
|
||||
trackKeys.add(new TrackKey(i, j, k));
|
||||
trackTitles.add(trackNameProvider.getTrackName(trackGroup.getFormat(k)));
|
||||
}
|
||||
}
|
||||
if (!trackKeys.isEmpty()) {
|
||||
builder.setView(dialogView);
|
||||
}
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareError(DownloadHelper helper, IOException e) {
|
||||
Toast.makeText(
|
||||
context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
ArrayList<TrackKey> selectedTrackKeys = new ArrayList<>();
|
||||
for (int i = 0; i < representationList.getChildCount(); i++) {
|
||||
if (representationList.isItemChecked(i)) {
|
||||
selectedTrackKeys.add(trackKeys.get(i));
|
||||
}
|
||||
}
|
||||
if (!selectedTrackKeys.isEmpty() || trackKeys.isEmpty()) {
|
||||
// We have selected keys, or we're dealing with single stream content.
|
||||
DownloadAction downloadAction =
|
||||
downloadHelper.getDownloadAction(Util.getUtf8Bytes(name), selectedTrackKeys);
|
||||
startDownload(downloadAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,14 +16,14 @@
|
||||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
@ -48,6 +48,7 @@ import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer2.offline.FilteringManifestParser;
|
||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
@ -57,14 +58,15 @@ import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.FilteringDashManifestParser;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.FilteringHlsPlaylistParser;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.RenditionKey;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.FilteringSsManifestParser;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.TrackKey;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.StreamKey;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
@ -74,11 +76,12 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.DebugTextViewHelper;
|
||||
import com.google.android.exoplayer2.ui.PlayerControlView;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.ui.TrackSelectionView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
||||
import com.google.android.exoplayer2.util.EventLogger;
|
||||
import com.google.android.exoplayer2.util.ParcelableArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.CookieHandler;
|
||||
@ -99,13 +102,11 @@ public class PlayerActivity extends Activity
|
||||
|
||||
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
|
||||
public static final String EXTENSION_EXTRA = "extension";
|
||||
public static final String MANIFEST_FILTER_EXTRA = "manifest_filter";
|
||||
|
||||
public static final String ACTION_VIEW_LIST =
|
||||
"com.google.android.exoplayer.demo.action.VIEW_LIST";
|
||||
public static final String URI_LIST_EXTRA = "uri_list";
|
||||
public static final String EXTENSION_LIST_EXTRA = "extension_list";
|
||||
public static final String MANIFEST_FILTER_LIST_EXTRA = "manifest_filter_list";
|
||||
|
||||
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
|
||||
|
||||
@ -116,6 +117,12 @@ public class PlayerActivity extends Activity
|
||||
// For backwards compatibility only.
|
||||
private static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
|
||||
|
||||
// Saved instance state keys.
|
||||
private static final String KEY_TRACK_SELECTOR_PARAMETERS = "track_selector_parameters";
|
||||
private static final String KEY_WINDOW = "window";
|
||||
private static final String KEY_POSITION = "position";
|
||||
private static final String KEY_AUTO_PLAY = "auto_play";
|
||||
|
||||
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
|
||||
private static final CookieManager DEFAULT_COOKIE_MANAGER;
|
||||
static {
|
||||
@ -123,7 +130,6 @@ public class PlayerActivity extends Activity
|
||||
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
}
|
||||
|
||||
private Handler mainHandler;
|
||||
private PlayerView playerView;
|
||||
private LinearLayout debugRootView;
|
||||
private TextView debugTextView;
|
||||
@ -132,14 +138,13 @@ public class PlayerActivity extends Activity
|
||||
private SimpleExoPlayer player;
|
||||
private MediaSource mediaSource;
|
||||
private DefaultTrackSelector trackSelector;
|
||||
private TrackSelectionHelper trackSelectionHelper;
|
||||
private DefaultTrackSelector.Parameters trackSelectorParameters;
|
||||
private DebugTextViewHelper debugViewHelper;
|
||||
private boolean inErrorState;
|
||||
private TrackGroupArray lastSeenTrackGroupArray;
|
||||
|
||||
private boolean shouldAutoPlay;
|
||||
private int resumeWindow;
|
||||
private long resumePosition;
|
||||
private boolean startAutoPlay;
|
||||
private int startWindow;
|
||||
private long startPosition;
|
||||
|
||||
// Fields used only for ad playback. The ads loader is loaded via reflection.
|
||||
|
||||
@ -152,10 +157,7 @@ public class PlayerActivity extends Activity
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
shouldAutoPlay = true;
|
||||
clearResumePosition();
|
||||
mediaDataSourceFactory = buildDataSourceFactory(true);
|
||||
mainHandler = new Handler();
|
||||
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
|
||||
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
|
||||
}
|
||||
@ -168,14 +170,24 @@ public class PlayerActivity extends Activity
|
||||
|
||||
playerView = findViewById(R.id.player_view);
|
||||
playerView.setControllerVisibilityListener(this);
|
||||
playerView.setErrorMessageProvider(new PlayerErrorMessageProvider());
|
||||
playerView.requestFocus();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
trackSelectorParameters = savedInstanceState.getParcelable(KEY_TRACK_SELECTOR_PARAMETERS);
|
||||
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
|
||||
startWindow = savedInstanceState.getInt(KEY_WINDOW);
|
||||
startPosition = savedInstanceState.getLong(KEY_POSITION);
|
||||
} else {
|
||||
trackSelectorParameters = new DefaultTrackSelector.ParametersBuilder().build();
|
||||
clearStartPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
releasePlayer();
|
||||
shouldAutoPlay = true;
|
||||
clearResumePosition();
|
||||
clearStartPosition();
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
@ -220,17 +232,27 @@ public class PlayerActivity extends Activity
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
if (grantResults.length > 0) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
initializePlayer();
|
||||
} else {
|
||||
showToast(R.string.storage_permission_denied);
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
if (grantResults.length == 0) {
|
||||
// Empty results are triggered if a permission is requested while another request was already
|
||||
// pending and can be safely ignored in this case.
|
||||
return;
|
||||
}
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
initializePlayer();
|
||||
} else {
|
||||
showToast(R.string.storage_permission_denied);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
updateTrackSelectorParameters();
|
||||
updateStartPosition();
|
||||
outState.putParcelable(KEY_TRACK_SELECTOR_PARAMETERS, trackSelectorParameters);
|
||||
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
|
||||
outState.putInt(KEY_WINDOW, startWindow);
|
||||
outState.putLong(KEY_POSITION, startPosition);
|
||||
}
|
||||
|
||||
// Activity input
|
||||
@ -248,8 +270,19 @@ public class PlayerActivity extends Activity
|
||||
if (view.getParent() == debugRootView) {
|
||||
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
|
||||
if (mappedTrackInfo != null) {
|
||||
trackSelectionHelper.showSelectionDialog(
|
||||
this, ((Button) view).getText(), mappedTrackInfo, (int) view.getTag());
|
||||
CharSequence title = ((Button) view).getText();
|
||||
int rendererIndex = (int) view.getTag();
|
||||
int rendererType = mappedTrackInfo.getRendererType(rendererIndex);
|
||||
boolean allowAdaptiveSelections =
|
||||
rendererType == C.TRACK_TYPE_VIDEO
|
||||
|| (rendererType == C.TRACK_TYPE_AUDIO
|
||||
&& mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO)
|
||||
== MappedTrackInfo.RENDERER_SUPPORT_NO_TRACKS);
|
||||
Pair<AlertDialog, TrackSelectionView> dialogPair =
|
||||
TrackSelectionView.getDialog(this, title, trackSelector, rendererIndex);
|
||||
dialogPair.second.setShowDisableOption(true);
|
||||
dialogPair.second.setAllowAdaptiveSelections(allowAdaptiveSelections);
|
||||
dialogPair.first.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,11 +309,9 @@ public class PlayerActivity extends Activity
|
||||
String action = intent.getAction();
|
||||
Uri[] uris;
|
||||
String[] extensions;
|
||||
Parcelable[] manifestFilters;
|
||||
if (ACTION_VIEW.equals(action)) {
|
||||
uris = new Uri[] {intent.getData()};
|
||||
extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)};
|
||||
manifestFilters = new Parcelable[] {intent.getParcelableExtra(MANIFEST_FILTER_EXTRA)};
|
||||
} else if (ACTION_VIEW_LIST.equals(action)) {
|
||||
String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
|
||||
uris = new Uri[uriStrings.length];
|
||||
@ -291,10 +322,6 @@ public class PlayerActivity extends Activity
|
||||
if (extensions == null) {
|
||||
extensions = new String[uriStrings.length];
|
||||
}
|
||||
manifestFilters = intent.getParcelableArrayExtra(MANIFEST_FILTER_LIST_EXTRA);
|
||||
if (manifestFilters == null) {
|
||||
manifestFilters = new Parcelable[uriStrings.length];
|
||||
}
|
||||
} else {
|
||||
showToast(getString(R.string.unexpected_intent_action, action));
|
||||
finish();
|
||||
@ -361,23 +388,14 @@ public class PlayerActivity extends Activity
|
||||
new DefaultRenderersFactory(this, extensionRendererMode);
|
||||
|
||||
trackSelector = new DefaultTrackSelector(trackSelectionFactory);
|
||||
trackSelectionHelper = new TrackSelectionHelper(trackSelector, trackSelectionFactory);
|
||||
trackSelector.setParameters(trackSelectorParameters);
|
||||
lastSeenTrackGroupArray = null;
|
||||
|
||||
player =
|
||||
ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, drmSessionManager);
|
||||
player.addListener(new PlayerEventListener());
|
||||
|
||||
EventLogger eventLogger = new EventLogger(trackSelector);
|
||||
player.addListener(eventLogger);
|
||||
player.addMetadataOutput(eventLogger);
|
||||
player.addAudioDebugListener(eventLogger);
|
||||
player.addVideoDebugListener(eventLogger);
|
||||
if (drmSessionManager != null) {
|
||||
drmSessionManager.addListener(mainHandler, eventLogger);
|
||||
}
|
||||
|
||||
player.setPlayWhenReady(shouldAutoPlay);
|
||||
player.setPlayWhenReady(startAutoPlay);
|
||||
player.addAnalyticsListener(new EventLogger(trackSelector));
|
||||
playerView.setPlayer(player);
|
||||
playerView.setPlaybackPreparer(this);
|
||||
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||
@ -385,9 +403,7 @@ public class PlayerActivity extends Activity
|
||||
|
||||
MediaSource[] mediaSources = new MediaSource[uris.length];
|
||||
for (int i = 0; i < uris.length; i++) {
|
||||
ParcelableArray<?> manifestFilter = (ParcelableArray<?>) manifestFilters[i];
|
||||
List<?> filter = manifestFilter != null ? manifestFilter.asList() : null;
|
||||
mediaSources[i] = buildMediaSource(uris[i], extensions[i], filter);
|
||||
mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
|
||||
}
|
||||
mediaSource =
|
||||
mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
|
||||
@ -398,8 +414,7 @@ public class PlayerActivity extends Activity
|
||||
releaseAdsLoader();
|
||||
loadedAdTagUri = adTagUri;
|
||||
}
|
||||
MediaSource adsMediaSource =
|
||||
createAdsMediaSource(mediaSource, Uri.parse(adTagUriString), eventLogger);
|
||||
MediaSource adsMediaSource = createAdsMediaSource(mediaSource, Uri.parse(adTagUriString));
|
||||
if (adsMediaSource != null) {
|
||||
mediaSource = adsMediaSource;
|
||||
} else {
|
||||
@ -408,24 +423,21 @@ public class PlayerActivity extends Activity
|
||||
} else {
|
||||
releaseAdsLoader();
|
||||
}
|
||||
mediaSource.addEventListener(mainHandler, eventLogger);
|
||||
}
|
||||
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
||||
if (haveResumePosition) {
|
||||
player.seekTo(resumeWindow, resumePosition);
|
||||
boolean haveStartPosition = startWindow != C.INDEX_UNSET;
|
||||
if (haveStartPosition) {
|
||||
player.seekTo(startWindow, startPosition);
|
||||
}
|
||||
player.prepare(mediaSource, !haveResumePosition, false);
|
||||
inErrorState = false;
|
||||
player.prepare(mediaSource, !haveStartPosition, false);
|
||||
updateButtonVisibilities();
|
||||
}
|
||||
|
||||
private MediaSource buildMediaSource(Uri uri) {
|
||||
return buildMediaSource(uri, null, null);
|
||||
return buildMediaSource(uri, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private MediaSource buildMediaSource(
|
||||
Uri uri, @Nullable String overrideExtension, @Nullable List<?> manifestFilter) {
|
||||
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
|
||||
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
||||
switch (type) {
|
||||
case C.TYPE_DASH:
|
||||
@ -433,17 +445,22 @@ public class PlayerActivity extends Activity
|
||||
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
|
||||
buildDataSourceFactory(false))
|
||||
.setManifestParser(
|
||||
new FilteringDashManifestParser((List<RepresentationKey>) manifestFilter))
|
||||
new FilteringManifestParser<>(
|
||||
new DashManifestParser(), (List<RepresentationKey>) getOfflineStreamKeys(uri)))
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_SS:
|
||||
return new SsMediaSource.Factory(
|
||||
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
|
||||
buildDataSourceFactory(false))
|
||||
.setManifestParser(new FilteringSsManifestParser((List<TrackKey>) manifestFilter))
|
||||
.setManifestParser(
|
||||
new FilteringManifestParser<>(
|
||||
new SsManifestParser(), (List<StreamKey>) getOfflineStreamKeys(uri)))
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_HLS:
|
||||
return new HlsMediaSource.Factory(mediaDataSourceFactory)
|
||||
.setPlaylistParser(new FilteringHlsPlaylistParser((List<String>) manifestFilter))
|
||||
.setPlaylistParser(
|
||||
new FilteringManifestParser<>(
|
||||
new HlsPlaylistParser(), (List<RenditionKey>) getOfflineStreamKeys(uri)))
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_OTHER:
|
||||
return new ExtractorMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
|
||||
@ -453,45 +470,58 @@ public class PlayerActivity extends Activity
|
||||
}
|
||||
}
|
||||
|
||||
private List<?> getOfflineStreamKeys(Uri uri) {
|
||||
return ((DemoApplication) getApplication()).getDownloadTracker().getOfflineStreamKeys(uri);
|
||||
}
|
||||
|
||||
private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
|
||||
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
|
||||
throws UnsupportedDrmException {
|
||||
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||
buildHttpDataSourceFactory(false));
|
||||
HttpDataSource.Factory licenseDataSourceFactory =
|
||||
((DemoApplication) getApplication()).buildHttpDataSourceFactory(/* listener= */ null);
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory);
|
||||
if (keyRequestPropertiesArray != null) {
|
||||
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
|
||||
keyRequestPropertiesArray[i + 1]);
|
||||
}
|
||||
}
|
||||
DefaultDrmSessionManager<FrameworkMediaCrypto> drmSessionManager =
|
||||
new DefaultDrmSessionManager<>(
|
||||
uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, null, multiSession);
|
||||
return drmSessionManager;
|
||||
return new DefaultDrmSessionManager<>(
|
||||
uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, null, multiSession);
|
||||
}
|
||||
|
||||
private void releasePlayer() {
|
||||
if (player != null) {
|
||||
updateTrackSelectorParameters();
|
||||
updateStartPosition();
|
||||
debugViewHelper.stop();
|
||||
debugViewHelper = null;
|
||||
shouldAutoPlay = player.getPlayWhenReady();
|
||||
updateResumePosition();
|
||||
player.release();
|
||||
player = null;
|
||||
mediaSource = null;
|
||||
trackSelector = null;
|
||||
trackSelectionHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateResumePosition() {
|
||||
resumeWindow = player.getCurrentWindowIndex();
|
||||
resumePosition = Math.max(0, player.getContentPosition());
|
||||
private void updateTrackSelectorParameters() {
|
||||
if (trackSelector != null) {
|
||||
trackSelectorParameters = trackSelector.getParameters();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearResumePosition() {
|
||||
resumeWindow = C.INDEX_UNSET;
|
||||
resumePosition = C.TIME_UNSET;
|
||||
private void updateStartPosition() {
|
||||
if (player != null) {
|
||||
startAutoPlay = player.getPlayWhenReady();
|
||||
startWindow = player.getCurrentWindowIndex();
|
||||
startPosition = Math.max(0, player.getContentPosition());
|
||||
}
|
||||
}
|
||||
|
||||
private void clearStartPosition() {
|
||||
startAutoPlay = true;
|
||||
startWindow = C.INDEX_UNSET;
|
||||
startPosition = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -506,21 +536,8 @@ public class PlayerActivity extends Activity
|
||||
.buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new HttpDataSource factory.
|
||||
*
|
||||
* @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new
|
||||
* DataSource factory.
|
||||
* @return A new HttpDataSource factory.
|
||||
*/
|
||||
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
|
||||
return ((DemoApplication) getApplication())
|
||||
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||
}
|
||||
|
||||
/** Returns an ads media source, reusing the ads loader if one exists. */
|
||||
private @Nullable MediaSource createAdsMediaSource(
|
||||
MediaSource mediaSource, Uri adTagUri, EventLogger eventLogger) {
|
||||
private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) {
|
||||
// Load the extension source using reflection so the demo app doesn't have to depend on it.
|
||||
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
|
||||
try {
|
||||
@ -550,8 +567,7 @@ public class PlayerActivity extends Activity
|
||||
return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
|
||||
}
|
||||
};
|
||||
return new AdsMediaSource(
|
||||
mediaSource, adMediaSourceFactory, adsLoader, adUiViewGroup, mainHandler, eventLogger);
|
||||
return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, adUiViewGroup);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// IMA extension not loaded.
|
||||
return null;
|
||||
@ -582,20 +598,20 @@ public class PlayerActivity extends Activity
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mappedTrackInfo.length; i++) {
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i);
|
||||
if (trackGroups.length != 0) {
|
||||
Button button = new Button(this);
|
||||
int label;
|
||||
switch (player.getRendererType(i)) {
|
||||
case C.TRACK_TYPE_AUDIO:
|
||||
label = R.string.audio;
|
||||
label = R.string.exo_track_selection_title_audio;
|
||||
break;
|
||||
case C.TRACK_TYPE_VIDEO:
|
||||
label = R.string.video;
|
||||
label = R.string.exo_track_selection_title_video;
|
||||
break;
|
||||
case C.TRACK_TYPE_TEXT:
|
||||
label = R.string.text;
|
||||
label = R.string.exo_track_selection_title_text;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
@ -646,48 +662,20 @@ public class PlayerActivity extends Activity
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
|
||||
if (inErrorState) {
|
||||
// This will only occur if the user has performed a seek whilst in the error state. Update
|
||||
// the resume position so that if the user then retries, playback will resume from the
|
||||
// position to which they seeked.
|
||||
updateResumePosition();
|
||||
if (player.getPlaybackError() != null) {
|
||||
// The user has performed a seek whilst in the error state. Update the resume position so
|
||||
// that if the user then retries, playback resumes from the position to which they seeked.
|
||||
updateStartPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException e) {
|
||||
String errorString = null;
|
||||
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
||||
Exception cause = e.getRendererException();
|
||||
if (cause instanceof DecoderInitializationException) {
|
||||
// Special case for decoder initialization failures.
|
||||
DecoderInitializationException decoderInitializationException =
|
||||
(DecoderInitializationException) cause;
|
||||
if (decoderInitializationException.decoderName == null) {
|
||||
if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
|
||||
errorString = getString(R.string.error_querying_decoders);
|
||||
} else if (decoderInitializationException.secureDecoderRequired) {
|
||||
errorString = getString(R.string.error_no_secure_decoder,
|
||||
decoderInitializationException.mimeType);
|
||||
} else {
|
||||
errorString = getString(R.string.error_no_decoder,
|
||||
decoderInitializationException.mimeType);
|
||||
}
|
||||
} else {
|
||||
errorString = getString(R.string.error_instantiating_decoder,
|
||||
decoderInitializationException.decoderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errorString != null) {
|
||||
showToast(errorString);
|
||||
}
|
||||
inErrorState = true;
|
||||
if (isBehindLiveWindow(e)) {
|
||||
clearResumePosition();
|
||||
clearStartPosition();
|
||||
initializePlayer();
|
||||
} else {
|
||||
updateResumePosition();
|
||||
updateStartPosition();
|
||||
updateButtonVisibilities();
|
||||
showControls();
|
||||
}
|
||||
@ -700,11 +688,11 @@ public class PlayerActivity extends Activity
|
||||
if (trackGroups != lastSeenTrackGroupArray) {
|
||||
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
|
||||
if (mappedTrackInfo != null) {
|
||||
if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_VIDEO)
|
||||
if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO)
|
||||
== MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
|
||||
showToast(R.string.error_unsupported_video);
|
||||
}
|
||||
if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_AUDIO)
|
||||
if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO)
|
||||
== MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
|
||||
showToast(R.string.error_unsupported_audio);
|
||||
}
|
||||
@ -712,7 +700,40 @@ public class PlayerActivity extends Activity
|
||||
lastSeenTrackGroupArray = trackGroups;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerErrorMessageProvider implements ErrorMessageProvider<ExoPlaybackException> {
|
||||
|
||||
@Override
|
||||
public Pair<Integer, String> getErrorMessage(ExoPlaybackException e) {
|
||||
String errorString = getString(R.string.error_generic);
|
||||
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
||||
Exception cause = e.getRendererException();
|
||||
if (cause instanceof DecoderInitializationException) {
|
||||
// Special case for decoder initialization failures.
|
||||
DecoderInitializationException decoderInitializationException =
|
||||
(DecoderInitializationException) cause;
|
||||
if (decoderInitializationException.decoderName == null) {
|
||||
if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
|
||||
errorString = getString(R.string.error_querying_decoders);
|
||||
} else if (decoderInitializationException.secureDecoderRequired) {
|
||||
errorString =
|
||||
getString(
|
||||
R.string.error_no_secure_decoder, decoderInitializationException.mimeType);
|
||||
} else {
|
||||
errorString =
|
||||
getString(R.string.error_no_decoder, decoderInitializationException.mimeType);
|
||||
}
|
||||
} else {
|
||||
errorString =
|
||||
getString(
|
||||
R.string.error_instantiating_decoder,
|
||||
decoderInitializationException.decoderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair.create(0, errorString);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,15 +24,17 @@ import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.JsonReader;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseExpandableListAdapter;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ExpandableListView.OnChildClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.offline.DownloadService;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
@ -44,19 +46,27 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An activity for selecting from a list of samples.
|
||||
*/
|
||||
public class SampleChooserActivity extends Activity {
|
||||
/** An activity for selecting from a list of media samples. */
|
||||
public class SampleChooserActivity extends Activity
|
||||
implements DownloadTracker.Listener, OnChildClickListener {
|
||||
|
||||
private static final String TAG = "SampleChooserActivity";
|
||||
|
||||
private DownloadTracker downloadTracker;
|
||||
private SampleAdapter sampleAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.sample_chooser_activity);
|
||||
sampleAdapter = new SampleAdapter();
|
||||
ExpandableListView sampleListView = findViewById(R.id.sample_list);
|
||||
sampleListView.setAdapter(sampleAdapter);
|
||||
sampleListView.setOnChildClickListener(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String dataUri = intent.getDataString();
|
||||
String[] uris;
|
||||
@ -79,8 +89,32 @@ public class SampleChooserActivity extends Activity {
|
||||
uriList.toArray(uris);
|
||||
Arrays.sort(uris);
|
||||
}
|
||||
|
||||
downloadTracker = ((DemoApplication) getApplication()).getDownloadTracker();
|
||||
SampleListLoader loaderTask = new SampleListLoader();
|
||||
loaderTask.execute(uris);
|
||||
|
||||
// Ping the download service in case it's not running (but should be).
|
||||
startService(
|
||||
new Intent(this, DemoDownloadService.class).setAction(DownloadService.ACTION_INIT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
downloadTracker.addListener(this);
|
||||
sampleAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
downloadTracker.removeListener(this);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadsChanged() {
|
||||
sampleAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void onSampleGroups(final List<SampleGroup> groups, boolean sawError) {
|
||||
@ -88,20 +122,44 @@ public class SampleChooserActivity extends Activity {
|
||||
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
ExpandableListView sampleList = findViewById(R.id.sample_list);
|
||||
sampleList.setAdapter(new SampleAdapter(this, groups));
|
||||
sampleList.setOnChildClickListener(new OnChildClickListener() {
|
||||
@Override
|
||||
public boolean onChildClick(ExpandableListView parent, View view, int groupPosition,
|
||||
int childPosition, long id) {
|
||||
onSampleSelected(groups.get(groupPosition).samples.get(childPosition));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
sampleAdapter.setSampleGroups(groups);
|
||||
}
|
||||
|
||||
private void onSampleSelected(Sample sample) {
|
||||
@Override
|
||||
public boolean onChildClick(
|
||||
ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
|
||||
Sample sample = (Sample) view.getTag();
|
||||
startActivity(sample.buildIntent(this));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSampleDownloadButtonClicked(Sample sample) {
|
||||
int downloadUnsupportedStringId = getDownloadUnsupportedStringId(sample);
|
||||
if (downloadUnsupportedStringId != 0) {
|
||||
Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} else {
|
||||
UriSample uriSample = (UriSample) sample;
|
||||
downloadTracker.toggleDownload(this, sample.name, uriSample.uri, uriSample.extension);
|
||||
}
|
||||
}
|
||||
|
||||
private int getDownloadUnsupportedStringId(Sample sample) {
|
||||
if (sample instanceof PlaylistSample) {
|
||||
return R.string.download_playlist_unsupported;
|
||||
}
|
||||
UriSample uriSample = (UriSample) sample;
|
||||
if (uriSample.drmInfo != null) {
|
||||
return R.string.download_drm_unsupported;
|
||||
}
|
||||
if (uriSample.adTagUri != null) {
|
||||
return R.string.download_ads_unsupported;
|
||||
}
|
||||
String scheme = uriSample.uri.getScheme();
|
||||
if (!("http".equals(scheme) || "https".equals(scheme))) {
|
||||
return R.string.download_scheme_unsupported;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private final class SampleListLoader extends AsyncTask<String, Void, List<SampleGroup>> {
|
||||
@ -175,7 +233,7 @@ public class SampleChooserActivity extends Activity {
|
||||
|
||||
private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
|
||||
String sampleName = null;
|
||||
String uri = null;
|
||||
Uri uri = null;
|
||||
String extension = null;
|
||||
String drmScheme = null;
|
||||
String drmLicenseUrl = null;
|
||||
@ -194,7 +252,7 @@ public class SampleChooserActivity extends Activity {
|
||||
sampleName = reader.nextString();
|
||||
break;
|
||||
case "uri":
|
||||
uri = reader.nextString();
|
||||
uri = Uri.parse(reader.nextString());
|
||||
break;
|
||||
case "extension":
|
||||
extension = reader.nextString();
|
||||
@ -278,14 +336,17 @@ public class SampleChooserActivity extends Activity {
|
||||
|
||||
}
|
||||
|
||||
private static final class SampleAdapter extends BaseExpandableListAdapter {
|
||||
private final class SampleAdapter extends BaseExpandableListAdapter implements OnClickListener {
|
||||
|
||||
private final Context context;
|
||||
private final List<SampleGroup> sampleGroups;
|
||||
private List<SampleGroup> sampleGroups;
|
||||
|
||||
public SampleAdapter(Context context, List<SampleGroup> sampleGroups) {
|
||||
this.context = context;
|
||||
public SampleAdapter() {
|
||||
sampleGroups = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void setSampleGroups(List<SampleGroup> sampleGroups) {
|
||||
this.sampleGroups = sampleGroups;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -303,10 +364,12 @@ public class SampleChooserActivity extends Activity {
|
||||
View convertView, ViewGroup parent) {
|
||||
View view = convertView;
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent,
|
||||
false);
|
||||
view = getLayoutInflater().inflate(R.layout.sample_list_item, parent, false);
|
||||
View downloadButton = view.findViewById(R.id.download_button);
|
||||
downloadButton.setOnClickListener(this);
|
||||
downloadButton.setFocusable(false);
|
||||
}
|
||||
((TextView) view).setText(getChild(groupPosition, childPosition).name);
|
||||
initializeChildView(view, getChild(groupPosition, childPosition));
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -330,8 +393,9 @@ public class SampleChooserActivity extends Activity {
|
||||
ViewGroup parent) {
|
||||
View view = convertView;
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(context).inflate(android.R.layout.simple_expandable_list_item_1,
|
||||
parent, false);
|
||||
view =
|
||||
getLayoutInflater()
|
||||
.inflate(android.R.layout.simple_expandable_list_item_1, parent, false);
|
||||
}
|
||||
((TextView) view).setText(getGroup(groupPosition).title);
|
||||
return view;
|
||||
@ -352,6 +416,25 @@ public class SampleChooserActivity extends Activity {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
onSampleDownloadButtonClicked((Sample) view.getTag());
|
||||
}
|
||||
|
||||
private void initializeChildView(View view, Sample sample) {
|
||||
view.setTag(sample);
|
||||
TextView sampleTitle = view.findViewById(R.id.sample_title);
|
||||
sampleTitle.setText(sample.name);
|
||||
|
||||
boolean canDownload = getDownloadUnsupportedStringId(sample) == 0;
|
||||
boolean isDownloaded = canDownload && downloadTracker.isDownloaded(((UriSample) sample).uri);
|
||||
ImageButton downloadButton = view.findViewById(R.id.download_button);
|
||||
downloadButton.setTag(sample);
|
||||
downloadButton.setColorFilter(
|
||||
canDownload ? (isDownloaded ? 0xFF42A5F5 : 0xFFBDBDBD) : 0xFFEEEEEE);
|
||||
downloadButton.setImageResource(
|
||||
isDownloaded ? R.drawable.ic_download_done : R.drawable.ic_download);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SampleGroup {
|
||||
@ -420,7 +503,7 @@ public class SampleChooserActivity extends Activity {
|
||||
|
||||
private static final class UriSample extends Sample {
|
||||
|
||||
public final String uri;
|
||||
public final Uri uri;
|
||||
public final String extension;
|
||||
public final String adTagUri;
|
||||
|
||||
@ -429,7 +512,7 @@ public class SampleChooserActivity extends Activity {
|
||||
boolean preferExtensionDecoders,
|
||||
String abrAlgorithm,
|
||||
DrmInfo drmInfo,
|
||||
String uri,
|
||||
Uri uri,
|
||||
String extension,
|
||||
String adTagUri) {
|
||||
super(name, preferExtensionDecoders, abrAlgorithm, drmInfo);
|
||||
@ -441,7 +524,7 @@ public class SampleChooserActivity extends Activity {
|
||||
@Override
|
||||
public Intent buildIntent(Context context) {
|
||||
return super.buildIntent(context)
|
||||
.setData(Uri.parse(uri))
|
||||
.setData(uri)
|
||||
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
|
||||
.putExtra(PlayerActivity.AD_TAG_URI_EXTRA, adTagUri)
|
||||
.setAction(PlayerActivity.ACTION_VIEW);
|
||||
@ -468,7 +551,7 @@ public class SampleChooserActivity extends Activity {
|
||||
String[] uris = new String[children.length];
|
||||
String[] extensions = new String[children.length];
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
uris[i] = children[i].uri;
|
||||
uris[i] = children[i].uri.toString();
|
||||
extensions[i] = children[i].extension;
|
||||
}
|
||||
return super.buildIntent(context)
|
||||
|
@ -1,255 +0,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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
BIN
demos/main/src/main/res/drawable-hdpi/ic_download.png
Normal file
After Width: | Height: | Size: 199 B |
BIN
demos/main/src/main/res/drawable-hdpi/ic_download_done.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
demos/main/src/main/res/drawable-mdpi/ic_download.png
Normal file
After Width: | Height: | Size: 163 B |
BIN
demos/main/src/main/res/drawable-mdpi/ic_download_done.png
Normal file
After Width: | Height: | Size: 182 B |
BIN
demos/main/src/main/res/drawable-xhdpi/ic_download.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
demos/main/src/main/res/drawable-xhdpi/ic_download_done.png
Normal file
After Width: | Height: | Size: 304 B |
BIN
demos/main/src/main/res/drawable-xxhdpi/ic_download.png
Normal file
After Width: | Height: | Size: 303 B |
BIN
demos/main/src/main/res/drawable-xxhdpi/ic_download_done.png
Normal file
After Width: | Height: | Size: 450 B |
BIN
demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
Normal file
After Width: | Height: | Size: 304 B |
BIN
demos/main/src/main/res/drawable-xxxhdpi/ic_download_done.png
Normal file
After Width: | Height: | Size: 575 B |
38
demos/main/src/main/res/layout/sample_list_item.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?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>
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
<?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.
|
||||
@ -14,8 +13,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="exo_media_action_repeat_all_description">"Repetir todo"</string>
|
||||
<string name="exo_media_action_repeat_off_description">"Non repetir"</string>
|
||||
<string name="exo_media_action_repeat_one_description">"Repetir un"</string>
|
||||
</resources>
|
||||
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/representation_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
@ -17,18 +17,10 @@
|
||||
|
||||
<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="error_generic">Playback failed</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>
|
||||
@ -55,4 +47,14 @@
|
||||
|
||||
<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>
|
||||
|
@ -19,6 +19,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
@ -307,6 +308,11 @@ public final class CastPlayer implements Player {
|
||||
return playbackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExoPlaybackException getPlaybackError() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayWhenReady(boolean playWhenReady) {
|
||||
if (remoteMediaClient == null) {
|
||||
|
@ -280,6 +280,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
|
||||
new SocketTimeoutException(), dataSpec, getStatus(currentUrlRequest));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new OpenException(new InterruptedIOException(e), dataSpec, Status.INVALID);
|
||||
}
|
||||
|
||||
@ -352,17 +353,18 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
|
||||
if (!operation.block(readTimeoutMs)) {
|
||||
throw new SocketTimeoutException();
|
||||
}
|
||||
} catch (InterruptedException | SocketTimeoutException e) {
|
||||
// If we're timing out or getting interrupted, the operation is still ongoing.
|
||||
// So we'll need to replace readBuffer to avoid the possibility of it being written to by
|
||||
// this operation during a subsequent request.
|
||||
} catch (InterruptedException e) {
|
||||
// The operation is ongoing so replace readBuffer to avoid it being written to by this
|
||||
// operation during a subsequent request.
|
||||
readBuffer = null;
|
||||
Thread.currentThread().interrupt();
|
||||
throw new HttpDataSourceException(
|
||||
e instanceof InterruptedException
|
||||
? new InterruptedIOException((InterruptedException) e)
|
||||
: (SocketTimeoutException) e,
|
||||
currentDataSpec,
|
||||
HttpDataSourceException.TYPE_READ);
|
||||
new InterruptedIOException(e), currentDataSpec, HttpDataSourceException.TYPE_READ);
|
||||
} catch (SocketTimeoutException e) {
|
||||
// The operation is ongoing so replace readBuffer to avoid it being written to by this
|
||||
// operation during a subsequent request.
|
||||
readBuffer = null;
|
||||
throw new HttpDataSourceException(e, currentDataSpec, HttpDataSourceException.TYPE_READ);
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
|
@ -21,6 +21,7 @@ import android.util.Log;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@ -86,7 +87,7 @@ public final class CronetEngineWrapper {
|
||||
public CronetEngineWrapper(Context context, boolean preferGMSCoreCronet) {
|
||||
CronetEngine cronetEngine = null;
|
||||
@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
|
||||
for (int i = cronetProviders.size() - 1; i >= 0; i--) {
|
||||
if (!cronetProviders.get(i).isEnabled()
|
||||
|
@ -74,7 +74,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
|
||||
*/
|
||||
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
|
||||
AudioSink audioSink, boolean enableFloatOutput) {
|
||||
super(eventHandler, eventListener, null, false, audioSink);
|
||||
super(
|
||||
eventHandler,
|
||||
eventListener,
|
||||
/* drmSessionManager= */ null,
|
||||
/* playClearSamplesWithoutKeys= */ false,
|
||||
audioSink);
|
||||
this.enableFloatOutput = enableFloatOutput;
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ dependencies {
|
||||
implementation 'com.android.support:support-annotations:' + supportLibraryVersion
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
androidTestImplementation project(modulePrefix + 'testutils')
|
||||
testImplementation project(modulePrefix + 'testutils-robolectric')
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -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 final Id3Peeker id3Peeker;
|
||||
private final @Flags int flags;
|
||||
private final boolean isId3MetadataDisabled;
|
||||
|
||||
private FlacDecoderJni decoderJni;
|
||||
|
||||
@ -90,7 +90,6 @@ public final class FlacExtractor implements Extractor {
|
||||
private ByteBuffer outputByteBuffer;
|
||||
|
||||
private Metadata id3Metadata;
|
||||
private long id3SectionSize;
|
||||
|
||||
private boolean metadataParsed;
|
||||
|
||||
@ -105,8 +104,8 @@ public final class FlacExtractor implements Extractor {
|
||||
* @param flags Flags that control the extractor's behavior.
|
||||
*/
|
||||
public FlacExtractor(int flags) {
|
||||
this.flags = flags;
|
||||
id3Peeker = new Id3Peeker();
|
||||
isId3MetadataDisabled = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,24 +124,16 @@ public final class FlacExtractor implements Extractor {
|
||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||
if (input.getPosition() == 0) {
|
||||
id3Metadata = peekId3Data(input);
|
||||
id3SectionSize = input.getPeekPosition();
|
||||
}
|
||||
boolean isFlacFormat = peekFlacSignature(input);
|
||||
if (isFlacFormat) {
|
||||
// If this is FLAC format, we should skip the whole ID3 section.
|
||||
skipFullyId3Section(input);
|
||||
}
|
||||
return isFlacFormat;
|
||||
return peekFlacSignature(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final ExtractorInput input, PositionHolder seekPosition)
|
||||
throws IOException, InterruptedException {
|
||||
if (input.getPosition() == 0) {
|
||||
if (input.getPosition() == 0 && !isId3MetadataDisabled && id3Metadata == null) {
|
||||
id3Metadata = peekId3Data(input);
|
||||
id3SectionSize = input.getPeekPosition();
|
||||
}
|
||||
skipFullyId3Section(input);
|
||||
|
||||
decoderJni.setData(input);
|
||||
|
||||
@ -155,7 +146,7 @@ public final class FlacExtractor implements Extractor {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
decoderJni.reset(0);
|
||||
input.setRetryPosition(id3SectionSize, e);
|
||||
input.setRetryPosition(0, e);
|
||||
throw e; // never executes
|
||||
}
|
||||
metadataParsed = true;
|
||||
@ -163,7 +154,7 @@ public final class FlacExtractor implements Extractor {
|
||||
boolean isSeekable = decoderJni.getSeekPosition(0) != -1;
|
||||
extractorOutput.seekMap(
|
||||
isSeekable
|
||||
? new FlacSeekMap(streamInfo.durationUs(), decoderJni, id3SectionSize)
|
||||
? new FlacSeekMap(streamInfo.durationUs(), decoderJni)
|
||||
: new SeekMap.Unseekable(streamInfo.durationUs(), 0));
|
||||
Format mediaFormat =
|
||||
Format.createAudioSampleFormat(
|
||||
@ -181,7 +172,7 @@ public final class FlacExtractor implements Extractor {
|
||||
/* drmInitData= */ null,
|
||||
/* selectionFlags= */ 0,
|
||||
/* language= */ null,
|
||||
(flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : id3Metadata);
|
||||
isId3MetadataDisabled ? null : id3Metadata);
|
||||
trackOutput.format(mediaFormat);
|
||||
|
||||
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
|
||||
@ -196,7 +187,7 @@ public final class FlacExtractor implements Extractor {
|
||||
} catch (IOException e) {
|
||||
if (lastDecodePosition >= 0) {
|
||||
decoderJni.reset(lastDecodePosition);
|
||||
input.setRetryPosition(id3SectionSize + lastDecodePosition, e);
|
||||
input.setRetryPosition(lastDecodePosition, e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
@ -212,12 +203,11 @@ public final class FlacExtractor implements Extractor {
|
||||
|
||||
@Override
|
||||
public void seek(long position, long timeUs) {
|
||||
if (position <= id3SectionSize) {
|
||||
if (position == 0) {
|
||||
metadataParsed = false;
|
||||
}
|
||||
long flacStreamPosition = Math.max(0, position - id3SectionSize);
|
||||
if (decoderJni != null) {
|
||||
decoderJni.reset(flacStreamPosition);
|
||||
decoderJni.reset(position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,9 +228,8 @@ public final class FlacExtractor implements Extractor {
|
||||
@Nullable
|
||||
private Metadata peekId3Data(ExtractorInput input) throws IOException, InterruptedException {
|
||||
input.resetPeekPosition();
|
||||
boolean disableId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
|
||||
Id3Decoder.FramePredicate id3FramePredicate =
|
||||
disableId3Frames ? Id3Decoder.NO_FRAMES_PREDICATE : null;
|
||||
isId3MetadataDisabled ? Id3Decoder.NO_FRAMES_PREDICATE : null;
|
||||
return id3Peeker.peekId3Data(input, id3FramePredicate);
|
||||
}
|
||||
|
||||
@ -255,22 +244,14 @@ public final class FlacExtractor implements Extractor {
|
||||
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 final long durationUs;
|
||||
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.decoderJni = decoderJni;
|
||||
this.id3SectionSize = id3SectionSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -281,8 +262,7 @@ public final class FlacExtractor implements Extractor {
|
||||
@Override
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
// TODO: Access the seek table via JNI to return two seek points when appropriate.
|
||||
return new SeekPoints(
|
||||
new SeekPoint(timeUs, id3SectionSize + decoderJni.getSeekPosition(timeUs)));
|
||||
return new SeekPoints(new SeekPoint(timeUs, decoderJni.getSeekPosition(timeUs)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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.AdState;
|
||||
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.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -80,6 +82,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
private final Context context;
|
||||
|
||||
private @Nullable ImaSdkSettings imaSdkSettings;
|
||||
private @Nullable AdEventListener adEventListener;
|
||||
private int vastLoadTimeoutMs;
|
||||
private int mediaLoadTimeoutMs;
|
||||
|
||||
@ -108,6 +111,18 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -144,7 +159,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
*/
|
||||
public ImaAdsLoader buildForAdTag(Uri adTagUri) {
|
||||
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
|
||||
*/
|
||||
public ImaAdsLoader buildForAdsResponse(String adsResponse) {
|
||||
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
|
||||
private final @Nullable String adsResponse;
|
||||
private final int vastLoadTimeoutMs;
|
||||
private final int mediaLoadTimeoutMs;
|
||||
private final @Nullable AdEventListener adEventListener;
|
||||
private final Timeline.Period period;
|
||||
private final List<VideoAdPlayerCallback> adCallbacks;
|
||||
private final ImaSdkFactory imaSdkFactory;
|
||||
@ -229,7 +257,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
private VideoProgressUpdate lastAdProgress;
|
||||
|
||||
private AdsManager adsManager;
|
||||
private AdErrorEvent pendingAdErrorEvent;
|
||||
private AdLoadException pendingAdLoadError;
|
||||
private Timeline timeline;
|
||||
private long contentDurationMs;
|
||||
private int podIndexOffset;
|
||||
@ -308,7 +336,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
/* imaSdkSettings= */ null,
|
||||
/* adsResponse= */ null,
|
||||
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
|
||||
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET);
|
||||
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
|
||||
/* adEventListener= */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -330,7 +359,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
imaSdkSettings,
|
||||
/* adsResponse= */ null,
|
||||
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
|
||||
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET);
|
||||
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
|
||||
/* adEventListener= */ null);
|
||||
}
|
||||
|
||||
private ImaAdsLoader(
|
||||
@ -339,12 +369,14 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
@Nullable ImaSdkSettings imaSdkSettings,
|
||||
@Nullable String adsResponse,
|
||||
int vastLoadTimeoutMs,
|
||||
int mediaLoadTimeoutMs) {
|
||||
int mediaLoadTimeoutMs,
|
||||
@Nullable AdEventListener adEventListener) {
|
||||
Assertions.checkArgument(adTagUri != null || adsResponse != null);
|
||||
this.adTagUri = adTagUri;
|
||||
this.adsResponse = adsResponse;
|
||||
this.vastLoadTimeoutMs = vastLoadTimeoutMs;
|
||||
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
|
||||
this.adEventListener = adEventListener;
|
||||
period = new Timeline.Period();
|
||||
adCallbacks = new ArrayList<>(1);
|
||||
imaSdkFactory = ImaSdkFactory.getInstance();
|
||||
@ -500,6 +532,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
this.adsManager = adsManager;
|
||||
adsManager.addAdErrorListener(this);
|
||||
adsManager.addAdEventListener(this);
|
||||
if (adEventListener != null) {
|
||||
adsManager.addAdEventListener(adEventListener);
|
||||
}
|
||||
if (player != null) {
|
||||
// If a player is attached already, start playback immediately.
|
||||
try {
|
||||
@ -544,13 +579,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
updateAdPlaybackState();
|
||||
} else if (isAdGroupLoadError(error)) {
|
||||
try {
|
||||
handleAdGroupLoadError();
|
||||
handleAdGroupLoadError(error);
|
||||
} catch (Exception e) {
|
||||
maybeNotifyInternalError("onAdError", e);
|
||||
}
|
||||
}
|
||||
if (pendingAdErrorEvent == null) {
|
||||
pendingAdErrorEvent = adErrorEvent;
|
||||
if (pendingAdLoadError == null) {
|
||||
pendingAdLoadError = AdLoadException.createForAllAds(error);
|
||||
}
|
||||
maybeNotifyPendingAdLoadError();
|
||||
}
|
||||
@ -937,9 +972,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
break;
|
||||
case LOG:
|
||||
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"))) {
|
||||
handleAdGroupLoadError();
|
||||
handleAdGroupLoadError(new IOException(message));
|
||||
}
|
||||
break;
|
||||
case ALL_ADS_COMPLETED:
|
||||
@ -1011,7 +1047,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAdGroupLoadError() {
|
||||
private void handleAdGroupLoadError(Exception error) {
|
||||
int adGroupIndex =
|
||||
this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex;
|
||||
if (adGroupIndex == C.INDEX_UNSET) {
|
||||
@ -1033,6 +1069,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
}
|
||||
}
|
||||
updateAdPlaybackState();
|
||||
if (pendingAdLoadError == null) {
|
||||
pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) {
|
||||
@ -1111,21 +1150,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
}
|
||||
|
||||
private void maybeNotifyPendingAdLoadError() {
|
||||
if (pendingAdErrorEvent != null) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onAdLoadError(
|
||||
new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError()));
|
||||
}
|
||||
pendingAdErrorEvent = null;
|
||||
if (pendingAdLoadError != null && eventListener != null) {
|
||||
eventListener.onAdLoadError(pendingAdLoadError, new DataSpec(adTagUri));
|
||||
pendingAdLoadError = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeNotifyInternalError(String name, Exception cause) {
|
||||
String message = "Internal error in " + name;
|
||||
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.
|
||||
if (adPlaybackState == null) {
|
||||
adPlaybackState = new AdPlaybackState();
|
||||
@ -1135,6 +1168,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
}
|
||||
}
|
||||
updateAdPlaybackState();
|
||||
if (eventListener != null) {
|
||||
eventListener.onAdLoadError(
|
||||
AdLoadException.createForUnexpected(new RuntimeException(message, cause)),
|
||||
new DataSpec(adTagUri));
|
||||
}
|
||||
}
|
||||
|
||||
private static long[] getAdGroupTimesUs(List<Float> cuePoints) {
|
||||
|
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.jobdispatcher;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@ -34,13 +32,8 @@ import com.google.android.exoplayer2.scheduler.Scheduler;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* A {@link Scheduler} which uses {@link com.firebase.jobdispatcher.FirebaseJobDispatcher} to
|
||||
* schedule a {@link Service} to be started when its requirements are met. The started service must
|
||||
* 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:
|
||||
* A {@link Scheduler} that uses {@link FirebaseJobDispatcher}. To use this scheduler, you must add
|
||||
* {@link JobDispatcherSchedulerService} to your manifest:
|
||||
*
|
||||
* <pre>{@literal
|
||||
* <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
@ -54,18 +47,6 @@ import com.google.android.exoplayer2.util.Util;
|
||||
* </service>
|
||||
* }</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
|
||||
* should be guarded with a call to {@code
|
||||
* GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)}
|
||||
@ -76,44 +57,37 @@ import com.google.android.exoplayer2.util.Util;
|
||||
public final class JobDispatcherScheduler implements Scheduler {
|
||||
|
||||
private static final String TAG = "JobDispatcherScheduler";
|
||||
private static final String SERVICE_ACTION = "SERVICE_ACTION";
|
||||
private static final String SERVICE_PACKAGE = "SERVICE_PACKAGE";
|
||||
private static final String REQUIREMENTS = "REQUIREMENTS";
|
||||
private static final String KEY_SERVICE_ACTION = "service_action";
|
||||
private static final String KEY_SERVICE_PACKAGE = "service_package";
|
||||
private static final String KEY_REQUIREMENTS = "requirements";
|
||||
|
||||
private final String jobTag;
|
||||
private final Job job;
|
||||
private final FirebaseJobDispatcher jobDispatcher;
|
||||
|
||||
/**
|
||||
* @param context Used to create a {@link FirebaseJobDispatcher} service.
|
||||
* @param requirements The requirements to execute the job.
|
||||
* @param jobTag Unique tag for the job. Using the same tag as a previous job can cause that job
|
||||
* to be replaced or canceled.
|
||||
* @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.
|
||||
* @param context A context.
|
||||
* @param jobTag A tag for jobs scheduled by this instance. If the same tag was used by a previous
|
||||
* instance, anything scheduled by the previous instance will be canceled by this instance if
|
||||
* {@link #schedule(Requirements, String, String)} or {@link #cancel()} are called.
|
||||
*/
|
||||
public JobDispatcherScheduler(
|
||||
Context context,
|
||||
Requirements requirements,
|
||||
String jobTag,
|
||||
String serviceAction,
|
||||
String servicePackage) {
|
||||
this.jobDispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context));
|
||||
public JobDispatcherScheduler(Context context, String jobTag) {
|
||||
this.jobDispatcher =
|
||||
new FirebaseJobDispatcher(new GooglePlayDriver(context.getApplicationContext()));
|
||||
this.jobTag = jobTag;
|
||||
this.job = buildJob(jobDispatcher, requirements, jobTag, serviceAction, servicePackage);
|
||||
}
|
||||
|
||||
@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);
|
||||
logd("Scheduling JobDispatcher job: " + jobTag + " result: " + result);
|
||||
logd("Scheduling job: " + jobTag + " result: " + result);
|
||||
return result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
int result = jobDispatcher.cancel(jobTag);
|
||||
logd("Canceling JobDispatcher job: " + jobTag + " result: " + result);
|
||||
logd("Canceling job: " + jobTag + " result: " + result);
|
||||
return result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@ -151,13 +125,12 @@ public final class JobDispatcherScheduler implements Scheduler {
|
||||
}
|
||||
builder.setLifetime(Lifetime.FOREVER).setReplaceCurrent(true);
|
||||
|
||||
// Extras, work duration.
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(SERVICE_ACTION, serviceAction);
|
||||
extras.putString(SERVICE_PACKAGE, servicePackage);
|
||||
extras.putInt(REQUIREMENTS, requirements.getRequirementsData());
|
||||
|
||||
extras.putString(KEY_SERVICE_ACTION, serviceAction);
|
||||
extras.putString(KEY_SERVICE_PACKAGE, servicePackage);
|
||||
extras.putInt(KEY_REQUIREMENTS, requirements.getRequirementsData());
|
||||
builder.setExtras(extras);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
logd("JobDispatcherSchedulerService is started");
|
||||
Bundle extras = params.getExtras();
|
||||
Requirements requirements = new Requirements(extras.getInt(REQUIREMENTS));
|
||||
Requirements requirements = new Requirements(extras.getInt(KEY_REQUIREMENTS));
|
||||
if (requirements.checkRequirements(this)) {
|
||||
logd("requirements are met");
|
||||
String serviceAction = extras.getString(SERVICE_ACTION);
|
||||
String servicePackage = extras.getString(SERVICE_PACKAGE);
|
||||
logd("Requirements are met");
|
||||
String serviceAction = extras.getString(KEY_SERVICE_ACTION);
|
||||
String servicePackage = extras.getString(KEY_SERVICE_PACKAGE);
|
||||
Intent intent = new Intent(serviceAction).setPackage(servicePackage);
|
||||
logd("starting service action: " + serviceAction + " package: " + servicePackage);
|
||||
if (Util.SDK_INT >= 26) {
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
logd("Starting service action: " + serviceAction + " package: " + servicePackage);
|
||||
Util.startForegroundService(this, intent);
|
||||
} else {
|
||||
logd("requirements are not met");
|
||||
logd("Requirements are not met");
|
||||
jobFinished(params, /* needsReschedule */ true);
|
||||
}
|
||||
return false;
|
||||
|
@ -53,7 +53,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
|
||||
|
||||
private @Nullable PlaybackPreparer playbackPreparer;
|
||||
private ControlDispatcher controlDispatcher;
|
||||
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private SurfaceHolderGlueHost surfaceHolderGlueHost;
|
||||
private boolean hasSurface;
|
||||
private boolean lastNotifiedPreparedState;
|
||||
@ -110,7 +110,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
|
||||
* @param errorMessageProvider The {@link ErrorMessageProvider}.
|
||||
*/
|
||||
public void setErrorMessageProvider(
|
||||
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
@Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
this.errorMessageProvider = errorMessageProvider;
|
||||
}
|
||||
|
||||
|
@ -334,12 +334,11 @@ public final class MediaSessionConnector {
|
||||
private Player player;
|
||||
private CustomActionProvider[] customActionProviders;
|
||||
private Map<String, CustomActionProvider> customActionMap;
|
||||
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private PlaybackPreparer playbackPreparer;
|
||||
private QueueNavigator queueNavigator;
|
||||
private QueueEditor queueEditor;
|
||||
private RatingCallback ratingCallback;
|
||||
private ExoPlaybackException playbackException;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param player The player to be connected to the {@code MediaSession}.
|
||||
* @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.
|
||||
*/
|
||||
public void setPlayer(Player player, PlaybackPreparer playbackPreparer,
|
||||
public void setPlayer(
|
||||
Player player,
|
||||
@Nullable PlaybackPreparer playbackPreparer,
|
||||
CustomActionProvider... customActionProviders) {
|
||||
if (this.player != null) {
|
||||
this.player.removeListener(exoPlayerEventListener);
|
||||
@ -435,13 +436,16 @@ public final class MediaSessionConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ErrorMessageProvider}.
|
||||
* Sets the optional {@link ErrorMessageProvider}.
|
||||
*
|
||||
* @param errorMessageProvider The error message provider.
|
||||
*/
|
||||
public void setErrorMessageProvider(
|
||||
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
this.errorMessageProvider = errorMessageProvider;
|
||||
@Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
if (this.errorMessageProvider != errorMessageProvider) {
|
||||
this.errorMessageProvider = errorMessageProvider;
|
||||
updateMediaSessionPlaybackState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -451,9 +455,11 @@ public final class MediaSessionConnector {
|
||||
* @param queueNavigator The queue navigator.
|
||||
*/
|
||||
public void setQueueNavigator(QueueNavigator queueNavigator) {
|
||||
unregisterCommandReceiver(this.queueNavigator);
|
||||
this.queueNavigator = queueNavigator;
|
||||
registerCommandReceiver(queueNavigator);
|
||||
if (this.queueNavigator != queueNavigator) {
|
||||
unregisterCommandReceiver(this.queueNavigator);
|
||||
this.queueNavigator = queueNavigator;
|
||||
registerCommandReceiver(queueNavigator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -462,11 +468,13 @@ public final class MediaSessionConnector {
|
||||
* @param queueEditor The queue editor.
|
||||
*/
|
||||
public void setQueueEditor(QueueEditor queueEditor) {
|
||||
unregisterCommandReceiver(this.queueEditor);
|
||||
this.queueEditor = queueEditor;
|
||||
registerCommandReceiver(queueEditor);
|
||||
mediaSession.setFlags(queueEditor == null ? BASE_MEDIA_SESSION_FLAGS
|
||||
: EDITOR_MEDIA_SESSION_FLAGS);
|
||||
if (this.queueEditor != queueEditor) {
|
||||
unregisterCommandReceiver(this.queueEditor);
|
||||
this.queueEditor = queueEditor;
|
||||
registerCommandReceiver(queueEditor);
|
||||
mediaSession.setFlags(
|
||||
queueEditor == null ? BASE_MEDIA_SESSION_FLAGS : EDITOR_MEDIA_SESSION_FLAGS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,9 +483,11 @@ public final class MediaSessionConnector {
|
||||
* @param ratingCallback The rating callback.
|
||||
*/
|
||||
public void setRatingCallback(RatingCallback ratingCallback) {
|
||||
unregisterCommandReceiver(this.ratingCallback);
|
||||
this.ratingCallback = ratingCallback;
|
||||
registerCommandReceiver(this.ratingCallback);
|
||||
if (this.ratingCallback != ratingCallback) {
|
||||
unregisterCommandReceiver(this.ratingCallback);
|
||||
this.ratingCallback = ratingCallback;
|
||||
registerCommandReceiver(this.ratingCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommandReceiver(CommandReceiver commandReceiver) {
|
||||
@ -514,16 +524,16 @@ public final class MediaSessionConnector {
|
||||
}
|
||||
customActionMap = Collections.unmodifiableMap(currentActions);
|
||||
|
||||
int sessionPlaybackState = playbackException != null ? PlaybackStateCompat.STATE_ERROR
|
||||
: mapPlaybackState(player.getPlaybackState(), player.getPlayWhenReady());
|
||||
if (playbackException != null) {
|
||||
if (errorMessageProvider != null) {
|
||||
Pair<Integer, String> message = errorMessageProvider.getErrorMessage(playbackException);
|
||||
builder.setErrorMessage(message.first, message.second);
|
||||
}
|
||||
if (player.getPlaybackState() != Player.STATE_IDLE) {
|
||||
playbackException = null;
|
||||
}
|
||||
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());
|
||||
if (playbackError != null && errorMessageProvider != null) {
|
||||
Pair<Integer, String> message = errorMessageProvider.getErrorMessage(playbackError);
|
||||
builder.setErrorMessage(message.first, message.second);
|
||||
}
|
||||
long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player)
|
||||
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
|
||||
@ -699,12 +709,6 @@ public final class MediaSessionConnector {
|
||||
updateMediaSessionPlaybackState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
playbackException = error;
|
||||
updateMediaSessionPlaybackState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
|
||||
if (currentWindowIndex != player.getCurrentWindowIndex()) {
|
||||
|
@ -73,10 +73,11 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
|
||||
/**
|
||||
* 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.
|
||||
* @return A {@link MediaDescriptionCompat}.
|
||||
*/
|
||||
public abstract MediaDescriptionCompat getMediaDescription(int windowIndex);
|
||||
public abstract MediaDescriptionCompat getMediaDescription(Player player, int windowIndex);
|
||||
|
||||
@Override
|
||||
public long getSupportedQueueNavigatorActions(Player player) {
|
||||
@ -185,7 +186,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
|
||||
windowCount - queueSize);
|
||||
List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
|
||||
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);
|
||||
activeQueueItemId = currentWindowIndex;
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Herhaal een"</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_all_description">Herhaal alles</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Ponovi jednu"</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_all_description">Ponovi sve</string>
|
||||
</resources>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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">"Ponovite sve"</string>
|
||||
<string name="exo_media_action_repeat_off_description">"Ne ponavljaju"</string>
|
||||
<string name="exo_media_action_repeat_one_description">"Ponovite jedan"</string>
|
||||
</resources>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Repeteix-ne un"</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_all_description">Repeteix tot</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Opakovat jednu položku"</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_all_description">Opakovat vše</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Gentag en"</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_all_description">Gentag alle</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="exo_media_action_repeat_all_description">"Alle wiederholen"</string>
|
||||
<string name="exo_media_action_repeat_off_description">"Keinen Titel wiederholen"</string>
|
||||
<string name="exo_media_action_repeat_one_description">"Einen Titel wiederholen"</string>
|
||||
<string name="exo_media_action_repeat_off_description">Keinen wiederholen</string>
|
||||
<string name="exo_media_action_repeat_one_description">Einen wiederholen</string>
|
||||
<string name="exo_media_action_repeat_all_description">Alle wiederholen</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Repeat one"</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_all_description">Repeat all</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Repeat one"</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_all_description">Repeat all</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Repeat one"</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_all_description">Repeat all</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Repetir uno"</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_all_description">Repetir todo</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Repetir uno"</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_all_description">Repetir todo</string>
|
||||
</resources>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="exo_media_action_repeat_all_description">"Toista kaikki"</string>
|
||||
<string name="exo_media_action_repeat_off_description">"Toista ei mitään"</string>
|
||||
<string name="exo_media_action_repeat_one_description">"Toista yksi"</string>
|
||||
<string name="exo_media_action_repeat_off_description">Ei uudelleentoistoa</string>
|
||||
<string name="exo_media_action_repeat_one_description">Toista yksi uudelleen</string>
|
||||
<string name="exo_media_action_repeat_all_description">Toista kaikki uudelleen</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="exo_media_action_repeat_all_description">"Tout 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">"Répéter un élément"</string>
|
||||
<string name="exo_media_action_repeat_off_description">Ne rien lire en boucle</string>
|
||||
<string name="exo_media_action_repeat_one_description">Lire une chanson en boucle</string>
|
||||
<string name="exo_media_action_repeat_all_description">Tout lire en boucle</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Lire en boucle un élément"</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_all_description">Tout lire en boucle</string>
|
||||
</resources>
|
||||
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Ponovi jedno"</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_all_description">Ponovi sve</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Egy ismétlése"</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_all_description">Összes szám ismétlése</string>
|
||||
</resources>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Ulangi Satu"</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_all_description">Ulangi semua</string>
|
||||
</resources>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="exo_media_action_repeat_all_description">"Ripeti tutti"</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_off_description">Non ripetere nulla</string>
|
||||
<string name="exo_media_action_repeat_one_description">Ripeti uno</string>
|
||||
<string name="exo_media_action_repeat_all_description">Ripeti tutto</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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">"1曲を繰り返し"</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_all_description">全曲をリピート</string>
|
||||
</resources>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Kartoti vieną"</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_all_description">Kartoti viską</string>
|
||||
</resources>
|
||||
|
@ -1,21 +1,6 @@
|
||||
<?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.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_one_description">"Atkārtot vienu"</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_all_description">Atkārtot visu</string>
|
||||
</resources>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|
@ -0,0 +1,6 @@
|
||||
<?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>
|
@ -1,21 +0,0 @@
|
||||
<?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>
|