Add error and buffering views to PlayerView

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=195203362
This commit is contained in:
olly 2018-05-02 22:32:16 -07:00 committed by Oliver Woodman
parent 7799e8fd5e
commit b23eabd939
10 changed files with 196 additions and 62 deletions

View File

@ -12,9 +12,12 @@
`SimpleExoPlayer` to receive detailed meta data for each ExoPlayer event. `SimpleExoPlayer` to receive detailed meta data for each ExoPlayer event.
* Added `getPlaybackError` to `Player` interface. * Added `getPlaybackError` to `Player` interface.
* UI components: * UI components:
* Add support for displaying error messages and a buffering spinner in
`PlayerView`.
* Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update * Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update
([#3736](https://github.com/google/ExoPlayer/issues/3736)). ([#3736](https://github.com/google/ExoPlayer/issues/3736)).
* Add PlayerNotificationManager. * Add `PlayerNotificationManager` for displaying notifications reflecting the
player state.
* Downloading: Add `DownloadService`, `DownloadManager` and * Downloading: Add `DownloadService`, `DownloadManager` and
related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)). related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)).
* MediaSources: * MediaSources:

View File

@ -81,6 +81,7 @@ import com.google.android.exoplayer2.ui.TrackSelectionView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.ParcelableArray; import com.google.android.exoplayer2.util.ParcelableArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -143,7 +144,6 @@ public class PlayerActivity extends Activity
private DefaultTrackSelector trackSelector; private DefaultTrackSelector trackSelector;
private DefaultTrackSelector.Parameters trackSelectorParameters; private DefaultTrackSelector.Parameters trackSelectorParameters;
private DebugTextViewHelper debugViewHelper; private DebugTextViewHelper debugViewHelper;
private boolean inErrorState;
private TrackGroupArray lastSeenTrackGroupArray; private TrackGroupArray lastSeenTrackGroupArray;
private boolean startAutoPlay; private boolean startAutoPlay;
@ -174,6 +174,7 @@ public class PlayerActivity extends Activity
playerView = findViewById(R.id.player_view); playerView = findViewById(R.id.player_view);
playerView.setControllerVisibilityListener(this); playerView.setControllerVisibilityListener(this);
playerView.setErrorMessageProvider(new PlayerErrorMessageProvider());
playerView.requestFocus(); playerView.requestFocus();
if (savedInstanceState != null) { if (savedInstanceState != null) {
@ -235,16 +236,16 @@ public class PlayerActivity extends Activity
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
if (grantResults.length > 0) { if (grantResults.length == 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initializePlayer();
} else {
showToast(R.string.storage_permission_denied);
finish();
}
} else {
// Empty results are triggered if a permission is requested while another request was already // Empty results are triggered if a permission is requested while another request was already
// pending and can be safely ignored in this case. // pending and can be safely ignored in this case.
return;
}
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initializePlayer();
} else {
showToast(R.string.storage_permission_denied);
finish();
} }
} }
@ -440,7 +441,6 @@ public class PlayerActivity extends Activity
player.seekTo(startWindow, startPosition); player.seekTo(startWindow, startPosition);
} }
player.prepare(mediaSource, !haveStartPosition, false); player.prepare(mediaSource, !haveStartPosition, false);
inErrorState = false;
updateButtonVisibilities(); updateButtonVisibilities();
} }
@ -486,8 +486,10 @@ public class PlayerActivity extends Activity
private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18( private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession) UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
throws UnsupportedDrmException { throws UnsupportedDrmException {
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, HttpDataSource.Factory licenseDataSourceFactory =
buildHttpDataSourceFactory(false)); ((DemoApplication) getApplication()).buildHttpDataSourceFactory(/* listener= */ null);
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory);
if (keyRequestPropertiesArray != null) { if (keyRequestPropertiesArray != null) {
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) { for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
@ -543,18 +545,6 @@ public class PlayerActivity extends Activity
.buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null); .buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
} }
/**
* Returns a new HttpDataSource factory.
*
* @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new
* DataSource factory.
* @return A new HttpDataSource factory.
*/
private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) {
return ((DemoApplication) getApplication())
.buildHttpDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
}
/** Returns an ads media source, reusing the ads loader if one exists. */ /** Returns an ads media source, reusing the ads loader if one exists. */
private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { private @Nullable MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) {
// Load the extension source using reflection so the demo app doesn't have to depend on it. // Load the extension source using reflection so the demo app doesn't have to depend on it.
@ -623,7 +613,7 @@ public class PlayerActivity extends Activity
return; return;
} }
for (int i = 0; i < mappedTrackInfo.length; i++) { for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i); TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i);
if (trackGroups.length != 0) { if (trackGroups.length != 0) {
Button button = new Button(this); Button button = new Button(this);
@ -687,43 +677,15 @@ public class PlayerActivity extends Activity
@Override @Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
if (inErrorState) { if (player.getPlaybackError() != null) {
// This will only occur if the user has performed a seek whilst in the error state. Update // The user has performed a seek whilst in the error state. Update the resume position so
// the resume position so that if the user then retries, playback will resume from the // that if the user then retries, playback resumes from the position to which they seeked.
// position to which they seeked.
updateStartPosition(); updateStartPosition();
} }
} }
@Override @Override
public void onPlayerError(ExoPlaybackException e) { public void onPlayerError(ExoPlaybackException e) {
String errorString = null;
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException();
if (cause instanceof DecoderInitializationException) {
// Special case for decoder initialization failures.
DecoderInitializationException decoderInitializationException =
(DecoderInitializationException) cause;
if (decoderInitializationException.decoderName == null) {
if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
errorString = getString(R.string.error_querying_decoders);
} else if (decoderInitializationException.secureDecoderRequired) {
errorString = getString(R.string.error_no_secure_decoder,
decoderInitializationException.mimeType);
} else {
errorString = getString(R.string.error_no_decoder,
decoderInitializationException.mimeType);
}
} else {
errorString = getString(R.string.error_instantiating_decoder,
decoderInitializationException.decoderName);
}
}
}
if (errorString != null) {
showToast(errorString);
}
inErrorState = true;
if (isBehindLiveWindow(e)) { if (isBehindLiveWindow(e)) {
clearStartPosition(); clearStartPosition();
initializePlayer(); initializePlayer();
@ -753,7 +715,40 @@ public class PlayerActivity extends Activity
lastSeenTrackGroupArray = trackGroups; lastSeenTrackGroupArray = trackGroups;
} }
} }
}
private class PlayerErrorMessageProvider implements ErrorMessageProvider<ExoPlaybackException> {
@Override
public Pair<Integer, String> getErrorMessage(ExoPlaybackException e) {
String errorString = getString(R.string.error_generic);
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException();
if (cause instanceof DecoderInitializationException) {
// Special case for decoder initialization failures.
DecoderInitializationException decoderInitializationException =
(DecoderInitializationException) cause;
if (decoderInitializationException.decoderName == null) {
if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
errorString = getString(R.string.error_querying_decoders);
} else if (decoderInitializationException.secureDecoderRequired) {
errorString =
getString(
R.string.error_no_secure_decoder, decoderInitializationException.mimeType);
} else {
errorString =
getString(R.string.error_no_decoder, decoderInitializationException.mimeType);
}
} else {
errorString =
getString(
R.string.error_instantiating_decoder,
decoderInitializationException.decoderName);
}
}
}
return Pair.create(0, errorString);
}
} }
} }

