diff --git a/core_settings.gradle b/core_settings.gradle index 31547f60df..20e7b235a2 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -28,6 +28,7 @@ include modulePrefix + 'extension-ffmpeg' include modulePrefix + 'extension-flac' include modulePrefix + 'extension-gvr' include modulePrefix + 'extension-ima' +include modulePrefix + 'extension-mediasession' include modulePrefix + 'extension-okhttp' include modulePrefix + 'extension-opus' include modulePrefix + 'extension-vp9' @@ -44,6 +45,7 @@ project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'exten project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac') project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr') project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima') +project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession') project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp') project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus') project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9') diff --git a/extensions/mediasession/README.md b/extensions/mediasession/README.md new file mode 100644 index 0000000000..7515cf9eef --- /dev/null +++ b/extensions/mediasession/README.md @@ -0,0 +1,27 @@ +# ExoPlayer MediaSession Extension # + +## Description ## + +The MediaSession Extension mediates between an ExoPlayer instance and a +[MediaSession][]. It automatically retrieves and implements playback actions +and syncs the player state with the state of the media session. The behaviour +can be extended to support other playback and custom actions. + +[MediaSession]: https://developer.android.com/reference/android/support/v4/media/session/MediaSessionCompat.html + +## Getting the extension ## + +The easiest way to use the extension is to add it as a gradle dependency: + +```gradle +compile 'com.google.android.exoplayer:extension-mediasession:rX.X.X' +``` + +where `rX.X.X` is the version, which must match the version of the ExoPlayer +library being used. + +Alternatively, you can clone the ExoPlayer repository and depend on the module +locally. Instructions for doing this can be found in ExoPlayer's +[top level README][]. + +[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md diff --git a/extensions/mediasession/build.gradle b/extensions/mediasession/build.gradle new file mode 100644 index 0000000000..c439543967 --- /dev/null +++ b/extensions/mediasession/build.gradle @@ -0,0 +1,42 @@ +// 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. +apply from: '../../constants.gradle' +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + } +} + +dependencies { + compile project(':library-core') + compile 'com.android.support:support-media-compat:' + supportLibraryVersion + compile 'com.android.support:appcompat-v7:' + supportLibraryVersion +} + +ext { + javadocTitle = 'Media session extension' +} +apply from: '../../javadoc_library.gradle' + +ext { + releaseArtifact = 'extension-mediasession' + releaseDescription = 'Media session extension for ExoPlayer.' +} +apply from: '../../publish.gradle' diff --git a/extensions/mediasession/src/main/AndroidManifest.xml b/extensions/mediasession/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8ed6ef2011 --- /dev/null +++ b/extensions/mediasession/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java new file mode 100644 index 0000000000..e023d0a983 --- /dev/null +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -0,0 +1,857 @@ +/* + * 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.ext.mediasession; + +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.ResultReceiver; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.RatingCompat; +import android.support.v4.media.session.MediaControllerCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.util.Pair; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.ExoPlayerLibraryInfo; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Mediates between a {@link MediaSessionCompat} and an {@link SimpleExoPlayer} instance set with + * {@link #setPlayer(SimpleExoPlayer)}. + *

