Add artwork display mode to PlayerView

This change deprecates `PlayerView.setUseArtwork(boolean)` and
introduces `setArtworkDisplayMode(mode)` and
`artworkDisplayMode="off|fit|fill"` instead.

- off: no artwork is displayed (like deprecated useArtwork=false)
- fit: letterbox like media (like deprecated useArtwork=true)
- fill: scales the artwork to fill the entire width/weight of the player view

#minor-release

PiperOrigin-RevId: 534167226
This commit is contained in:
bachinger 2023-05-22 21:36:35 +01:00 committed by tonihei
parent 96a4ae7e40
commit 46fb454b3f
4 changed files with 164 additions and 17 deletions

View File

@ -39,6 +39,8 @@
example, a Bluetooth headset
([#167](https://github.com/androidx/media/issues/167)).
* UI:
* Deprecate `PlayerView.setUseArtwork(boolean)` and replace it with
`PlayerView.setArtworkDisplayMode(@ArtworkDisplayMode)`.
* Downloads:
* OkHttp Extension:
* Cronet Extension:

View File

@ -501,6 +501,94 @@
"totalTrackCount": 2,
"duration": 160,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "mixed_media_01",
"title": "Tear of steal - DASH",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"image": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
},
{
"id": "mixed_media_02",
"title": "Intro - The Way Of Waking Up (feat. Alan Watts - MP3)",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/01_-_Intro_-_The_Way_Of_Waking_Up_feat_Alan_Watts.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
},
{
"id": "mixed_media_03",
"title": "TTML Netflix Japanese examples (MP4)",
"source": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png",
"subtitles": [
{
"subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_japanese_ttml.xml",
"subtitle_mime_type": "application/ttml+xml",
"subtitle_lang": "ja"
}
]
},
{
"id": "mixed_media_04",
"title": "The Coldest Shoulder",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/automotive-media/The_Coldest_Shoulder.mp3",
"image": "https://storage.googleapis.com/automotive-media/album_art_3.jpg"
},
{
"id": "mixed_media_05",
"title": "Dizzy - MPEG-4 Timed Text",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4",
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png"
},
{
"id": "mixed_media_06",
"title": "Apple 4x3 basic stream (TS)",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
},
{
"id": "mixed_media_07",
"title": "The Calm Before The Storm",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/05_-_The_Calm_Before_The_Storm.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
},
{
"id": "mixed_media_08",
"title": "Android screens (MKV)",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
},
{
"id": "mixed_media_09",
"title": "No Pain, No Gain",
"album": "Mixed media",
"artist": "Mixed artists",
"genre": "Mixed",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/06_-_No_Pain_No_Gain.mp3",
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
}
]
}

View File