View File

@ -19,6 +19,8 @@
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string> <string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
<string name="error_generic">Playback failed</string>
<string name="error_unrecognized_abr_algorithm">Unrecognized ABR algorithm</string> <string name="error_unrecognized_abr_algorithm">Unrecognized ABR algorithm</string>
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string> <string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>

View File

@ -53,7 +53,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
private @Nullable PlaybackPreparer playbackPreparer; private @Nullable PlaybackPreparer playbackPreparer;
private ControlDispatcher controlDispatcher; private ControlDispatcher controlDispatcher;
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private SurfaceHolderGlueHost surfaceHolderGlueHost; private SurfaceHolderGlueHost surfaceHolderGlueHost;
private boolean hasSurface; private boolean hasSurface;
private boolean lastNotifiedPreparedState; private boolean lastNotifiedPreparedState;
@ -110,7 +110,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
* @param errorMessageProvider The {@link ErrorMessageProvider}. * @param errorMessageProvider The {@link ErrorMessageProvider}.
*/ */
public void setErrorMessageProvider( public void setErrorMessageProvider(
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) { @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider; this.errorMessageProvider = errorMessageProvider;
} }

View File

@ -334,7 +334,7 @@ public final class MediaSessionConnector {
private Player player; private Player player;
private CustomActionProvider[] customActionProviders; private CustomActionProvider[] customActionProviders;
private Map<String, CustomActionProvider> customActionMap; private Map<String, CustomActionProvider> customActionMap;
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private PlaybackPreparer playbackPreparer; private PlaybackPreparer playbackPreparer;
private QueueNavigator queueNavigator; private QueueNavigator queueNavigator;
private QueueEditor queueEditor; private QueueEditor queueEditor;
@ -436,12 +436,12 @@ public final class MediaSessionConnector {
} }
/** /**
* Sets the {@link ErrorMessageProvider}. * Sets the optional {@link ErrorMessageProvider}.
* *
* @param errorMessageProvider The error message provider. * @param errorMessageProvider The error message provider.
*/ */
public void setErrorMessageProvider( public void setErrorMessageProvider(
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) { @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
if (this.errorMessageProvider != errorMessageProvider) { if (this.errorMessageProvider != errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider; this.errorMessageProvider = errorMessageProvider;
updateMediaSessionPlaybackState(); updateMediaSessionPlaybackState();

View File

@ -36,9 +36,11 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher; import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher; import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackPreparer; import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Player.DiscontinuityReason;
@ -51,6 +53,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.VideoListener;
@ -102,6 +105,12 @@ import java.util.List;
* <li>Corresponding method: {@link #setControllerHideDuringAds(boolean)} * <li>Corresponding method: {@link #setControllerHideDuringAds(boolean)}
* <li>Default: {@code true} * <li>Default: {@code true}
* </ul> * </ul>
* <li><b>{@code show_buffering}</b> - Whether the buffering spinner is displayed when the player
* is buffering.
* <ul>
* <li>Corresponding method: {@link #setShowBuffering(boolean)}
* <li>Default: {@code false}
* </ul>
* <li><b>{@code resize_mode}</b> - Controls how video and album art is resized within the view. * <li><b>{@code resize_mode}</b> - Controls how video and album art is resized within the view.
* Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}. * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}.
* <ul> * <ul>
@ -164,6 +173,11 @@ import java.util.List;
* <ul> * <ul>
* <li>Type: {@link View} * <li>Type: {@link View}
* </ul> * </ul>
* <li><b>{@code exo_buffering}</b> - A view that's made visible when the player is buffering.
* This view typically displays a buffering spinner or animation.
* <ul>
* <li>Type: {@link View}
* </ul>
* <li><b>{@code exo_subtitles}</b> - Displays subtitles. * <li><b>{@code exo_subtitles}</b> - Displays subtitles.
* <ul> * <ul>
* <li>Type: {@link SubtitleView} * <li>Type: {@link SubtitleView}
@ -172,6 +186,10 @@ import java.util.List;
* <ul> * <ul>
* <li>Type: {@link ImageView} * <li>Type: {@link ImageView}
* </ul> * </ul>
* <li><b>{@code exo_error_message}</b> - Displays an error message to the user if playback fails.
* <ul>
* <li>Type: {@link TextView}
* </ul>
* <li><b>{@code exo_controller_placeholder}</b> - A placeholder that's replaced with the inflated * <li><b>{@code exo_controller_placeholder}</b> - A placeholder that's replaced with the inflated
* {@link PlayerControlView}. Ignored if an {@code exo_controller} view exists. * {@link PlayerControlView}. Ignored if an {@code exo_controller} view exists.
* <ul> * <ul>
@ -213,6 +231,8 @@ public class PlayerView extends FrameLayout {
private final View surfaceView; private final View surfaceView;
private final ImageView artworkView; private final ImageView artworkView;
private final SubtitleView subtitleView; private final SubtitleView subtitleView;
private final @Nullable View bufferingView;
private final @Nullable TextView errorMessageView;
private final PlayerControlView controller; private final PlayerControlView controller;
private final ComponentListener componentListener; private final ComponentListener componentListener;
private final FrameLayout overlayFrameLayout; private final FrameLayout overlayFrameLayout;
@ -221,6 +241,9 @@ public class PlayerView extends FrameLayout {
private boolean useController; private boolean useController;
private boolean useArtwork; private boolean useArtwork;
private Bitmap defaultArtwork; private Bitmap defaultArtwork;
private boolean showBuffering;
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private @Nullable CharSequence customErrorMessage;
private int controllerShowTimeoutMs; private int controllerShowTimeoutMs;
private boolean controllerAutoShow; private boolean controllerAutoShow;
private boolean controllerHideDuringAds; private boolean controllerHideDuringAds;
@ -244,6 +267,8 @@ public class PlayerView extends FrameLayout {
surfaceView = null; surfaceView = null;
artworkView = null; artworkView = null;
subtitleView = null; subtitleView = null;
bufferingView = null;
errorMessageView = null;
controller = null; controller = null;
componentListener = null; componentListener = null;
overlayFrameLayout = null; overlayFrameLayout = null;
@ -269,6 +294,7 @@ public class PlayerView extends FrameLayout {
boolean controllerHideOnTouch = true; boolean controllerHideOnTouch = true;
boolean controllerAutoShow = true; boolean controllerAutoShow = true;
boolean controllerHideDuringAds = true; boolean controllerHideDuringAds = true;
boolean showBuffering = false;
if (attrs != null) { if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PlayerView, 0, 0); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PlayerView, 0, 0);
try { try {
@ -286,6 +312,7 @@ public class PlayerView extends FrameLayout {
controllerHideOnTouch = controllerHideOnTouch =
a.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch); a.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch);
controllerAutoShow = a.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow); controllerAutoShow = a.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow);
showBuffering = a.getBoolean(R.styleable.PlayerView_show_buffering, showBuffering);
controllerHideDuringAds = controllerHideDuringAds =
a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds); a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds);
} finally { } finally {
@ -341,6 +368,19 @@ public class PlayerView extends FrameLayout {
subtitleView.setUserDefaultTextSize(); subtitleView.setUserDefaultTextSize();
} }
// Buffering view.
bufferingView = findViewById(R.id.exo_buffering);
if (bufferingView != null) {
bufferingView.setVisibility(View.GONE);
}
this.showBuffering = showBuffering;
// Error message view.
errorMessageView = findViewById(R.id.exo_error_message);
if (errorMessageView != null) {
errorMessageView.setVisibility(View.GONE);
}
// Playback control view. // Playback control view.
PlayerControlView customController = findViewById(R.id.exo_controller); PlayerControlView customController = findViewById(R.id.exo_controller);
View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder); View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder);
@ -438,6 +478,8 @@ public class PlayerView extends FrameLayout {
if (subtitleView != null) { if (subtitleView != null) {
subtitleView.setCues(null); subtitleView.setCues(null);
} }
updateBuffering();
updateErrorMessage();
if (player != null) { if (player != null) {
Player.VideoComponent newVideoComponent = player.getVideoComponent(); Player.VideoComponent newVideoComponent = player.getVideoComponent();
if (newVideoComponent != null) { if (newVideoComponent != null) {
@ -558,6 +600,44 @@ public class PlayerView extends FrameLayout {
} }
} }
/**
* Sets whether a buffering spinner is displayed when the player is in the buffering state. The
* buffering spinner is not displayed by default.
*
* @param showBuffering Whether the buffering icon is displayer
*/
public void setShowBuffering(boolean showBuffering) {
if (this.showBuffering != showBuffering) {
this.showBuffering = showBuffering;
updateBuffering();
}
}
/**
* Sets the optional {@link ErrorMessageProvider}.
*
* @param errorMessageProvider The error message provider.
*/
public void setErrorMessageProvider(
@Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
if (this.errorMessageProvider != errorMessageProvider) {
this.errorMessageProvider = errorMessageProvider;
updateErrorMessage();
}
}
/**
* Sets a custom error message to be displayed by the view. The error message will be displayed
* permanently, unless it is cleared by passing {@code null} to this method.
*
* @param message The message to display, or {@code null} to clear a previously set message.
*/
public void setCustomErrorMessage(@Nullable CharSequence message) {
Assertions.checkState(errorMessageView != null);
customErrorMessage = message;
updateErrorMessage();
}
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
if (player != null && player.isPlayingAd()) { if (player != null && player.isPlayingAd()) {
@ -954,6 +1034,40 @@ public class PlayerView extends FrameLayout {
} }
} }
private void updateBuffering() {
if (bufferingView != null) {
boolean showBufferingSpinner =
showBuffering
&& player != null
&& player.getPlaybackState() == Player.STATE_BUFFERING
&& player.getPlayWhenReady();
bufferingView.setVisibility(showBufferingSpinner ? View.VISIBLE : View.GONE);
}
}
private void updateErrorMessage() {
if (errorMessageView != null) {
if (customErrorMessage != null) {
errorMessageView.setText(customErrorMessage);
errorMessageView.setVisibility(View.VISIBLE);
return;
}
ExoPlaybackException error = null;
if (player != null
&& player.getPlaybackState() == Player.STATE_IDLE
&& errorMessageProvider != null) {
error = player.getPlaybackError();
}
if (error != null) {
CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second;
errorMessageView.setText(errorMessage);
errorMessageView.setVisibility(View.VISIBLE);
} else {
errorMessageView.setVisibility(View.GONE);
}
}
}
@TargetApi(23) @TargetApi(23)
private static void configureEditModeLogoV23(Resources resources, ImageView logo) { private static void configureEditModeLogoV23(Resources resources, ImageView logo) {
logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null)); logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null));
@ -1070,6 +1184,8 @@ public class PlayerView extends FrameLayout {
@Override @Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
updateBuffering();
updateErrorMessage();
if (isPlayingAd() && controllerHideDuringAds) { if (isPlayingAd() && controllerHideDuringAds) {
hideController(); hideController();
} else { } else {

View File

@ -36,6 +36,20 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<ProgressBar android:id="@id/exo_buffering"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center"/>
<TextView android:id="@id/exo_error_message"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/exo_error_message_background_color"
android:padding="16dp"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout> </com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<FrameLayout android:id="@id/exo_overlay" <FrameLayout android:id="@id/exo_overlay"

View File

@ -50,6 +50,7 @@
<attr name="hide_on_touch" format="boolean"/> <attr name="hide_on_touch" format="boolean"/>
<attr name="hide_during_ads" format="boolean"/> <attr name="hide_during_ads" format="boolean"/>
<attr name="auto_show" format="boolean"/> <attr name="auto_show" format="boolean"/>
<attr name="show_buffering" format="boolean"/>
<attr name="resize_mode"/> <attr name="resize_mode"/>
<attr name="surface_type"/> <attr name="surface_type"/>
<attr name="player_layout_id"/> <attr name="player_layout_id"/>

View File

@ -18,6 +18,7 @@
<dimen name="exo_media_button_width">71dp</dimen> <dimen name="exo_media_button_width">71dp</dimen>
<dimen name="exo_media_button_height">52dp</dimen> <dimen name="exo_media_button_height">52dp</dimen>
<color name="exo_error_message_background_color">#AA000000</color>
<color name="exo_edit_mode_background_color">#FFF4F3F0</color> <color name="exo_edit_mode_background_color">#FFF4F3F0</color>
</resources> </resources>

View File

@ -33,5 +33,7 @@
<item name="exo_duration" type="id"/> <item name="exo_duration" type="id"/>
<item name="exo_position" type="id"/> <item name="exo_position" type="id"/>
<item name="exo_progress" type="id"/> <item name="exo_progress" type="id"/>
<item name="exo_buffering" type="id"/>
<item name="exo_error_message" type="id"/>
</resources> </resources>