+ * By default the {@code MediaSessionConnector} listens for {@link #DEFAULT_PLAYBACK_ACTIONS} sent + * by a media controller and realizes these actions by calling appropriate ExoPlayer methods. + * Further, the state of ExoPlayer will be synced automatically with the {@link PlaybackStateCompat} + * of the media session to broadcast state transitions to clients. You can optionally extend this + * behaviour by providing various collaborators. + *

+ * Media actions to initiate media playback ({@code PlaybackStateCompat#ACTION_PREPARE_*} and + * {@code PlaybackStateCompat#ACTION_PLAY_*} need to be handled by a {@link PlaybackPreparer} which + * build a {@link com.google.android.exoplayer2.source.MediaSource} to prepare ExoPlayer. Deploy + * your preparer by calling {@link #setPlaybackPreparer(PlaybackPreparer)}. + *

+ * To support a media session queue and navigation within this queue, you can set a + * {@link QueueNavigator} to maintain the queue yourself and implement queue navigation commands + * (like 'skip to next') sent by controllers. It's recommended to use the + * {@link TimelineQueueNavigator} to allow users navigating the windows of the ExoPlayer timeline. + *

+ * If you want to allow media controllers to manipulate the queue, implement a {@link QueueEditor} + * and deploy it with {@link #setQueueEditor(QueueEditor)}. + *

+ * Set an {@link ErrorMessageProvider} to provide an error code and a human readable error message + * to be broadcast to controllers. + */ +public final class MediaSessionConnector { + + static { + ExoPlayerLibraryInfo.registerModule("goog.exo.mediasession"); + } + + /** + * Actions that are published to the media session by default + * ({@code PlaybackStateCompat.ACTION_PLAY_PAUSE}, {@code PlaybackStateCompat.ACTION_PLAY}, + * {@code PlaybackStateCompat.ACTION_PAUSE}, {@code PlaybackStateCompat.ACTION_FAST_FORWARD}, + * {@code PlaybackStateCompat.ACTION_REWIND}). + */ + public static final long DEFAULT_PLAYBACK_ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND; + + public static final String EXTRAS_PITCH = "EXO_PITCH"; + + public static final long DEFAULT_FAST_FORWARD_MS = 15000; + public static final long DEFAULT_REWIND_MS = 5000; + + /** + * Interface of components taking responsibility of a set of media session playback actions + * ({@code PlaybackStateCompat#ACTION_*}). + */ + public interface PlaybackActionSupport { + /** + * Returns the bit mask of the playback actions supported by this component. + */ + long getSupportedPlaybackActions(); + } + + /** + * Interface to which media controller commands regarding preparing playback for a given media + * clip are delegated to. + *

+ * Normally preparing playback includes preparing the player with a + * {@link com.google.android.exoplayer2.source.MediaSource} and setting up the media session queue + * with a corresponding list of queue items. + */ + public interface PlaybackPreparer extends PlaybackActionSupport { + /** + * See {@link MediaSessionCompat.Callback#onPrepare()}. + */ + void onPrepare(ExoPlayer player); + /** + * See {@link MediaSessionCompat.Callback#onPrepareFromMediaId(String, Bundle)}. + */ + void onPrepareFromMediaId(ExoPlayer player, String mediaId, Bundle extras); + /** + * See {@link MediaSessionCompat.Callback#onPrepareFromSearch(String, Bundle)}. + */ + void onPrepareFromSearch(ExoPlayer player, String query, Bundle extras); + /** + * See {@link MediaSessionCompat.Callback#onPrepareFromUri(Uri, Bundle)}. + */ + void onPrepareFromUri(ExoPlayer player, Uri uri, Bundle extras); + /** + * See {@link MediaSessionCompat.Callback#onCommand(String, Bundle, ResultReceiver)}. + */ + void onCommand(ExoPlayer player, String command, Bundle extras, ResultReceiver cb); + } + + /** + * Navigator to handle queue navigation commands and maintain the media session queue with + * {#link MediaSessionCompat#setQueue(List)} to provide the active queue item to the connector. + */ + public interface QueueNavigator extends PlaybackActionSupport { + /** + * Called when the timeline of the player has changed. + * + * @param player The player of which the timeline has changed. + */ + void onTimelineChanged(ExoPlayer player); + /** + * Called when the current window index changed. + * + * @param player The player of which the current window index of the timeline has changed. + */ + void onCurrentWindowIndexChanged(ExoPlayer player); + /** + * Gets the id of the currently active queue item or + * {@link MediaSessionCompat.QueueItem#UNKNOWN_ID} if the active item is unknown. + *

+ * To let the connector publish metadata for the active queue item, the queue item with the + * returned id must be available in the list of items returned by + * {@link MediaControllerCompat#getQueue()}. + * + * @param player The player connected to the media session. + * @return The id of the active queue item. + */ + long getActiveQueueItemId(@Nullable ExoPlayer player); + /** + * See {@link MediaSessionCompat.Callback#onSkipToPrevious()). + */ + void onSkipToPrevious(ExoPlayer player); + /** + * See {@link MediaSessionCompat.Callback#onSkipToQueueItem(long)}. + */ + void onSkipToQueueItem(ExoPlayer player, long id); + /** + * See {@link MediaSessionCompat.Callback#onSkipToNext()}. + */ + void onSkipToNext(ExoPlayer player); + /** + * See {@link MediaSessionCompat.Callback#onSetShuffleModeEnabled(boolean)}. + */ + void onSetShuffleModeEnabled(ExoPlayer player, boolean enabled); + } + + /** + * Editor to manipulate the queue. + */ + public interface QueueEditor extends PlaybackActionSupport { + /** + * See {@link MediaSessionCompat.Callback#onAddQueueItem(MediaDescriptionCompat description)}. + */ + void onAddQueueItem(ExoPlayer player, MediaDescriptionCompat description); + /** + * See {@link MediaSessionCompat.Callback#onAddQueueItem(MediaDescriptionCompat description, + * int index)}. + */ + void onAddQueueItem(ExoPlayer player, MediaDescriptionCompat description, int index); + /** + * See {@link MediaSessionCompat.Callback#onRemoveQueueItem(MediaDescriptionCompat + * description)}. + */ + void onRemoveQueueItem(ExoPlayer player, MediaDescriptionCompat description); + /** + * See {@link MediaSessionCompat.Callback#onRemoveQueueItemAt(int index)). + */ + void onRemoveQueueItemAt(ExoPlayer player, int index); + /** + * See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. + */ + void onSetRating(ExoPlayer player, RatingCompat rating); + } + + /** + * Provides a {@link PlaybackStateCompat.CustomAction} to be published and handles the action when + * sent by a media controller. + */ + public interface CustomActionProvider { + /** + * Called when a custom action provided by this provider is sent to the media session. + * + * @param player The player for which to process the custom action. + * @param action The name of the action which was sent by a media controller. + * @param extras Optional extras sent by a media controller. + */ + void onCustomAction(SimpleExoPlayer player, String action, Bundle extras); + + /** + * Returns a {@link PlaybackStateCompat.CustomAction} which will be published to the + * media session by the connector or {@code null} if this action should not be published at the + * given player state. + * + * @param player The player for which to provide actions. + * @return The custom action to be included in the session playback state or {@code null}. + */ + PlaybackStateCompat.CustomAction getCustomAction(SimpleExoPlayer player); + } + + /** + * Provides an user readable error code and a message for {@link ExoPlaybackException}s. + */ + public interface ErrorMessageProvider { + /** + * Returns a pair of an error code and a user readable error message for a given + * {@link ExoPlaybackException}. + */ + Pair getErrorMessage(ExoPlaybackException playbackException); + } + + /** + * The wrapped {@link MediaSessionCompat}. + */ + public final MediaSessionCompat mediaSession; + private final MediaControllerCompat mediaController; + private final Handler handler; + private final boolean doMaintainMetadata; + private final ExoPlayerEventListener exoPlayerEventListener; + private final MediaSessionCallback mediaSessionCallback; + private final CustomActionProvider[] customActionProviders; + + private SimpleExoPlayer player; + private int currentWindowIndex; + private long playbackActions; + private long fastForwardIncrementMs; + private long rewindIncrementMs; + private Map customActionMap; + private ErrorMessageProvider errorMessageProvider; + private PlaybackPreparer playbackPreparer; + private QueueNavigator queueNavigator; + private QueueEditor queueEditor; + private ExoPlaybackException playbackException; + + /** + * Creates a {@code MediaSessionConnector}. This is equivalent to calling + * {@code #MediaSessionConnector(mediaSession, true)}. + *

+ * Constructing the {@link MediaSessionConnector} needs to be done on the same thread as + * constructing the player instance. + * + * @param mediaSession The {@link MediaSessionCompat} to connect to. + */ + public MediaSessionConnector(MediaSessionCompat mediaSession) { + this(mediaSession, true); + } + + /** + * Creates a {@code MediaSessionConnector} with {@link CustomActionProvider}s. + *

+ * The order in which the {@link CustomActionProvider}s are passed to the constructor determines + * the order of the actions published with the playback state of the session. + *

+ * If you choose to pass {@code false} for {@code doMaintainMetadata} you need to maintain the + * metadata of the media session yourself (provide at least the duration to allow clients to show + * a progress bar). + *

+ * Constructing the {@link MediaSessionConnector} needs to be done on the same thread as + * constructing the player instance. + * + * @param mediaSession The {@link MediaSessionCompat} to connect to. + * @param doMaintainMetadata Sets whether the connector should maintain the metadata of the + * session. + * @param customActionProviders {@link CustomActionProvider}s to publish and handle custom + * actions. + */ + public MediaSessionConnector(MediaSessionCompat mediaSession, boolean doMaintainMetadata, + CustomActionProvider... customActionProviders) { + this.mediaSession = mediaSession; + this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper() + : Looper.getMainLooper()); + this.doMaintainMetadata = doMaintainMetadata; + this.customActionProviders = customActionProviders != null ? customActionProviders + : new CustomActionProvider[0]; + mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + mediaController = mediaSession.getController(); + mediaSessionCallback = new MediaSessionCallback(); + exoPlayerEventListener = new ExoPlayerEventListener(); + playbackActions = DEFAULT_PLAYBACK_ACTIONS; + customActionMap = Collections.emptyMap(); + fastForwardIncrementMs = DEFAULT_FAST_FORWARD_MS; + rewindIncrementMs = DEFAULT_REWIND_MS; + } + + /** + * Sets the player to which media commands sent by a media controller are delegated. + *

+ * The media session callback is set if the {@code player} is not {@code null} and the callback is + * removed if the {@code player} is {@code null}. + * + * @param player The player to be connected to the {@code MediaSession}. + */ + public void setPlayer(SimpleExoPlayer player) { + if (this.player != null) { + this.player.removeListener(exoPlayerEventListener); + mediaSession.setCallback(null); + } + this.player = player; + if (player != null) { + mediaSession.setCallback(mediaSessionCallback, handler); + player.addListener(exoPlayerEventListener); + } + updateMediaSessionPlaybackState(); + updateMediaSessionMetadata(); + } + + /** + * Sets the fast forward increment in milliseconds. A positive value will cause the + * {@link PlaybackStateCompat#ACTION_FAST_FORWARD} playback action to be added. A zero or a + * negative value will cause it to be removed. + * + * @param fastForwardIncrementMs The fast forward increment in milliseconds. + */ + public void setFastForwardIncrementMs(long fastForwardIncrementMs) { + this.fastForwardIncrementMs = fastForwardIncrementMs; + if (fastForwardIncrementMs > 0) { + addPlaybackActions(PlaybackStateCompat.ACTION_FAST_FORWARD); + } else { + removePlaybackActions(PlaybackStateCompat.ACTION_FAST_FORWARD); + } + } + + /** + * Sets the rewind increment in milliseconds. A positive value will cause the + * {@link PlaybackStateCompat#ACTION_REWIND} playback action to be added. A zero or a + * negative value will cause it to be removed. + * + * @param rewindIncrementMs The rewind increment in milliseconds. + */ + public void setRewindIncrementMs(long rewindIncrementMs) { + this.rewindIncrementMs = rewindIncrementMs; + if (rewindIncrementMs > 0) { + addPlaybackActions(PlaybackStateCompat.ACTION_REWIND); + } else { + removePlaybackActions(PlaybackStateCompat.ACTION_REWIND); + } + } + + /** + * Adds playback actions. The playback actions that are enabled by default are those in + * {@link MediaSessionConnector#DEFAULT_PLAYBACK_ACTIONS}. See {@link PlaybackStateCompat} for + * available playback action constants. + * + * @param playbackActions The playback actions to add. + */ + public void addPlaybackActions(long playbackActions) { + this.playbackActions |= playbackActions; + } + + /** + * Removes playback actions. The playback actions that are enabled by default are those in + * {@link MediaSessionConnector#DEFAULT_PLAYBACK_ACTIONS}. + * + * @param playbackActions The playback actions to remove. + */ + public void removePlaybackActions(long playbackActions) { + this.playbackActions &= ~playbackActions; + } + + /** + * Sets the playback actions. The playback actions that are enabled by default are overridden. + * + * @param playbackActions The playback actions to publish. + */ + public void setPlaybackActions(long playbackActions) { + this.playbackActions = playbackActions; + } + + /** + * Sets the optional {@link ErrorMessageProvider}. + * + * @param errorMessageProvider The {@link ErrorMessageProvider}. + */ + public void setErrorMessageProvider(ErrorMessageProvider errorMessageProvider) { + this.errorMessageProvider = errorMessageProvider; + } + + /** + * Sets the {@link QueueNavigator} to handle queue navigation for the media actions + * {@code ACTION_SKIP_TO_NEXT}, {@code ACTION_SKIP_TO_PREVIOUS}, + * {@code ACTION_SKIP_TO_QUEUE_ITEM} and {@code ACTION_SET_SHUFFLE_MODE_ENABLED}. + * + * @param queueNavigator The navigator to handle queue navigation. + */ + public void setQueueNavigator(QueueNavigator queueNavigator) { + if (this.queueNavigator != null) { + removePlaybackActions(this.queueNavigator.getSupportedPlaybackActions()); + } + this.queueNavigator = queueNavigator; + if (queueNavigator != null) { + addPlaybackActions(queueNavigator.getSupportedPlaybackActions()); + } + } + + /** + * Sets the queue editor to handle commands to manipulate the queue sent by a media controller. + * + * @param queueEditor The editor to handle queue manipulation actions. + */ + public void setQueueEditor(QueueEditor queueEditor) { + if (this.queueEditor != null) { + removePlaybackActions(this.queueEditor.getSupportedPlaybackActions()); + } + this.queueEditor = queueEditor; + if (queueEditor != null) { + addPlaybackActions(queueEditor.getSupportedPlaybackActions()); + } + } + + /** + * Sets the {@link PlaybackPreparer} to which preparation commands sent by a media + * controller are delegated. + *

+ * Required to work properly with Android Auto which requires + * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}. + * + * @param playbackPreparer The preparer to delegate to. + */ + public void setPlaybackPreparer(PlaybackPreparer playbackPreparer) { + if (this.playbackPreparer != null) { + removePlaybackActions(this.playbackPreparer.getSupportedPlaybackActions()); + } + this.playbackPreparer = playbackPreparer; + if (playbackPreparer != null) { + addPlaybackActions(playbackPreparer.getSupportedPlaybackActions()); + } + } + + private void updateMediaSessionPlaybackState() { + PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(); + if (player == null) { + builder.setActions(0).setState(PlaybackStateCompat.STATE_NONE, 0, 0, 0); + mediaSession.setPlaybackState(builder.build()); + return; + } + + Map currentActions = new HashMap<>(); + for (CustomActionProvider customActionProvider : customActionProviders) { + PlaybackStateCompat.CustomAction customAction = customActionProvider + .getCustomAction(player); + if (customAction != null) { + currentActions.put(customAction.getAction(), customActionProvider); + builder.addCustomAction(customAction); + } + } + customActionMap = Collections.unmodifiableMap(currentActions); + + int sessionPlaybackState = playbackException != null ? PlaybackStateCompat.STATE_ERROR + : mapPlaybackState(player.getPlaybackState(), player.getPlayWhenReady()); + if (playbackException != null) { + if (errorMessageProvider != null) { + Pair message = errorMessageProvider.getErrorMessage(playbackException); + builder.setErrorMessage(message.first, message.second); + } + if (player.getPlaybackState() != ExoPlayer.STATE_IDLE) { + playbackException = null; + } + } + long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player) + : MediaSessionCompat.QueueItem.UNKNOWN_ID; + updatePlaybackActions(activeQueueItemId); + Bundle extras = new Bundle(); + extras.putFloat(EXTRAS_PITCH, player.getPlaybackParameters().pitch); + builder.setActions(playbackActions) + .setActiveQueueItemId(activeQueueItemId) + .setBufferedPosition(player.getBufferedPosition()) + .setState(sessionPlaybackState, player.getCurrentPosition(), + player.getPlaybackParameters().speed, SystemClock.elapsedRealtime()) + .setExtras(extras); + mediaSession.setPlaybackState(builder.build()); + } + + private void updatePlaybackActions(long activeQueueItemId) { + List queue = mediaController.getQueue(); + if (queue == null || queue.size() < 2) { + removePlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); + } else if (player.getRepeatMode() != ExoPlayer.REPEAT_MODE_OFF) { + addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); + } else if (activeQueueItemId == queue.get(0).getQueueId()) { + removePlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); + addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT); + } else if (activeQueueItemId == queue.get(queue.size() - 1).getQueueId()) { + removePlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT); + addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); + } else { + addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); + } + } + + private void updateMediaSessionMetadata() { + if (doMaintainMetadata) { + MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); + if (player != null && player.isPlayingAd()) { + builder.putLong(MediaMetadataCompat.METADATA_KEY_ADVERTISEMENT, 1); + } + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, player == null ? 0 + : player.getDuration() == C.TIME_UNSET ? -1 : player.getDuration()); + + if (queueNavigator != null) { + long activeQueueItemId = queueNavigator.getActiveQueueItemId(player); + List queue = mediaController.getQueue(); + for (int i = 0; queue != null && i < queue.size(); i++) { + MediaSessionCompat.QueueItem queueItem = queue.get(i); + if (queueItem.getQueueId() == activeQueueItemId) { + MediaDescriptionCompat description = queueItem.getDescription(); + if (description.getTitle() != null) { + builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, + String.valueOf(description.getTitle())); + } + if (description.getSubtitle() != null) { + builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, + String.valueOf(description.getSubtitle())); + } + if (description.getDescription() != null) { + builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, + String.valueOf(description.getDescription())); + } + if (description.getIconBitmap() != null) { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, + description.getIconBitmap()); + } + if (description.getIconUri() != null) { + builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, + String.valueOf(description.getIconUri())); + } + if (description.getMediaId() != null) { + builder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, + String.valueOf(description.getMediaId())); + } + if (description.getMediaUri() != null) { + builder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, + String.valueOf(description.getMediaUri())); + } + break; + } + } + } + mediaSession.setMetadata(builder.build()); + } + } + + private int mapPlaybackState(int exoPlayerPlaybackState, boolean playWhenReady) { + switch (exoPlayerPlaybackState) { + case ExoPlayer.STATE_BUFFERING: + return PlaybackStateCompat.STATE_BUFFERING; + case ExoPlayer.STATE_READY: + return playWhenReady ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED; + case ExoPlayer.STATE_ENDED: + return PlaybackStateCompat.STATE_PAUSED; + default: + return PlaybackStateCompat.STATE_NONE; + } + } + + private boolean isActionPublished(long action) { + return (playbackActions & action) != 0; + } + + private boolean canDispatchToQueueNavigator(long action) { + return queueNavigator != null && isActionPublished(action); + } + + private boolean canDispatchToPlaybackPreparer(long action) { + return playbackPreparer != null && isActionPublished(action); + } + + private class ExoPlayerEventListener implements ExoPlayer.EventListener { + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + if (queueNavigator != null) { + queueNavigator.onTimelineChanged(player); + } + currentWindowIndex = player.getCurrentWindowIndex(); + updateMediaSessionMetadata(); + } + + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + // Do nothing. + } + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updateMediaSessionPlaybackState(); + } + + @Override + public void onRepeatModeChanged(@ExoPlayer.RepeatMode int repeatMode) { + mediaSession.setRepeatMode(repeatMode == ExoPlayer.REPEAT_MODE_ONE + ? PlaybackStateCompat.REPEAT_MODE_ONE : repeatMode == ExoPlayer.REPEAT_MODE_ALL + ? PlaybackStateCompat.REPEAT_MODE_ALL : PlaybackStateCompat.REPEAT_MODE_NONE); + updateMediaSessionPlaybackState(); + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + playbackException = error; + updateMediaSessionPlaybackState(); + } + + @Override + public void onPositionDiscontinuity() { + if (currentWindowIndex != player.getCurrentWindowIndex()) { + if (queueNavigator != null) { + queueNavigator.onCurrentWindowIndexChanged(player); + } + updateMediaSessionMetadata(); + currentWindowIndex = player.getCurrentWindowIndex(); + } + updateMediaSessionPlaybackState(); + } + + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + updateMediaSessionPlaybackState(); + } + + } + + private class MediaSessionCallback extends MediaSessionCompat.Callback { + + @Override + public void onPlay() { + player.setPlayWhenReady(true); + } + + @Override + public void onPause() { + player.setPlayWhenReady(false); + } + + @Override + public void onSeekTo(long position) { + long duration = player.getDuration(); + if (duration != C.TIME_UNSET) { + position = Math.min(position, duration); + } + player.seekTo(Math.max(position, 0)); + } + + @Override + public void onFastForward() { + if (fastForwardIncrementMs <= 0) { + return; + } + onSeekTo(player.getCurrentPosition() + fastForwardIncrementMs); + } + + @Override + public void onRewind() { + if (rewindIncrementMs <= 0) { + return; + } + onSeekTo(player.getCurrentPosition() - rewindIncrementMs); + } + + @Override + public void onSkipToNext() { + if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_NEXT)) { + queueNavigator.onSkipToNext(player); + } + } + + @Override + public void onSkipToPrevious() { + if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)) { + queueNavigator.onSkipToPrevious(player); + } + } + + @Override + public void onSkipToQueueItem(long id) { + if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM)) { + queueNavigator.onSkipToQueueItem(player, id); + } + } + + @Override + public void onStop() { + if (isActionPublished(PlaybackStateCompat.ACTION_STOP)) { + player.stop(); + } + } + + @Override + public void onSetRepeatMode(int repeatMode) { + // implemented as custom action + } + + @Override + public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { + Map actionMap = customActionMap; + if (actionMap.containsKey(action)) { + actionMap.get(action).onCustomAction(player, action, extras); + updateMediaSessionPlaybackState(); + } + } + + @Override + public void onCommand(String command, Bundle extras, ResultReceiver cb) { + if (playbackPreparer != null) { + playbackPreparer.onCommand(player, command, extras, cb); + } + } + + @Override + public void onPrepare() { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE)) { + player.stop(); + player.setPlayWhenReady(false); + playbackPreparer.onPrepare(player); + } + } + + @Override + public void onPrepareFromMediaId(String mediaId, Bundle extras) { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID)) { + player.stop(); + player.setPlayWhenReady(false); + playbackPreparer.onPrepareFromMediaId(player, mediaId, extras); + } + } + + @Override + public void onPrepareFromSearch(String query, Bundle extras) { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH)) { + player.stop(); + player.setPlayWhenReady(false); + playbackPreparer.onPrepareFromSearch(player, query, extras); + } + } + + @Override + public void onPrepareFromUri(Uri uri, Bundle extras) { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_URI)) { + player.stop(); + player.setPlayWhenReady(false); + playbackPreparer.onPrepareFromUri(player, uri, extras); + } + } + + @Override + public void onPlayFromMediaId(String mediaId, Bundle extras) { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID)) { + player.stop(); + player.setPlayWhenReady(true); + playbackPreparer.onPrepareFromMediaId(player, mediaId, extras); + } + } + + @Override + public void onPlayFromSearch(String query, Bundle extras) { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH)) { + player.stop(); + player.setPlayWhenReady(true); + playbackPreparer.onPrepareFromSearch(player, query, extras); + } + } + + @Override + public void onPlayFromUri(Uri uri, Bundle extras) { + if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_URI)) { + player.stop(); + player.setPlayWhenReady(true); + playbackPreparer.onPrepareFromUri(player, uri, extras); + } + } + + @Override + public void onSetShuffleModeEnabled(boolean enabled) { + if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE_ENABLED)) { + queueNavigator.onSetShuffleModeEnabled(player, enabled); + } + } + + @Override + public void onAddQueueItem(MediaDescriptionCompat description) { + if (queueEditor != null) { + queueEditor.onAddQueueItem(player, description); + } + } + + @Override + public void onAddQueueItem(MediaDescriptionCompat description, int index) { + if (queueEditor != null) { + queueEditor.onAddQueueItem(player, description, index); + } + } + + @Override + public void onRemoveQueueItem(MediaDescriptionCompat description) { + if (queueEditor != null) { + queueEditor.onRemoveQueueItem(player, description); + } + } + + @Override + public void onRemoveQueueItemAt(int index) { + if (queueEditor != null) { + queueEditor.onRemoveQueueItemAt(player, index); + } + } + + @Override + public void onSetRating(RatingCompat rating) { + if (queueEditor != null && isActionPublished(PlaybackStateCompat.ACTION_SET_RATING)) { + queueEditor.onSetRating(player, rating); + } + } + + } + +} diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java new file mode 100644 index 0000000000..3e453bdd28 --- /dev/null +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java @@ -0,0 +1,96 @@ +package com.google.android.exoplayer2.ext.mediasession; +/* + * 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. + */ + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.media.session.PlaybackStateCompat; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.util.RepeatModeUtil; + +/** + * Provides a custom action for toggling repeat actions. + */ +public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider { + + private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE"; + + private final @RepeatModeUtil.RepeatToggleModes int repeatToggleModes; + private final CharSequence repeatAllDescription; + private final CharSequence repeatOneDescription; + private final CharSequence repeatOffDescription; + + /** + * Creates a new {@link RepeatModeActionProvider}. + *