@ -93,10 +93,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* The following attributes can be set on a PlayerView when used in a layout XML file:
*
* <ul>
* <li><b>{@code use_artwork}</b> - Whether artwork is used if available in audio streams.
* <li><b>{@code artwork_display_mode}</b> - Whether artwork is used if available in audio streams
* and {@link ArtworkDisplayMode how it is displayed}.
* <ul>
* <li>Corresponding method: {@link #setUseArtwork(boolean)}
* <li>Default: {@code true}
* <li>Corresponding method: {@link #setArtworkDisplayMode(int)}
* <li>Default: {@link #ARTWORK_DISPLAY_MODE_FIT}
* </ul>
* <li><b>{@code default_artwork}</b> - Default artwork to use if no artwork available in audio
* streams.
@ -201,6 +202,27 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
void onFullscreenButtonClick(boolean isFullScreen);
}
/**
* Determines the artwork display mode. One of {@link #ARTWORK_DISPLAY_MODE_OFF}, {@link
* #ARTWORK_DISPLAY_MODE_FIT} or {@link #ARTWORK_DISPLAY_MODE_FILL}.
*/
@UnstableApi
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({ARTWORK_DISPLAY_MODE_OFF, ARTWORK_DISPLAY_MODE_FIT, ARTWORK_DISPLAY_MODE_FILL})
public @interface ArtworkDisplayMode {}
/** No artwork is shown. */
@UnstableApi public static final int ARTWORK_DISPLAY_MODE_OFF = 0;
/** The artwork is fit into the player view and centered creating a letterbox style. */
@UnstableApi public static final int ARTWORK_DISPLAY_MODE_FIT = 1;
/**
* The artwork covers the entire space of the player view. If the aspect ratio of the image is
* different than the player view some areas of the image are cropped.
*/
@UnstableApi public static final int ARTWORK_DISPLAY_MODE_FILL = 2;
/**
* Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link
* #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}.
@ -255,7 +277,8 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
@Nullable private FullscreenButtonClickListener fullscreenButtonClickListener;
private boolean useArtwork;
private @ArtworkDisplayMode int artworkDisplayMode;
@Nullable private Drawable defaultArtwork;
private @ShowBuffering int showBuffering;
private boolean keepContentOnPlayerReset;
@ -308,6 +331,7 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
int shutterColor = 0;
int playerLayoutId = R.layout.exo_player_view;
boolean useArtwork = true;
int artworkDisplayMode = ARTWORK_DISPLAY_MODE_FIT;
int defaultArtworkId = 0;
boolean useController = true;
int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
@ -328,6 +352,8 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
shutterColor = a.getColor(R.styleable.PlayerView_shutter_background_color, shutterColor);
playerLayoutId = a.getResourceId(R.styleable.PlayerView_player_layout_id, playerLayoutId);
useArtwork = a.getBoolean(R.styleable.PlayerView_use_artwork, useArtwork);
artworkDisplayMode =
a.getInt(R.styleable.PlayerView_artwork_display_mode, artworkDisplayMode);
defaultArtworkId =
a.getResourceId(R.styleable.PlayerView_default_artwork, defaultArtworkId);
useController = a.getBoolean(R.styleable.PlayerView_use_controller, useController);
@ -419,7 +445,9 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
// Artwork view.
artworkView = findViewById(R.id.exo_artwork);
this.useArtwork = useArtwork && artworkView != null;
boolean isArtworkEnabled =
useArtwork && artworkDisplayMode != ARTWORK_DISPLAY_MODE_OFF && artworkView != null;
this.artworkDisplayMode = isArtworkEnabled ? artworkDisplayMode : ARTWORK_DISPLAY_MODE_OFF;
if (defaultArtworkId != 0) {
defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId);
}
@ -556,7 +584,10 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
} else if (surfaceView instanceof SurfaceView) {
player.setVideoSurfaceView((SurfaceView) surfaceView);
}
updateAspectRatio();
if (player.getCurrentTracks().isTypeSupported(C.TRACK_TYPE_VIDEO)) {
// If the player already is or was playing a video, onVideoSizeChanged isn't called.
updateAspectRatio();
}
}
if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) {
subtitleView.setCues(player.getCurrentCues().cues);
@ -595,26 +626,40 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
return contentFrame.getResizeMode();
}
/** Returns whether artwork is displayed if present in the media. */
/**
* @deprecated Use {@link #getArtworkDisplayMode()} instead.
*/
@UnstableApi
@Deprecated
public boolean getUseArtwork() {
return useArtwork;
return this.artworkDisplayMode != ARTWORK_DISPLAY_MODE_OFF;
}
/**
* Sets whether artwork is displayed if present in the media.
*
* @param useArtwork Whether artwork is displayed.
* @deprecated Use {@link #setArtworkDisplayMode(int)} instead.
*/
@UnstableApi
@Deprecated
public void setUseArtwork(boolean useArtwork) {
Assertions.checkState(!useArtwork || artworkView != null);
if (this.useArtwork != useArtwork) {
this.useArtwork = useArtwork;
setArtworkDisplayMode(useArtwork ? ARTWORK_DISPLAY_MODE_OFF : ARTWORK_DISPLAY_MODE_FIT);
}
/** Sets whether and how artwork is displayed if present in the media. */
@UnstableApi
public void setArtworkDisplayMode(@ArtworkDisplayMode int artworkDisplayMode) {
Assertions.checkState(artworkDisplayMode == ARTWORK_DISPLAY_MODE_OFF || artworkView != null);
if (this.artworkDisplayMode != artworkDisplayMode) {
this.artworkDisplayMode = artworkDisplayMode;
updateForCurrentTrackSelections(/* isNewPlayer= */ false);
}
}
/** Returns the {@link ArtworkDisplayMode artwork display mode}. */
@UnstableApi
public @ArtworkDisplayMode int getArtworkDisplayMode() {
return artworkDisplayMode;
}
/** Returns the default artwork to display. */
@UnstableApi
@Nullable
@ -1243,7 +1288,7 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
@EnsuresNonNullIf(expression = "artworkView", result = true)
private boolean useArtwork() {
if (useArtwork) {
if (artworkDisplayMode != ARTWORK_DISPLAY_MODE_OFF) {
Assertions.checkStateNotNull(artworkView);
return true;
}
@ -1364,8 +1409,14 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
if (drawableWidth > 0 && drawableHeight > 0) {
float artworkAspectRatio = (float) drawableWidth / drawableHeight;
onContentAspectRatioChanged(contentFrame, artworkAspectRatio);
float artworkLayoutAspectRatio = (float) drawableWidth / drawableHeight;
ImageView.ScaleType scaleStyle = ImageView.ScaleType.FIT_XY;
if (artworkDisplayMode == ARTWORK_DISPLAY_MODE_FILL) {
artworkLayoutAspectRatio = (float) getWidth() / getHeight();
scaleStyle = ImageView.ScaleType.CENTER_CROP;
}
onContentAspectRatioChanged(contentFrame, artworkLayoutAspectRatio);
artworkView.setScaleType(scaleStyle);
artworkView.setImageDrawable(drawable);
artworkView.setVisibility(VISIBLE);
return true;

View File

@ -42,6 +42,11 @@
<!-- LegacyPlayerView and PlayerView attributes -->
<attr name="use_artwork" format="boolean"/>
<attr name="artwork_display_mode" format="enum">
<enum name="off" value="0"/>
<enum name="fit" value="1"/>
<enum name="fill" value="2"/>
</attr>
<attr name="shutter_background_color" format="color"/>
<attr name="default_artwork" format="reference"/>
<attr name="use_controller" format="boolean"/>
@ -93,6 +98,7 @@
<declare-styleable name="PlayerView">
<attr name="use_artwork"/>
<attr name="artwork_display_mode"/>
<attr name="shutter_background_color"/>
<attr name="default_artwork"/>
<attr name="use_controller"/>