Add error and buffering views to PlayerView
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=195203362
This commit is contained in:
parent
7799e8fd5e
commit
b23eabd939
@ -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:
|
||||||
|
@ -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,17 +236,17 @@ 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) {
|
||||||
|
// Empty results are triggered if a permission is requested while another request was already
|
||||||
|
// pending and can be safely ignored in this case.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
} else {
|
} else {
|
||||||
showToast(R.string.storage_permission_denied);
|
showToast(R.string.storage_permission_denied);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Empty results are triggered if a permission is requested while another request was already
|
|
||||||
// pending and can be safely ignored in this case.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
@ -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"/>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user