+ * This is equivalent to calling the two argument constructor with + * {@code RepeatModeUtil#REPEAT_TOGGLE_MODE_ONE | RepeatModeUtil#REPEAT_TOGGLE_MODE_ALL}. + * + * @param context The context. + */ + public RepeatModeActionProvider(Context context) { + this(context, RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE | RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL); + } + + /** + * Creates a new {@link RepeatModeActionProvider} for the given repeat toggle modes. + * + * @param context The context. + * @param repeatToggleModes The toggle modes to enable. + */ + public RepeatModeActionProvider(Context context, + @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { + this.repeatToggleModes = repeatToggleModes; + repeatAllDescription = context.getString(R.string.exo_media_action_repeat_all_description); + repeatOneDescription = context.getString(R.string.exo_media_action_repeat_one_description); + repeatOffDescription = context.getString(R.string.exo_media_action_repeat_off_description); + } + + @Override + public void onCustomAction(SimpleExoPlayer player, String action, Bundle extras) { + int mode = player.getRepeatMode(); + int proposedMode = RepeatModeUtil.getNextRepeatMode(mode, repeatToggleModes); + if (mode != proposedMode) { + player.setRepeatMode(proposedMode); + } + } + + @Override + public PlaybackStateCompat.CustomAction getCustomAction(SimpleExoPlayer player) { + CharSequence actionLabel; + int iconResourceId; + switch (player.getRepeatMode()) { + case ExoPlayer.REPEAT_MODE_ONE: + actionLabel = repeatOneDescription; + iconResourceId = R.drawable.exo_media_action_repeat_one; + break; + case ExoPlayer.REPEAT_MODE_ALL: + actionLabel = repeatAllDescription; + iconResourceId = R.drawable.exo_media_action_repeat_all; + break; + case ExoPlayer.REPEAT_MODE_OFF: + default: + actionLabel = repeatOffDescription; + iconResourceId = R.drawable.exo_media_action_repeat_off; + break; + } + PlaybackStateCompat.CustomAction.Builder repeatBuilder = new PlaybackStateCompat.CustomAction + .Builder(ACTION_REPEAT_MODE, actionLabel, iconResourceId); + return repeatBuilder.build(); + } + +} diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java new file mode 100644 index 0000000000..97959ccf12 --- /dev/null +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java @@ -0,0 +1,169 @@ +/* + * 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.ext.mediasession; + +import android.support.annotation.Nullable; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.util.Util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * An abstract implementation of the {@link MediaSessionConnector.QueueNavigator} that's based on an + * {@link ExoPlayer}'s current {@link Timeline} and maps the timeline of the player to the media + * session queue. + */ +public abstract class TimelineQueueNavigator implements MediaSessionConnector.QueueNavigator { + + public static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000; + public static final int DEFAULT_MAX_QUEUE_SIZE = 10; + + private final MediaSessionCompat mediaSession; + private final int maxQueueSize; + + private long activeQueueItemId; + + /** + * Creates a new timeline queue navigator for a given {@link MediaSessionCompat}. + *

+ * This is equivalent to calling + * {@code #TimelineQueueNavigator(mediaSession, DEFAULT_MAX_QUEUE_SIZE)}. + * + * @param mediaSession The {@link MediaSessionCompat}. + */ + public TimelineQueueNavigator(MediaSessionCompat mediaSession) { + this(mediaSession, DEFAULT_MAX_QUEUE_SIZE); + } + + /** + * Creates a new timeline queue navigator for a given {@link MediaSessionCompat} and a maximum + * queue size of {@code maxQueueSize}. + *

+ * If the actual queue size is larger than {@code maxQueueSize} a floating window of + * {@code maxQueueSize} is applied and moved back and forth when the user is navigating within the + * queue. + * + * @param mediaSession The {@link MediaSessionCompat}. + * @param maxQueueSize The maximum queue size. + */ + public TimelineQueueNavigator(MediaSessionCompat mediaSession, int maxQueueSize) { + this.mediaSession = mediaSession; + this.maxQueueSize = maxQueueSize; + activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + } + + /** + * Gets the {@link MediaDescriptionCompat} for a given timeline window index. + * + * @param windowIndex The timeline window index for which to provide a description. + * @return A {@link MediaDescriptionCompat}. + */ + public abstract MediaDescriptionCompat getMediaDescription(int windowIndex); + + @Override + public long getSupportedPlaybackActions() { + return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + } + + @Override + public void onTimelineChanged(ExoPlayer player) { + publishFloatingQueueWindow(player); + } + + @Override + public void onCurrentWindowIndexChanged(ExoPlayer player) { + publishFloatingQueueWindow(player); + } + + @Override + public final long getActiveQueueItemId(@Nullable ExoPlayer player) { + return activeQueueItemId; + } + + @Override + public final void onSkipToPrevious(ExoPlayer player) { + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty()) { + return; + } + int previousWindowIndex = timeline.getPreviousWindowIndex(player.getCurrentWindowIndex(), + player.getRepeatMode()); + if (player.getCurrentPosition() > MAX_POSITION_FOR_SEEK_TO_PREVIOUS + || previousWindowIndex == C.INDEX_UNSET) { + player.seekTo(0); + } else { + player.seekTo(previousWindowIndex, C.TIME_UNSET); + } + } + + @Override + public final void onSkipToQueueItem(ExoPlayer player, long id) { + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty()) { + return; + } + int windowIndex = (int) id; + if (0 <= windowIndex && windowIndex < timeline.getWindowCount()) { + player.seekTo(windowIndex, C.TIME_UNSET); + } + } + + @Override + public final void onSkipToNext(ExoPlayer player) { + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty()) { + return; + } + int nextWindowIndex = timeline.getNextWindowIndex(player.getCurrentWindowIndex(), + player.getRepeatMode()); + if (nextWindowIndex != C.INDEX_UNSET) { + player.seekTo(nextWindowIndex, C.TIME_UNSET); + } + } + + @Override + public void onSetShuffleModeEnabled(ExoPlayer player, boolean enabled) { + // TODO: Implement this. + } + + private void publishFloatingQueueWindow(ExoPlayer player) { + if (player.getCurrentTimeline().isEmpty()) { + mediaSession.setQueue(Collections.emptyList()); + activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + return; + } + int windowCount = player.getCurrentTimeline().getWindowCount(); + int currentWindowIndex = player.getCurrentWindowIndex(); + int queueSize = Math.min(maxQueueSize, windowCount); + int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, + windowCount - queueSize); + List queue = new ArrayList<>(); + for (int i = startIndex; i < startIndex + queueSize; i++) { + queue.add(new MediaSessionCompat.QueueItem(getMediaDescription(i), i)); + } + mediaSession.setQueue(queue); + activeQueueItemId = currentWindowIndex; + } + +} diff --git a/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_all.xml b/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_all.xml new file mode 100644 index 0000000000..dad37fa1f0 --- /dev/null +++ b/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_all.xml @@ -0,0 +1,23 @@ + + + + diff --git a/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_off.xml b/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_off.xml new file mode 100644 index 0000000000..132eae0d76 --- /dev/null +++ b/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_off.xml @@ -0,0 +1,23 @@ + + + + diff --git a/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_one.xml b/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_one.xml new file mode 100644 index 0000000000..d51010566a --- /dev/null +++ b/extensions/mediasession/src/main/res/drawable-anydpi-v21/exo_media_action_repeat_one.xml @@ -0,0 +1,23 @@ + + + + diff --git a/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_all.png b/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_all.png new file mode 100644 index 0000000000..2824e7847c Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_all.png differ diff --git a/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_off.png b/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_off.png new file mode 100644 index 0000000000..0b92f583da Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_off.png differ diff --git a/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_one.png b/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_one.png new file mode 100644 index 0000000000..232aa2b1cd Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-hdpi/exo_media_action_repeat_one.png differ diff --git a/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_all.png b/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_all.png new file mode 100644 index 0000000000..5c91a47519 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_all.png differ diff --git a/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_off.png b/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_off.png new file mode 100644 index 0000000000..a94abd864f Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_off.png differ diff --git a/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_one.png b/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_one.png new file mode 100644 index 0000000000..a59a985239 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-ldpi/exo_media_action_repeat_one.png differ diff --git a/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_all.png b/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_all.png new file mode 100644 index 0000000000..97f7e1cc75 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_all.png differ diff --git a/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_off.png b/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_off.png new file mode 100644 index 0000000000..6a02321702 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_off.png differ diff --git a/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_one.png b/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_one.png new file mode 100644 index 0000000000..59bac33705 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-mdpi/exo_media_action_repeat_one.png differ diff --git a/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_all.png b/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_all.png new file mode 100644 index 0000000000..2baaedecbf Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_all.png differ diff --git a/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_off.png b/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_off.png new file mode 100644 index 0000000000..2468f92f9f Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_off.png differ diff --git a/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_one.png b/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_one.png new file mode 100644 index 0000000000..4e1d53db77 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-xhdpi/exo_media_action_repeat_one.png differ diff --git a/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_all.png b/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_all.png new file mode 100644 index 0000000000..d7207ebc0d Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_all.png differ diff --git a/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_off.png b/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_off.png new file mode 100644 index 0000000000..4d6253ead6 Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_off.png differ diff --git a/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_one.png b/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_one.png new file mode 100644 index 0000000000..d577f4ebcd Binary files /dev/null and b/extensions/mediasession/src/main/res/drawable-xxhdpi/exo_media_action_repeat_one.png differ diff --git a/extensions/mediasession/src/main/res/values-af/strings.xml b/extensions/mediasession/src/main/res/values-af/strings.xml new file mode 100644 index 0000000000..4ef78cd84f --- /dev/null +++ b/extensions/mediasession/src/main/res/values-af/strings.xml @@ -0,0 +1,21 @@ + + + + "Herhaal alles" + "Herhaal niks" + "Herhaal een" + diff --git a/extensions/mediasession/src/main/res/values-am/strings.xml b/extensions/mediasession/src/main/res/values-am/strings.xml new file mode 100644 index 0000000000..531f605584 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-am/strings.xml @@ -0,0 +1,21 @@ + + + + "ሁሉንም ድገም" + "ምንም አትድገም" + "አንዱን ድገም" + diff --git a/extensions/mediasession/src/main/res/values-ar/strings.xml b/extensions/mediasession/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..0101a746e0 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ar/strings.xml @@ -0,0 +1,21 @@ + + + + "تكرار الكل" + "عدم التكرار" + "تكرار مقطع واحد" + diff --git a/extensions/mediasession/src/main/res/values-az-rAZ/strings.xml b/extensions/mediasession/src/main/res/values-az-rAZ/strings.xml new file mode 100644 index 0000000000..34408143fa --- /dev/null +++ b/extensions/mediasession/src/main/res/values-az-rAZ/strings.xml @@ -0,0 +1,21 @@ + + + + "Bütün təkrarlayın" + "Təkrar bir" + "Heç bir təkrar" + diff --git a/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml b/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml new file mode 100644 index 0000000000..67a51cf85e --- /dev/null +++ b/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,21 @@ + + + + "Ponovi sve" + "Ne ponavljaj nijednu" + "Ponovi jednu" + diff --git a/extensions/mediasession/src/main/res/values-be-rBY/strings.xml b/extensions/mediasession/src/main/res/values-be-rBY/strings.xml new file mode 100644 index 0000000000..2f05607235 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-be-rBY/strings.xml @@ -0,0 +1,21 @@ + + + + "Паўтарыць усё" + "Паўтараць ні" + "Паўтарыць адзін" + diff --git a/extensions/mediasession/src/main/res/values-bg/strings.xml b/extensions/mediasession/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..16910d640a --- /dev/null +++ b/extensions/mediasession/src/main/res/values-bg/strings.xml @@ -0,0 +1,21 @@ + + + + "Повтаряне на всички" + "Без повтаряне" + "Повтаряне на един елемент" + diff --git a/extensions/mediasession/src/main/res/values-bn-rBD/strings.xml b/extensions/mediasession/src/main/res/values-bn-rBD/strings.xml new file mode 100644 index 0000000000..8872b464c6 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-bn-rBD/strings.xml @@ -0,0 +1,21 @@ + + + + "সবগুলির পুনরাবৃত্তি করুন" + "একটিরও পুনরাবৃত্তি করবেন না" + "একটির পুনরাবৃত্তি করুন" + diff --git a/extensions/mediasession/src/main/res/values-bs-rBA/strings.xml b/extensions/mediasession/src/main/res/values-bs-rBA/strings.xml new file mode 100644 index 0000000000..d0bf068573 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-bs-rBA/strings.xml @@ -0,0 +1,21 @@ + + + + "Ponovite sve" + "Ne ponavljaju" + "Ponovite jedan" + diff --git a/extensions/mediasession/src/main/res/values-ca/strings.xml b/extensions/mediasession/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000000..89414d736e --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ca/strings.xml @@ -0,0 +1,21 @@ + + + + "Repeteix-ho tot" + "No en repeteixis cap" + "Repeteix-ne un" + diff --git a/extensions/mediasession/src/main/res/values-cs/strings.xml b/extensions/mediasession/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000000..784d872570 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-cs/strings.xml @@ -0,0 +1,21 @@ + + + + "Opakovat vše" + "Neopakovat" + "Opakovat jednu položku" + diff --git a/extensions/mediasession/src/main/res/values-da/strings.xml b/extensions/mediasession/src/main/res/values-da/strings.xml new file mode 100644 index 0000000000..2c9784d122 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-da/strings.xml @@ -0,0 +1,21 @@ + + + + "Gentag alle" + "Gentag ingen" + "Gentag en" + diff --git a/extensions/mediasession/src/main/res/values-de/strings.xml b/extensions/mediasession/src/main/res/values-de/strings.xml new file mode 100644 index 0000000000..c11e449665 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-de/strings.xml @@ -0,0 +1,21 @@ + + + + "Alle wiederholen" + "Keinen Titel wiederholen" + "Einen Titel wiederholen" + diff --git a/extensions/mediasession/src/main/res/values-el/strings.xml b/extensions/mediasession/src/main/res/values-el/strings.xml new file mode 100644 index 0000000000..6279af5d64 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-el/strings.xml @@ -0,0 +1,21 @@ + + + + "Επανάληψη όλων" + "Καμία επανάληψη" + "Επανάληψη ενός στοιχείου" + diff --git a/extensions/mediasession/src/main/res/values-en-rAU/strings.xml b/extensions/mediasession/src/main/res/values-en-rAU/strings.xml new file mode 100644 index 0000000000..a3fccf8b52 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-en-rAU/strings.xml @@ -0,0 +1,21 @@ + + + + "Repeat all" + "Repeat none" + "Repeat one" + diff --git a/extensions/mediasession/src/main/res/values-en-rGB/strings.xml b/extensions/mediasession/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000..a3fccf8b52 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,21 @@ + + + + "Repeat all" + "Repeat none" + "Repeat one" + diff --git a/extensions/mediasession/src/main/res/values-en-rIN/strings.xml b/extensions/mediasession/src/main/res/values-en-rIN/strings.xml new file mode 100644 index 0000000000..a3fccf8b52 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-en-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "Repeat all" + "Repeat none" + "Repeat one" + diff --git a/extensions/mediasession/src/main/res/values-es-rUS/strings.xml b/extensions/mediasession/src/main/res/values-es-rUS/strings.xml new file mode 100644 index 0000000000..0fe29d3d5a --- /dev/null +++ b/extensions/mediasession/src/main/res/values-es-rUS/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetir todo" + "No repetir" + "Repetir uno" + diff --git a/extensions/mediasession/src/main/res/values-es/strings.xml b/extensions/mediasession/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..0fe29d3d5a --- /dev/null +++ b/extensions/mediasession/src/main/res/values-es/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetir todo" + "No repetir" + "Repetir uno" + diff --git a/extensions/mediasession/src/main/res/values-et-rEE/strings.xml b/extensions/mediasession/src/main/res/values-et-rEE/strings.xml new file mode 100644 index 0000000000..1bc3b59706 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-et-rEE/strings.xml @@ -0,0 +1,21 @@ + + + + "Korda kõike" + "Ära korda midagi" + "Korda ühte" + diff --git a/extensions/mediasession/src/main/res/values-eu-rES/strings.xml b/extensions/mediasession/src/main/res/values-eu-rES/strings.xml new file mode 100644 index 0000000000..f15f03160f --- /dev/null +++ b/extensions/mediasession/src/main/res/values-eu-rES/strings.xml @@ -0,0 +1,21 @@ + + + + "Errepikatu guztiak" + "Ez errepikatu" + "Errepikatu bat" + diff --git a/extensions/mediasession/src/main/res/values-fa/strings.xml b/extensions/mediasession/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000000..e37a08de64 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-fa/strings.xml @@ -0,0 +1,21 @@ + + + + "تکرار همه" + "تکرار هیچ‌کدام" + "یک‌بار تکرار" + diff --git a/extensions/mediasession/src/main/res/values-fi/strings.xml b/extensions/mediasession/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000000..c920827976 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-fi/strings.xml @@ -0,0 +1,21 @@ + + + + "Toista kaikki" + "Toista ei mitään" + "Toista yksi" + diff --git a/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml b/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000000..c5191e74a9 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml @@ -0,0 +1,21 @@ + + + + "Tout lire en boucle" + "Aucune répétition" + "Répéter un élément" + diff --git a/extensions/mediasession/src/main/res/values-fr/strings.xml b/extensions/mediasession/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..1d76358d1f --- /dev/null +++ b/extensions/mediasession/src/main/res/values-fr/strings.xml @@ -0,0 +1,21 @@ + + + + "Tout lire en boucle" + "Ne rien lire en boucle" + "Lire en boucle un élément" + diff --git a/extensions/mediasession/src/main/res/values-gl-rES/strings.xml b/extensions/mediasession/src/main/res/values-gl-rES/strings.xml new file mode 100644 index 0000000000..6b65b3e843 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-gl-rES/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetir todo" + "Non repetir" + "Repetir un" + diff --git a/extensions/mediasession/src/main/res/values-gu-rIN/strings.xml b/extensions/mediasession/src/main/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000000..0eb9cab37e --- /dev/null +++ b/extensions/mediasession/src/main/res/values-gu-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "બધા પુનરાવર્તન કરો" + "કંઈ પુનરાવર્તન કરો" + "એક પુનરાવર્તન કરો" + diff --git a/extensions/mediasession/src/main/res/values-hi/strings.xml b/extensions/mediasession/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000000..8ce336d5e5 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-hi/strings.xml @@ -0,0 +1,21 @@ + + + + "सभी को दोहराएं" + "कुछ भी न दोहराएं" + "एक दोहराएं" + diff --git a/extensions/mediasession/src/main/res/values-hr/strings.xml b/extensions/mediasession/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000000..9f995ec15b --- /dev/null +++ b/extensions/mediasession/src/main/res/values-hr/strings.xml @@ -0,0 +1,21 @@ + + + + "Ponovi sve" + "Bez ponavljanja" + "Ponovi jedno" + diff --git a/extensions/mediasession/src/main/res/values-hu/strings.xml b/extensions/mediasession/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..2335ade72e --- /dev/null +++ b/extensions/mediasession/src/main/res/values-hu/strings.xml @@ -0,0 +1,21 @@ + + + + "Összes ismétlése" + "Nincs ismétlés" + "Egy ismétlése" + diff --git a/extensions/mediasession/src/main/res/values-hy-rAM/strings.xml b/extensions/mediasession/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000000..19a89e6c87 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,21 @@ + + + + "կրկնել այն ամենը" + "Չկրկնել" + "Կրկնել մեկը" + diff --git a/extensions/mediasession/src/main/res/values-in/strings.xml b/extensions/mediasession/src/main/res/values-in/strings.xml new file mode 100644 index 0000000000..093a7f8576 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-in/strings.xml @@ -0,0 +1,21 @@ + + + + "Ulangi Semua" + "Jangan Ulangi" + "Ulangi Satu" + diff --git a/extensions/mediasession/src/main/res/values-is-rIS/strings.xml b/extensions/mediasession/src/main/res/values-is-rIS/strings.xml new file mode 100644 index 0000000000..b200abbdb2 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-is-rIS/strings.xml @@ -0,0 +1,21 @@ + + + + "Endurtaka allt" + "Endurtaka ekkert" + "Endurtaka eitt" + diff --git a/extensions/mediasession/src/main/res/values-it/strings.xml b/extensions/mediasession/src/main/res/values-it/strings.xml new file mode 100644 index 0000000000..c0682519f9 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-it/strings.xml @@ -0,0 +1,21 @@ + + + + "Ripeti tutti" + "Non ripetere nessuno" + "Ripeti uno" + diff --git a/extensions/mediasession/src/main/res/values-iw/strings.xml b/extensions/mediasession/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000000..5cf23d5a4c --- /dev/null +++ b/extensions/mediasession/src/main/res/values-iw/strings.xml @@ -0,0 +1,21 @@ + + + + "חזור על הכל" + "אל תחזור על כלום" + "חזור על פריט אחד" + diff --git a/extensions/mediasession/src/main/res/values-ja/strings.xml b/extensions/mediasession/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000000..6f543fbdee --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ja/strings.xml @@ -0,0 +1,21 @@ + + + + "全曲を繰り返し" + "繰り返しなし" + "1曲を繰り返し" + diff --git a/extensions/mediasession/src/main/res/values-ka-rGE/strings.xml b/extensions/mediasession/src/main/res/values-ka-rGE/strings.xml new file mode 100644 index 0000000000..96656612a7 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ka-rGE/strings.xml @@ -0,0 +1,21 @@ + + + + "გამეორება ყველა" + "გაიმეორეთ არცერთი" + "გაიმეორეთ ერთი" + diff --git a/extensions/mediasession/src/main/res/values-kk-rKZ/strings.xml b/extensions/mediasession/src/main/res/values-kk-rKZ/strings.xml new file mode 100644 index 0000000000..be4140120d --- /dev/null +++ b/extensions/mediasession/src/main/res/values-kk-rKZ/strings.xml @@ -0,0 +1,21 @@ + + + + "Барлығын қайталау" + "Ешқайсысын қайталамау" + "Біреуін қайталау" + diff --git a/extensions/mediasession/src/main/res/values-km-rKH/strings.xml b/extensions/mediasession/src/main/res/values-km-rKH/strings.xml new file mode 100644 index 0000000000..dd4b734e30 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-km-rKH/strings.xml @@ -0,0 +1,21 @@ + + + + "ធ្វើ​ម្ដង​ទៀត​ទាំងអស់" + "មិន​ធ្វើ​ឡើង​វិញ" + "ធ្វើ​​ឡើងវិញ​ម្ដង" + diff --git a/extensions/mediasession/src/main/res/values-kn-rIN/strings.xml b/extensions/mediasession/src/main/res/values-kn-rIN/strings.xml new file mode 100644 index 0000000000..3d79aca9e2 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-kn-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "ಎಲ್ಲವನ್ನು ಪುನರಾವರ್ತಿಸಿ" + "ಯಾವುದನ್ನೂ ಪುನರಾವರ್ತಿಸಬೇಡಿ" + "ಒಂದನ್ನು ಪುನರಾವರ್ತಿಸಿ" + diff --git a/extensions/mediasession/src/main/res/values-ko/strings.xml b/extensions/mediasession/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000000..d269937771 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ko/strings.xml @@ -0,0 +1,21 @@ + + + + "전체 반복" + "반복 안함" + "한 항목 반복" + diff --git a/extensions/mediasession/src/main/res/values-ky-rKG/strings.xml b/extensions/mediasession/src/main/res/values-ky-rKG/strings.xml new file mode 100644 index 0000000000..a8978ecc61 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ky-rKG/strings.xml @@ -0,0 +1,21 @@ + + + + "Баарын кайталоо" + "Эч бирин кайталабоо" + "Бирөөнү кайталоо" + diff --git a/extensions/mediasession/src/main/res/values-lo-rLA/strings.xml b/extensions/mediasession/src/main/res/values-lo-rLA/strings.xml new file mode 100644 index 0000000000..950a9ba097 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-lo-rLA/strings.xml @@ -0,0 +1,21 @@ + + + + "ຫຼິ້ນ​ຊ້ຳ​ທັງ​ໝົດ" + "​ບໍ່ຫຼິ້ນ​ຊ້ຳ" + "ຫຼິ້ນ​ຊ້ຳ" + diff --git a/extensions/mediasession/src/main/res/values-lt/strings.xml b/extensions/mediasession/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000000..ae8f1cf8c3 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-lt/strings.xml @@ -0,0 +1,21 @@ + + + + "Kartoti viską" + "Nekartoti nieko" + "Kartoti vieną" + diff --git a/extensions/mediasession/src/main/res/values-lv/strings.xml b/extensions/mediasession/src/main/res/values-lv/strings.xml new file mode 100644 index 0000000000..a69f6a0ad5 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-lv/strings.xml @@ -0,0 +1,21 @@ + + + + "Atkārtot visu" + "Neatkārtot nevienu" + "Atkārtot vienu" + diff --git a/extensions/mediasession/src/main/res/values-mk-rMK/strings.xml b/extensions/mediasession/src/main/res/values-mk-rMK/strings.xml new file mode 100644 index 0000000000..ddf2a60c20 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-mk-rMK/strings.xml @@ -0,0 +1,21 @@ + + + + "Повтори ги сите" + "Не повторувај ниту една" + "Повтори една" + diff --git a/extensions/mediasession/src/main/res/values-ml-rIN/strings.xml b/extensions/mediasession/src/main/res/values-ml-rIN/strings.xml new file mode 100644 index 0000000000..6f869e2931 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ml-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "എല്ലാം ആവർത്തിക്കുക" + "ഒന്നും ആവർത്തിക്കരുത്" + "ഒന്ന് ആവർത്തിക്കുക" + diff --git a/extensions/mediasession/src/main/res/values-mn-rMN/strings.xml b/extensions/mediasession/src/main/res/values-mn-rMN/strings.xml new file mode 100644 index 0000000000..8d3074b91a --- /dev/null +++ b/extensions/mediasession/src/main/res/values-mn-rMN/strings.xml @@ -0,0 +1,21 @@ + + + + "Бүгдийг давтах" + "Алийг нь ч давтахгүй" + "Нэгийг давтах" + diff --git a/extensions/mediasession/src/main/res/values-mr-rIN/strings.xml b/extensions/mediasession/src/main/res/values-mr-rIN/strings.xml new file mode 100644 index 0000000000..6e4bfccc16 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-mr-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "सर्व पुनरावृत्ती करा" + "काहीही पुनरावृत्ती करू नका" + "एक पुनरावृत्ती करा" + diff --git a/extensions/mediasession/src/main/res/values-ms-rMY/strings.xml b/extensions/mediasession/src/main/res/values-ms-rMY/strings.xml new file mode 100644 index 0000000000..829542b668 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ms-rMY/strings.xml @@ -0,0 +1,21 @@ + + + + "Ulang semua" + "Tiada ulangan" + "Ulangan" + diff --git a/extensions/mediasession/src/main/res/values-my-rMM/strings.xml b/extensions/mediasession/src/main/res/values-my-rMM/strings.xml new file mode 100644 index 0000000000..aeb1375ebf --- /dev/null +++ b/extensions/mediasession/src/main/res/values-my-rMM/strings.xml @@ -0,0 +1,21 @@ + + + + "အားလုံး ထပ်တလဲလဲဖွင့်ရန်" + "ထပ်တလဲလဲမဖွင့်ရန်" + "တစ်ခုအား ထပ်တလဲလဲဖွင့်ရန်" + diff --git a/extensions/mediasession/src/main/res/values-nb/strings.xml b/extensions/mediasession/src/main/res/values-nb/strings.xml new file mode 100644 index 0000000000..10f334b226 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-nb/strings.xml @@ -0,0 +1,21 @@ + + + + "Gjenta alle" + "Ikke gjenta noen" + "Gjenta én" + diff --git a/extensions/mediasession/src/main/res/values-ne-rNP/strings.xml b/extensions/mediasession/src/main/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000000..6d81ce5684 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ne-rNP/strings.xml @@ -0,0 +1,21 @@ + + + + "सबै दोहोर्याउनुहोस्" + "कुनै पनि नदोहोर्याउनुहोस्" + "एउटा दोहोर्याउनुहोस्" + diff --git a/extensions/mediasession/src/main/res/values-nl/strings.xml b/extensions/mediasession/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000000..55997be098 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-nl/strings.xml @@ -0,0 +1,21 @@ + + + + "Alles herhalen" + "Niet herhalen" + "Eén herhalen" + diff --git a/extensions/mediasession/src/main/res/values-pa-rIN/strings.xml b/extensions/mediasession/src/main/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000000..8eee0bee16 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-pa-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "ਸਭ ਨੂੰ ਦੁਹਰਾਓ" + "ਕੋਈ ਵੀ ਨਹੀਂ ਦੁਹਰਾਓ" + "ਇੱਕ ਦੁਹਰਾਓ" + diff --git a/extensions/mediasession/src/main/res/values-pl/strings.xml b/extensions/mediasession/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000000..6a52d58b63 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-pl/strings.xml @@ -0,0 +1,21 @@ + + + + "Powtórz wszystkie" + "Nie powtarzaj" + "Powtórz jeden" + diff --git a/extensions/mediasession/src/main/res/values-pt-rBR/strings.xml b/extensions/mediasession/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..efb8fc433f --- /dev/null +++ b/extensions/mediasession/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetir tudo" + "Não repetir" + "Repetir um" + diff --git a/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml b/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..efb8fc433f --- /dev/null +++ b/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetir tudo" + "Não repetir" + "Repetir um" + diff --git a/extensions/mediasession/src/main/res/values-pt/strings.xml b/extensions/mediasession/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000000..aadebbb3b0 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-pt/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetir tudo" + "Não repetir" + "Repetir uma" + diff --git a/extensions/mediasession/src/main/res/values-ro/strings.xml b/extensions/mediasession/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000000..f6aee447e5 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ro/strings.xml @@ -0,0 +1,21 @@ + + + + "Repetați toate" + "Repetați niciuna" + "Repetați unul" + diff --git a/extensions/mediasession/src/main/res/values-ru/strings.xml b/extensions/mediasession/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..575ad9f930 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ru/strings.xml @@ -0,0 +1,21 @@ + + + + "Повторять все" + "Не повторять" + "Повторять один элемент" + diff --git a/extensions/mediasession/src/main/res/values-si-rLK/strings.xml b/extensions/mediasession/src/main/res/values-si-rLK/strings.xml new file mode 100644 index 0000000000..8e172ac268 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-si-rLK/strings.xml @@ -0,0 +1,21 @@ + + + + "සියලු නැවත" + "කිසිවක් නැවත" + "නැවත නැවත එක්" + diff --git a/extensions/mediasession/src/main/res/values-sk/strings.xml b/extensions/mediasession/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000000..5d092003e5 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-sk/strings.xml @@ -0,0 +1,21 @@ + + + + "Opakovať všetko" + "Neopakovať" + "Opakovať jednu položku" + diff --git a/extensions/mediasession/src/main/res/values-sl/strings.xml b/extensions/mediasession/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000000..ecac3800c8 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-sl/strings.xml @@ -0,0 +1,21 @@ + + + + "Ponovi vse" + "Ne ponovi" + "Ponovi eno" + diff --git a/extensions/mediasession/src/main/res/values-sq-rAL/strings.xml b/extensions/mediasession/src/main/res/values-sq-rAL/strings.xml new file mode 100644 index 0000000000..6da24cc4c7 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-sq-rAL/strings.xml @@ -0,0 +1,21 @@ + + + + "Përsërit të gjithë" + "Përsëritni asnjë" + "Përsëritni një" + diff --git a/extensions/mediasession/src/main/res/values-sr/strings.xml b/extensions/mediasession/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000000..881cb2703b --- /dev/null +++ b/extensions/mediasession/src/main/res/values-sr/strings.xml @@ -0,0 +1,18 @@ + + + + diff --git a/extensions/mediasession/src/main/res/values-sv/strings.xml b/extensions/mediasession/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000000..3a7bb630aa --- /dev/null +++ b/extensions/mediasession/src/main/res/values-sv/strings.xml @@ -0,0 +1,21 @@ + + + + "Upprepa alla" + "Upprepa inga" + "Upprepa en" + diff --git a/extensions/mediasession/src/main/res/values-sw/strings.xml b/extensions/mediasession/src/main/res/values-sw/strings.xml new file mode 100644 index 0000000000..726012ab88 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-sw/strings.xml @@ -0,0 +1,21 @@ + + + + "Rudia zote" + "Usirudie Yoyote" + "Rudia Moja" + diff --git a/extensions/mediasession/src/main/res/values-ta-rIN/strings.xml b/extensions/mediasession/src/main/res/values-ta-rIN/strings.xml new file mode 100644 index 0000000000..9364bc0be2 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ta-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "அனைத்தையும் மீண்டும் இயக்கு" + "எதையும் மீண்டும் இயக்காதே" + "ஒன்றை மட்டும் மீண்டும் இயக்கு" + diff --git a/extensions/mediasession/src/main/res/values-te-rIN/strings.xml b/extensions/mediasession/src/main/res/values-te-rIN/strings.xml new file mode 100644 index 0000000000..b7ee7345d5 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-te-rIN/strings.xml @@ -0,0 +1,21 @@ + + + + "అన్నీ పునరావృతం చేయి" + "ఏదీ పునరావృతం చేయవద్దు" + "ఒకదాన్ని పునరావృతం చేయి" + diff --git a/extensions/mediasession/src/main/res/values-th/strings.xml b/extensions/mediasession/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..af502b3a4c --- /dev/null +++ b/extensions/mediasession/src/main/res/values-th/strings.xml @@ -0,0 +1,21 @@ + + + + "เล่นซ้ำทั้งหมด" + "ไม่เล่นซ้ำ" + "เล่นซ้ำรายการเดียว" + diff --git a/extensions/mediasession/src/main/res/values-tl/strings.xml b/extensions/mediasession/src/main/res/values-tl/strings.xml new file mode 100644 index 0000000000..239972a4c7 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-tl/strings.xml @@ -0,0 +1,21 @@ + + + + "Ulitin Lahat" + "Walang Uulitin" + "Ulitin ang Isa" + diff --git a/extensions/mediasession/src/main/res/values-tr/strings.xml b/extensions/mediasession/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000000..89a98b1ed9 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-tr/strings.xml @@ -0,0 +1,21 @@ + + + + "Tümünü Tekrarla" + "Hiçbirini Tekrarlama" + "Birini Tekrarla" + diff --git a/extensions/mediasession/src/main/res/values-uk/strings.xml b/extensions/mediasession/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000000..4e1d25eb8a --- /dev/null +++ b/extensions/mediasession/src/main/res/values-uk/strings.xml @@ -0,0 +1,21 @@ + + + + "Повторити все" + "Не повторювати" + "Повторити один елемент" + diff --git a/extensions/mediasession/src/main/res/values-ur-rPK/strings.xml b/extensions/mediasession/src/main/res/values-ur-rPK/strings.xml new file mode 100644 index 0000000000..ab2631a4ec --- /dev/null +++ b/extensions/mediasession/src/main/res/values-ur-rPK/strings.xml @@ -0,0 +1,21 @@ + + + + "سبھی کو دہرائیں" + "کسی کو نہ دہرائیں" + "ایک کو دہرائیں" + diff --git a/extensions/mediasession/src/main/res/values-uz-rUZ/strings.xml b/extensions/mediasession/src/main/res/values-uz-rUZ/strings.xml new file mode 100644 index 0000000000..c32d00af8e --- /dev/null +++ b/extensions/mediasession/src/main/res/values-uz-rUZ/strings.xml @@ -0,0 +1,21 @@ + + + + "Barchasini takrorlash" + "Takrorlamaslik" + "Bir marta takrorlash" + diff --git a/extensions/mediasession/src/main/res/values-vi/strings.xml b/extensions/mediasession/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000000..dabc9e05d5 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-vi/strings.xml @@ -0,0 +1,21 @@ + + + + "Lặp lại tất cả" + "Không lặp lại" + "Lặp lại một mục" + diff --git a/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml b/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..beb3403cb9 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,21 @@ + + + + "重复播放全部" + "不重复播放" + "重复播放单个视频" + diff --git a/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml b/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000000..775cd6441c --- /dev/null +++ b/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,21 @@ + + + + "重複播放所有媒體項目" + "不重複播放任何媒體項目" + "重複播放一個媒體項目" + diff --git a/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml b/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..d3789f4145 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,21 @@ + + + + "重複播放所有媒體項目" + "不重複播放" + "重複播放單一媒體項目" + diff --git a/extensions/mediasession/src/main/res/values-zu/strings.xml b/extensions/mediasession/src/main/res/values-zu/strings.xml new file mode 100644 index 0000000000..789b6fecb4 --- /dev/null +++ b/extensions/mediasession/src/main/res/values-zu/strings.xml @@ -0,0 +1,21 @@ + + + + "Phinda konke" + "Ungaphindi lutho" + "Phida okukodwa" + diff --git a/extensions/mediasession/src/main/res/values/strings.xml b/extensions/mediasession/src/main/res/values/strings.xml new file mode 100644 index 0000000000..72a67ff01c --- /dev/null +++ b/extensions/mediasession/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + Repeat none + Repeat one + Repeat all + diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java new file mode 100644 index 0000000000..07850269f9 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java @@ -0,0 +1,93 @@ +/* + * 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.util; + +import android.support.annotation.IntDef; + +import com.google.android.exoplayer2.ExoPlayer; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Util class for repeat mode handling. + */ +public final class RepeatModeUtil { + + /** + * Set of repeat toggle modes. Can be combined using bit-wise operations. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {REPEAT_TOGGLE_MODE_NONE, REPEAT_TOGGLE_MODE_ONE, + REPEAT_TOGGLE_MODE_ALL}) + public @interface RepeatToggleModes {} + /** + * All repeat mode buttons disabled. + */ + public static final int REPEAT_TOGGLE_MODE_NONE = 0; + /** + * "Repeat One" button enabled. + */ + public static final int REPEAT_TOGGLE_MODE_ONE = 1; + /** + * "Repeat All" button enabled. + */ + public static final int REPEAT_TOGGLE_MODE_ALL = 2; + + private RepeatModeUtil() { + // Prevent instantiation. + } + + /** + * Gets the next repeat mode out of {@code enabledModes} starting from {@code currentMode}. + * + * @param currentMode The current repeat mode. + * @param enabledModes Bitmask of enabled modes. + * @return The next repeat mode. + */ + public static @ExoPlayer.RepeatMode int getNextRepeatMode( + @ExoPlayer.RepeatMode int currentMode, int enabledModes) { + for (int offset = 1; offset <= 2; offset++) { + @ExoPlayer.RepeatMode int proposedMode = (currentMode + offset) % 3; + if (isRepeatModeEnabled(proposedMode, enabledModes)) { + return proposedMode; + } + } + return currentMode; + } + + /** + * Verifies whether a given {@code repeatMode} is enabled in the bitmask {@code enabledModes}. + * + * @param repeatMode The mode to check. + * @param enabledModes The bitmask representing the enabled modes. + * @return {@code true} if enabled. + */ + public static boolean isRepeatModeEnabled(@ExoPlayer.RepeatMode int repeatMode, + int enabledModes) { + switch (repeatMode) { + case ExoPlayer.REPEAT_MODE_OFF: + return true; + case ExoPlayer.REPEAT_MODE_ONE: + return (enabledModes & REPEAT_TOGGLE_MODE_ONE) != 0; + case ExoPlayer.REPEAT_MODE_ALL: + return (enabledModes & REPEAT_TOGGLE_MODE_ALL) != 0; + default: + return false; + } + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index ca1a967434..6a4f258dd2 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -22,7 +22,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.SystemClock; -import android.support.annotation.IntDef; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -39,9 +38,8 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Formatter; import java.util.Locale; @@ -82,7 +80,7 @@ import java.util.Locale; * {@code all}, or {@code one|all}. *

* *
  • {@code controller_layout_id} - Specifies the id of the layout to be inflated. See @@ -249,30 +247,11 @@ public class PlaybackControlView extends FrameLayout { }; - /** - * Set of repeat toggle modes. Can be combined using bit-wise operations. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {REPEAT_TOGGLE_MODE_NONE, REPEAT_TOGGLE_MODE_ONE, - REPEAT_TOGGLE_MODE_ALL}) - public @interface RepeatToggleModes {} - /** - * All repeat mode buttons disabled. - */ - public static final int REPEAT_TOGGLE_MODE_NONE = 0; - /** - * "Repeat One" button enabled. - */ - public static final int REPEAT_TOGGLE_MODE_ONE = 1; - /** - * "Repeat All" button enabled. - */ - public static final int REPEAT_TOGGLE_MODE_ALL = 2; - public static final int DEFAULT_FAST_FORWARD_MS = 15000; public static final int DEFAULT_REWIND_MS = 5000; public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000; - public static final @RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES = REPEAT_TOGGLE_MODE_NONE; + public static final @RepeatModeUtil.RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES + = RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE; /** * The maximum number of windows that can be shown in a multi-window time bar. @@ -315,7 +294,7 @@ public class PlaybackControlView extends FrameLayout { private int rewindMs; private int fastForwardMs; private int showTimeoutMs; - private @RepeatToggleModes int repeatToggleModes; + private @RepeatModeUtil.RepeatToggleModes int repeatToggleModes; private long hideAtMs; private long[] adGroupTimesMs; private boolean[] playedAdGroups; @@ -424,8 +403,8 @@ public class PlaybackControlView extends FrameLayout { } @SuppressWarnings("ResourceType") - private static @RepeatToggleModes int getRepeatToggleModes(TypedArray a, - @RepeatToggleModes int repeatToggleModes) { + private static @RepeatModeUtil.RepeatToggleModes int getRepeatToggleModes(TypedArray a, + @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { return a.getInt(R.styleable.PlaybackControlView_repeat_toggle_modes, repeatToggleModes); } @@ -535,28 +514,28 @@ public class PlaybackControlView extends FrameLayout { /** * Returns which repeat toggle modes are enabled. * - * @return The currently enabled {@link RepeatToggleModes}. + * @return The currently enabled {@link RepeatModeUtil.RepeatToggleModes}. */ - public @RepeatToggleModes int getRepeatToggleModes() { + public @RepeatModeUtil.RepeatToggleModes int getRepeatToggleModes() { return repeatToggleModes; } /** * Sets which repeat toggle modes are enabled. * - * @param repeatToggleModes A set of {@link RepeatToggleModes}. + * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}. */ - public void setRepeatToggleModes(@RepeatToggleModes int repeatToggleModes) { + public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { this.repeatToggleModes = repeatToggleModes; if (player != null) { @ExoPlayer.RepeatMode int currentMode = player.getRepeatMode(); - if (repeatToggleModes == REPEAT_TOGGLE_MODE_NONE + if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE && currentMode != ExoPlayer.REPEAT_MODE_OFF) { controlDispatcher.dispatchSetRepeatMode(player, ExoPlayer.REPEAT_MODE_OFF); - } else if (repeatToggleModes == REPEAT_TOGGLE_MODE_ONE + } else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE && currentMode == ExoPlayer.REPEAT_MODE_ALL) { controlDispatcher.dispatchSetRepeatMode(player, ExoPlayer.REPEAT_MODE_ONE); - } else if (repeatToggleModes == REPEAT_TOGGLE_MODE_ALL + } else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL && currentMode == ExoPlayer.REPEAT_MODE_ONE) { controlDispatcher.dispatchSetRepeatMode(player, ExoPlayer.REPEAT_MODE_ALL); } @@ -674,7 +653,7 @@ public class PlaybackControlView extends FrameLayout { if (!isVisible() || !isAttachedToWindow || repeatToggleButton == null) { return; } - if (repeatToggleModes == REPEAT_TOGGLE_MODE_NONE) { + if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { repeatToggleButton.setVisibility(View.GONE); return; } @@ -859,30 +838,6 @@ public class PlaybackControlView extends FrameLayout { } } - private @ExoPlayer.RepeatMode int getNextRepeatMode() { - @ExoPlayer.RepeatMode int currentMode = player.getRepeatMode(); - for (int offset = 1; offset <= 2; offset++) { - @ExoPlayer.RepeatMode int proposedMode = (currentMode + offset) % 3; - if (isRepeatModeEnabled(proposedMode)) { - return proposedMode; - } - } - return currentMode; - } - - private boolean isRepeatModeEnabled(@ExoPlayer.RepeatMode int repeatMode) { - switch (repeatMode) { - case ExoPlayer.REPEAT_MODE_OFF: - return true; - case ExoPlayer.REPEAT_MODE_ONE: - return (repeatToggleModes & REPEAT_TOGGLE_MODE_ONE) != 0; - case ExoPlayer.REPEAT_MODE_ALL: - return (repeatToggleModes & REPEAT_TOGGLE_MODE_ALL) != 0; - default: - return false; - } - } - private void rewind() { if (rewindMs <= 0) { return; @@ -1126,7 +1081,8 @@ public class PlaybackControlView extends FrameLayout { } else if (pauseButton == view) { controlDispatcher.dispatchSetPlayWhenReady(player, false); } else if (repeatToggleButton == view) { - controlDispatcher.dispatchSetRepeatMode(player, getNextRepeatMode()); + controlDispatcher.dispatchSetRepeatMode(player, RepeatModeUtil.getNextRepeatMode( + player.getRepeatMode(), repeatToggleModes)); } } hideAfterTimeout(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 7efa2c87ba..b3c79b9fdc 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -49,6 +49,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.PlaybackControlView.ControlDispatcher; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import java.util.List; @@ -637,9 +638,9 @@ public final class SimpleExoPlayerView extends FrameLayout { /** * Sets which repeat toggle modes are enabled. * - * @param repeatToggleModes A set of {@link PlaybackControlView.RepeatToggleModes}. + * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}. */ - public void setRepeatToggleModes(@PlaybackControlView.RepeatToggleModes int repeatToggleModes) { + public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { Assertions.checkState(controller != null); controller.setRepeatToggleModes(repeatToggleModes); }