diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4517a0b87f..913c5db6c1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,6 +3,17 @@ ### dev-v2 (not yet released) * New release notes go here! +* Core library: + * Log a warning when `SingleSampleMediaPeriod` transforms a load error + into end-of-stream. +* UI: + * Add builder for `PlayerNotificationManager`. +* Extractors: + * Fix Vorbis private codec data parsing in the Matroska extractor + ([#8496](https://github.com/google/ExoPlayer/issues/8496)). +* Text: + * Add support for the SSA `primaryColour` style attribute + ([#8435](https://github.com/google/ExoPlayer/issues/8435)). ### 2.13.0 (not yet released - targeted for 2021-02-TBD) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index e5b29b6a85..a1183c8a3f 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -23,6 +23,8 @@ import static com.google.android.exoplayer2.Player.EVENT_POSITION_DISCONTINUITY; import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkState; import android.app.Notification; import android.app.NotificationChannel; @@ -50,7 +52,6 @@ import com.google.android.exoplayer2.DefaultControlDispatcher; import com.google.android.exoplayer2.PlaybackPreparer; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; @@ -144,10 +145,12 @@ import java.util.Map; *
Alternatively, the action icons can be set programatically by using the {@link Builder}. + * + *
Unlike the drawables above, the large icon (i.e. the icon passed to {@link * NotificationCompat.Builder#setLargeIcon(Bitmap)} cannot be overridden in this way. Instead, the - * large icon is obtained from the {@link MediaDescriptionAdapter} injected when creating the - * PlayerNotificationManager. + * large icon is obtained from the {@link MediaDescriptionAdapter} passed to {@link + * Builder#Builder(Context, int, String, MediaDescriptionAdapter)}. */ public class PlayerNotificationManager { @@ -296,6 +299,249 @@ public class PlayerNotificationManager { int notificationId, Notification notification, boolean ongoing) {} } + /** A builder for {@link PlayerNotificationManager} instances. */ + public static class Builder { + + private final Context context; + private final int notificationId; + private final String channelId; + private final MediaDescriptionAdapter mediaDescriptionAdapter; + + @Nullable private NotificationListener notificationListener; + @Nullable private CustomActionReceiver customActionReceiver; + private int channelNameResourceId; + private int channelDescriptionResourceId; + private int channelImportance; + private int smallIconResourceId; + private int rewindActionIconResourceId; + private int playActionIconResourceId; + private int pauseActionIconResourceId; + private int stopActionIconResourceId; + private int fastForwardActionIconResourceId; + private int previousActionIconResourceId; + private int nextActionIconResourceId; + + /** + * Creates an instance. + * + * @param context The {@link Context}. + * @param notificationId The id of the notification to be posted. Must be greater than 0. + * @param channelId The id of the notification channel. + * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter} to be used. + */ + public Builder( + Context context, + int notificationId, + String channelId, + MediaDescriptionAdapter mediaDescriptionAdapter) { + checkArgument(notificationId > 0); + this.context = context; + this.notificationId = notificationId; + this.channelId = channelId; + this.mediaDescriptionAdapter = mediaDescriptionAdapter; + channelImportance = NotificationUtil.IMPORTANCE_LOW; + smallIconResourceId = R.drawable.exo_notification_small_icon; + playActionIconResourceId = R.drawable.exo_notification_play; + pauseActionIconResourceId = R.drawable.exo_notification_pause; + stopActionIconResourceId = R.drawable.exo_notification_stop; + rewindActionIconResourceId = R.drawable.exo_notification_rewind; + fastForwardActionIconResourceId = R.drawable.exo_notification_fastforward; + previousActionIconResourceId = R.drawable.exo_notification_previous; + nextActionIconResourceId = R.drawable.exo_notification_next; + } + + /** + * The name of the channel. If set to a value other than {@code 0}, the channel is automatically + * created when {@link #build()} is called. If the application has already created the + * notification channel, then this method should not be called. + * + *
The default is {@code 0}. + * + * @return This builder. + */ + public Builder setChannelNameResourceId(int channelNameResourceId) { + this.channelNameResourceId = channelNameResourceId; + return this; + } + + /** + * The description of the channel. Ignored if {@link #setChannelNameResourceId(int)} is not + * called with a value other than {@code 0}. If the application has already created the + * notification channel, then this method should not be called. + * + *
The default is {@code 0}. + * + * @return This builder. + */ + public Builder setChannelDescriptionResourceId(int channelDescriptionResourceId) { + this.channelDescriptionResourceId = channelDescriptionResourceId; + return this; + } + + /** + * The importance of the channel. Ignored if {@link #setChannelNameResourceId(int)} is not + * called with a value other than {@code 0}. If the application has already created the + * notification channel, then this method should not be called. + * + *
The default is {@link NotificationUtil#IMPORTANCE_LOW}. + * + * @return This builder. + */ + public Builder setChannelImportance(@NotificationUtil.Importance int channelImportance) { + this.channelImportance = channelImportance; + return this; + } + + /** + * The {@link NotificationListener} to be used. + * + *
The default is {@code null}. + * + * @return This builder. + */ + public Builder setNotificationListener(NotificationListener notificationListener) { + this.notificationListener = notificationListener; + return this; + } + + /** + * The {@link CustomActionReceiver} to be used. + * + *
The default is {@code null}. + * + * @return This builder. + */ + public Builder setCustomActionReceiver(CustomActionReceiver customActionReceiver) { + this.customActionReceiver = customActionReceiver; + return this; + } + + /** + * The resource id of the small icon of the notification shown in the status bar. See {@link + * NotificationCompat.Builder#setSmallIcon(int)}. + * + *
The default is {@link R.drawable#exo_notification_small_icon}. + * + * @return This builder. + */ + public Builder setSmallIconResourceId(int smallIconResourceId) { + this.smallIconResourceId = smallIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link #ACTION_PLAY}. + * + *
The default is {@link R.drawable#exo_notification_play}. + * + * @return This builder. + */ + public Builder setPlayActionIconResourceId(int playActionIconResourceId) { + this.playActionIconResourceId = playActionIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link #ACTION_PAUSE}. + * + *
The default is {@link R.drawable#exo_notification_pause}. + * + * @return This builder. + */ + public Builder setPauseActionIconResourceId(int pauseActionIconResourceId) { + this.pauseActionIconResourceId = pauseActionIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link #ACTION_STOP}. + * + *
The default is {@link R.drawable#exo_notification_stop}. + * + * @return This builder. + */ + public Builder setStopActionIconResourceId(int stopActionIconResourceId) { + this.stopActionIconResourceId = stopActionIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link #ACTION_REWIND}. + * + *
The default is {@link R.drawable#exo_notification_rewind}. + * + * @return This builder. + */ + public Builder setRewindActionIconResourceId(int rewindActionIconResourceId) { + this.rewindActionIconResourceId = rewindActionIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link + * #ACTION_FAST_FORWARD}. + * + *
The default is {@link R.drawable#exo_notification_fastforward}. + * + * @return This builder. + */ + public Builder setFastForwardActionIconResourceId(int fastForwardActionIconResourceId) { + this.fastForwardActionIconResourceId = fastForwardActionIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link #ACTION_PREVIOUS}. + * + *
The default is {@link R.drawable#exo_notification_previous}. + * + * @return This builder. + */ + public Builder setPreviousActionIconResourceId(int previousActionIconResourceId) { + this.previousActionIconResourceId = previousActionIconResourceId; + return this; + } + + /** + * The resource id of the drawable to be used as the icon of action {@link #ACTION_NEXT}. + * + *
The default is {@link R.drawable#exo_notification_next}. + * + * @return This builder. + */ + public Builder setNextActionIconResourceId(int nextActionIconResourceId) { + this.nextActionIconResourceId = nextActionIconResourceId; + return this; + } + + /** Builds the {@link PlayerNotificationManager}. */ + public PlayerNotificationManager build() { + if (channelNameResourceId != 0) { + NotificationUtil.createNotificationChannel( + context, + channelId, + channelNameResourceId, + channelDescriptionResourceId, + channelImportance); + } + return new PlayerNotificationManager( + context, + channelId, + notificationId, + mediaDescriptionAdapter, + notificationListener, + customActionReceiver, + smallIconResourceId, + playActionIconResourceId, + pauseActionIconResourceId, + stopActionIconResourceId, + rewindActionIconResourceId, + fastForwardActionIconResourceId, + previousActionIconResourceId, + nextActionIconResourceId); + } + } + /** Receives a {@link Bitmap}. */ public final class BitmapCallback { private final int notificationTag; @@ -417,10 +663,8 @@ public class PlayerNotificationManager { @Priority private int priority; private boolean useChronometer; - /** - * @deprecated Use {@link #createWithNotificationChannel(Context, String, int, int, int, - * MediaDescriptionAdapter)}. - */ + /** @deprecated Use the {@link Builder} instead. */ + @SuppressWarnings("deprecation") @Deprecated public static PlayerNotificationManager createWithNotificationChannel( Context context, @@ -437,27 +681,8 @@ public class PlayerNotificationManager { mediaDescriptionAdapter); } - /** - * Creates a notification manager and a low-priority notification channel with the specified - * {@code channelId} and {@code channelName}. - * - *
If the player notification manager is intended to be used within a foreground service, - * {@link #createWithNotificationChannel(Context, String, int, int, MediaDescriptionAdapter, - * NotificationListener)} should be used to which a {@link NotificationListener} can be passed. - * This way you'll receive the notification to put the service into the foreground by calling - * {@link android.app.Service#startForeground(int, Notification)}. - * - * @param context The {@link Context}. - * @param channelId The id of the notification channel. - * @param channelName A string resource identifier for the user visible name of the notification - * channel. The recommended maximum length is 40 characters. The string may be truncated if - * it's too long. - * @param channelDescription A string resource identifier for the user visible description of the - * notification channel, or 0 if no description is provided. The recommended maximum length is - * 300 characters. The value may be truncated if it is too long. - * @param notificationId The id of the notification. - * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}. - */ + /** @deprecated Use the {@link Builder} instead. */ + @Deprecated public static PlayerNotificationManager createWithNotificationChannel( Context context, String channelId, @@ -471,10 +696,7 @@ public class PlayerNotificationManager { context, channelId, notificationId, mediaDescriptionAdapter); } - /** - * @deprecated Use {@link #createWithNotificationChannel(Context, String, int, int, int, - * MediaDescriptionAdapter, NotificationListener)}. - */ + /** @deprecated Use the {@link Builder} instead. */ @Deprecated public static PlayerNotificationManager createWithNotificationChannel( Context context, @@ -493,21 +715,8 @@ public class PlayerNotificationManager { notificationListener); } - /** - * Creates a notification manager and a low-priority notification channel with the specified - * {@code channelId} and {@code channelName}. The {@link NotificationListener} passed as the last - * parameter will be notified when the notification is created and cancelled. - * - * @param context The {@link Context}. - * @param channelId The id of the notification channel. - * @param channelName A string resource identifier for the user visible name of the channel. The - * recommended maximum length is 40 characters. The string may be truncated if it's too long. - * @param channelDescription A string resource identifier for the user visible description of the - * channel, or 0 if no description is provided. - * @param notificationId The id of the notification. - * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}. - * @param notificationListener The {@link NotificationListener}. - */ + /** @deprecated Use the {@link Builder} instead. */ + @Deprecated public static PlayerNotificationManager createWithNotificationChannel( Context context, String channelId, @@ -522,19 +731,8 @@ public class PlayerNotificationManager { context, channelId, notificationId, mediaDescriptionAdapter, notificationListener); } - /** - * Creates a notification manager using the specified notification {@code channelId}. The caller - * is responsible for creating the notification channel. - * - *
When used within a service, consider using {@link #PlayerNotificationManager(Context, - * String, int, MediaDescriptionAdapter, NotificationListener)} to which a {@link - * NotificationListener} can be passed. - * - * @param context The {@link Context}. - * @param channelId The id of the notification channel. - * @param notificationId The id of the notification. - * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}. - */ + /** @deprecated Use the {@link Builder} instead. */ + @Deprecated public PlayerNotificationManager( Context context, String channelId, @@ -549,16 +747,8 @@ public class PlayerNotificationManager { /* customActionReceiver */ null); } - /** - * Creates a notification manager using the specified notification {@code channelId} and {@link - * NotificationListener}. The caller is responsible for creating the notification channel. - * - * @param context The {@link Context}. - * @param channelId The id of the notification channel. - * @param notificationId The id of the notification. - * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}. - * @param notificationListener The {@link NotificationListener}. - */ + /** @deprecated Use the {@link Builder} instead. */ + @Deprecated public PlayerNotificationManager( Context context, String channelId, @@ -574,20 +764,9 @@ public class PlayerNotificationManager { /* customActionReceiver= */ null); } - /** - * Creates a notification manager using the specified notification {@code channelId} and {@link - * CustomActionReceiver}. The caller is responsible for creating the notification channel. - * - *
When used within a service, consider using {@link #PlayerNotificationManager(Context,
- * String, int, MediaDescriptionAdapter, NotificationListener, CustomActionReceiver)} to which a
- * {@link NotificationListener} can be passed.
- *
- * @param context The {@link Context}.
- * @param channelId The id of the notification channel.
- * @param notificationId The id of the notification.
- * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
- * @param customActionReceiver The {@link CustomActionReceiver}.
- */
+ /** @deprecated Use the {@link Builder} instead. */
+ @SuppressWarnings("deprecation")
+ @Deprecated
public PlayerNotificationManager(
Context context,
String channelId,
@@ -603,18 +782,8 @@ public class PlayerNotificationManager {
customActionReceiver);
}
- /**
- * Creates a notification manager using the specified notification {@code channelId}, {@link
- * NotificationListener} and {@link CustomActionReceiver}. The caller is responsible for creating
- * the notification channel.
- *
- * @param context The {@link Context}.
- * @param channelId The id of the notification channel.
- * @param notificationId The id of the notification.
- * @param mediaDescriptionAdapter The {@link MediaDescriptionAdapter}.
- * @param notificationListener The {@link NotificationListener}.
- * @param customActionReceiver The {@link CustomActionReceiver}.
- */
+ /** @deprecated Use the {@link Builder} instead. */
+ @Deprecated
public PlayerNotificationManager(
Context context,
String channelId,
@@ -622,6 +791,38 @@ public class PlayerNotificationManager {
MediaDescriptionAdapter mediaDescriptionAdapter,
@Nullable NotificationListener notificationListener,
@Nullable CustomActionReceiver customActionReceiver) {
+ this(
+ context,
+ channelId,
+ notificationId,
+ mediaDescriptionAdapter,
+ notificationListener,
+ customActionReceiver,
+ R.drawable.exo_notification_small_icon,
+ R.drawable.exo_notification_play,
+ R.drawable.exo_notification_pause,
+ R.drawable.exo_notification_stop,
+ R.drawable.exo_notification_rewind,
+ R.drawable.exo_notification_fastforward,
+ R.drawable.exo_notification_previous,
+ R.drawable.exo_notification_next);
+ }
+
+ private PlayerNotificationManager(
+ Context context,
+ String channelId,
+ int notificationId,
+ MediaDescriptionAdapter mediaDescriptionAdapter,
+ @Nullable NotificationListener notificationListener,
+ @Nullable CustomActionReceiver customActionReceiver,
+ int smallIconResourceId,
+ int playActionIconResourceId,
+ int pauseActionIconResourceId,
+ int stopActionIconResourceId,
+ int rewindActionIconResourceId,
+ int fastForwardActionIconResourceId,
+ int previousActionIconResourceId,
+ int nextActionIconResourceId) {
context = context.getApplicationContext();
this.context = context;
this.channelId = channelId;
@@ -629,6 +830,7 @@ public class PlayerNotificationManager {
this.mediaDescriptionAdapter = mediaDescriptionAdapter;
this.notificationListener = notificationListener;
this.customActionReceiver = customActionReceiver;
+ this.smallIconResourceId = smallIconResourceId;
controlDispatcher = new DefaultControlDispatcher();
window = new Timeline.Window();
instanceId = instanceIdCounter++;
@@ -648,14 +850,23 @@ public class PlayerNotificationManager {
colorized = true;
useChronometer = true;
color = Color.TRANSPARENT;
- smallIconResourceId = R.drawable.exo_notification_small_icon;
defaults = 0;
priority = NotificationCompat.PRIORITY_LOW;
badgeIconType = NotificationCompat.BADGE_ICON_SMALL;
visibility = NotificationCompat.VISIBILITY_PUBLIC;
// initialize actions
- playbackActions = createPlaybackActions(context, instanceId);
+ playbackActions =
+ createPlaybackActions(
+ context,
+ instanceId,
+ playActionIconResourceId,
+ pauseActionIconResourceId,
+ stopActionIconResourceId,
+ rewindActionIconResourceId,
+ fastForwardActionIconResourceId,
+ previousActionIconResourceId,
+ nextActionIconResourceId);
for (String action : playbackActions.keySet()) {
intentFilter.addAction(action);
}
@@ -685,9 +896,8 @@ public class PlayerNotificationManager {
* player.getApplicationLooper() == Looper.getMainLooper()}).
*/
public final void setPlayer(@Nullable Player player) {
- Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
- Assertions.checkArgument(
- player == null || player.getApplicationLooper() == Looper.getMainLooper());
+ checkState(Looper.myLooper() == Looper.getMainLooper());
+ checkArgument(player == null || player.getApplicationLooper() == Looper.getMainLooper());
if (this.player == player) {
return;
}
@@ -1341,48 +1551,56 @@ public class PlayerNotificationManager {
}
private static Map