mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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.
|
||||
* Added `getPlaybackError` to `Player` interface.
|
||||
* UI components:
|
||||
* Add support for displaying error messages and a buffering spinner in
|
||||
`PlayerView`.
|
||||
* Add support for listening to `AspectRatioFrameLayout`'s aspect ratio update
|
||||
([#3736](https://github.com/google/ExoPlayer/issues/3736)).
|
||||
* Add PlayerNotificationManager.
|
||||
* Add `PlayerNotificationManager` for displaying notifications reflecting the
|
||||
player state.
|
||||
* Downloading: Add `DownloadService`, `DownloadManager` and
|
||||
related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)).
|
||||
* 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.DefaultBandwidthMeter;
|
||||
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.ParcelableArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -143,7 +144,6 @@ public class PlayerActivity extends Activity
|
||||
private DefaultTrackSelector trackSelector;
|
||||
private DefaultTrackSelector.Parameters trackSelectorParameters;
|
||||
private DebugTextViewHelper debugViewHelper;
|
||||
private boolean inErrorState;
|
||||
private TrackGroupArray lastSeenTrackGroupArray;
|
||||
|
||||
private boolean startAutoPlay;
|
||||
@ -174,6 +174,7 @@ public class PlayerActivity extends Activity
|
||||
|
||||
playerView = findViewById(R.id.player_view);
|
||||
playerView.setControllerVisibilityListener(this);
|
||||
playerView.setErrorMessageProvider(new PlayerErrorMessageProvider());
|
||||
playerView.requestFocus();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
@ -235,16 +236,16 @@ public class PlayerActivity extends Activity
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
if (grantResults.length > 0) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
initializePlayer();
|
||||
} else {
|
||||
showToast(R.string.storage_permission_denied);
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
initializePlayer();
|
||||
} else {
|
||||
showToast(R.string.storage_permission_denied);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,7 +441,6 @@ public class PlayerActivity extends Activity
|
||||
player.seekTo(startWindow, startPosition);
|
||||
}
|
||||
player.prepare(mediaSource, !haveStartPosition, false);
|
||||
inErrorState = false;
|
||||
updateButtonVisibilities();
|
||||
}
|
||||
|
||||
@ -486,8 +486,10 @@ public class PlayerActivity extends Activity
|
||||
private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
|
||||
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
|
||||
throws UnsupportedDrmException {
|
||||
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||
buildHttpDataSourceFactory(false));
|
||||
HttpDataSource.Factory licenseDataSourceFactory =
|
||||
((DemoApplication) getApplication()).buildHttpDataSourceFactory(/* listener= */ null);
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory);
|
||||
if (keyRequestPropertiesArray != null) {
|
||||
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
|
||||
@ -543,18 +545,6 @@ public class PlayerActivity extends Activity
|
||||
.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. */
|
||||
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.
|
||||
@ -623,7 +613,7 @@ public class PlayerActivity extends Activity
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mappedTrackInfo.length; i++) {
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i);
|
||||
if (trackGroups.length != 0) {
|
||||
Button button = new Button(this);
|
||||
@ -687,43 +677,15 @@ public class PlayerActivity extends Activity
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
|
||||
if (inErrorState) {
|
||||
// This will only occur if the user has performed a seek whilst in the error state. Update
|
||||
// the resume position so that if the user then retries, playback will resume from the
|
||||
// position to which they seeked.
|
||||
if (player.getPlaybackError() != null) {
|
||||
// The user has performed a seek whilst in the error state. Update the resume position so
|
||||
// that if the user then retries, playback resumes from the position to which they seeked.
|
||||
updateStartPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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)) {
|
||||
clearStartPosition();
|
||||
initializePlayer();
|
||||
@ -753,7 +715,40 @@ public class PlayerActivity extends Activity
|
||||
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="error_generic">Playback failed</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>
|
||||
|
@ -53,7 +53,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
|
||||
|
||||
private @Nullable PlaybackPreparer playbackPreparer;
|
||||
private ControlDispatcher controlDispatcher;
|
||||
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private SurfaceHolderGlueHost surfaceHolderGlueHost;
|
||||
private boolean hasSurface;
|
||||
private boolean lastNotifiedPreparedState;
|
||||
@ -110,7 +110,7 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter {
|
||||
* @param errorMessageProvider The {@link ErrorMessageProvider}.
|
||||
*/
|
||||
public void setErrorMessageProvider(
|
||||
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
@Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
this.errorMessageProvider = errorMessageProvider;
|
||||
}
|
||||
|
||||
|
@ -334,7 +334,7 @@ public final class MediaSessionConnector {
|
||||
private Player player;
|
||||
private CustomActionProvider[] customActionProviders;
|
||||
private Map<String, CustomActionProvider> customActionMap;
|
||||
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private PlaybackPreparer playbackPreparer;
|
||||
private QueueNavigator queueNavigator;
|
||||
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.
|
||||
*/
|
||||
public void setErrorMessageProvider(
|
||||
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
@Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||
if (this.errorMessageProvider != errorMessageProvider) {
|
||||
this.errorMessageProvider = errorMessageProvider;
|
||||
updateMediaSessionPlaybackState();
|
||||
|
@ -36,9 +36,11 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ControlDispatcher;
|
||||
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackPreparer;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
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.ui.AspectRatioFrameLayout.ResizeMode;
|
||||
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.Util;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
@ -102,6 +105,12 @@ import java.util.List;
|
||||
* <li>Corresponding method: {@link #setControllerHideDuringAds(boolean)}
|
||||
* <li>Default: {@code true}
|
||||
* </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.
|
||||
* Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}.
|
||||
* <ul>
|
||||
@ -164,6 +173,11 @@ import java.util.List;
|
||||
* <ul>
|
||||
* <li>Type: {@link View}
|
||||
* </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.
|
||||
* <ul>
|
||||
* <li>Type: {@link SubtitleView}
|
||||
@ -172,6 +186,10 @@ import java.util.List;
|
||||
* <ul>
|
||||
* <li>Type: {@link ImageView}
|
||||
* </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
|
||||
* {@link PlayerControlView}. Ignored if an {@code exo_controller} view exists.
|
||||
* <ul>
|
||||
@ -213,6 +231,8 @@ public class PlayerView extends FrameLayout {
|
||||
private final View surfaceView;
|
||||
private final ImageView artworkView;
|
||||
private final SubtitleView subtitleView;
|
||||
private final @Nullable View bufferingView;
|
||||
private final @Nullable TextView errorMessageView;
|
||||
private final PlayerControlView controller;
|
||||
private final ComponentListener componentListener;
|
||||
private final FrameLayout overlayFrameLayout;
|
||||
@ -221,6 +241,9 @@ public class PlayerView extends FrameLayout {
|
||||
private boolean useController;
|
||||
private boolean useArtwork;
|
||||
private Bitmap defaultArtwork;
|
||||
private boolean showBuffering;
|
||||
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||
private @Nullable CharSequence customErrorMessage;
|
||||
private int controllerShowTimeoutMs;
|
||||
private boolean controllerAutoShow;
|
||||
private boolean controllerHideDuringAds;
|
||||
@ -244,6 +267,8 @@ public class PlayerView extends FrameLayout {
|
||||
surfaceView = null;
|
||||
artworkView = null;
|
||||
subtitleView = null;
|
||||
bufferingView = null;
|
||||
errorMessageView = null;
|
||||
controller = null;
|
||||
componentListener = null;
|
||||
overlayFrameLayout = null;
|
||||
@ -269,6 +294,7 @@ public class PlayerView extends FrameLayout {
|
||||
boolean controllerHideOnTouch = true;
|
||||
boolean controllerAutoShow = true;
|
||||
boolean controllerHideDuringAds = true;
|
||||
boolean showBuffering = false;
|
||||
if (attrs != null) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PlayerView, 0, 0);
|
||||
try {
|
||||
@ -286,6 +312,7 @@ public class PlayerView extends FrameLayout {
|
||||
controllerHideOnTouch =
|
||||
a.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch);
|
||||
controllerAutoShow = a.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow);
|
||||
showBuffering = a.getBoolean(R.styleable.PlayerView_show_buffering, showBuffering);
|
||||
controllerHideDuringAds =
|
||||
a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds);
|
||||
} finally {
|
||||
@ -341,6 +368,19 @@ public class PlayerView extends FrameLayout {
|
||||
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.
|
||||
PlayerControlView customController = findViewById(R.id.exo_controller);
|
||||
View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder);
|
||||
@ -438,6 +478,8 @@ public class PlayerView extends FrameLayout {
|
||||
if (subtitleView != null) {
|
||||
subtitleView.setCues(null);
|
||||
}
|
||||
updateBuffering();
|
||||
updateErrorMessage();
|
||||
if (player != null) {
|
||||
Player.VideoComponent newVideoComponent = player.getVideoComponent();
|
||||
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
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
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)
|
||||
private static void configureEditModeLogoV23(Resources resources, ImageView logo) {
|
||||
logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null));
|
||||
@ -1070,6 +1184,8 @@ public class PlayerView extends FrameLayout {
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
updateBuffering();
|
||||
updateErrorMessage();
|
||||
if (isPlayingAd() && controllerHideDuringAds) {
|
||||
hideController();
|
||||
} else {
|
||||
|
@ -36,6 +36,20 @@
|
||||
android:layout_width="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>
|
||||
|
||||
<FrameLayout android:id="@id/exo_overlay"
|
||||
|
@ -50,6 +50,7 @@
|
||||
<attr name="hide_on_touch" format="boolean"/>
|
||||
<attr name="hide_during_ads" format="boolean"/>
|
||||
<attr name="auto_show" format="boolean"/>
|
||||
<attr name="show_buffering" format="boolean"/>
|
||||
<attr name="resize_mode"/>
|
||||
<attr name="surface_type"/>
|
||||
<attr name="player_layout_id"/>
|
||||
|
@ -18,6 +18,7 @@
|
||||
<dimen name="exo_media_button_width">71dp</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>
|
||||
|
||||
</resources>
|
||||
|
@ -33,5 +33,7 @@
|
||||
<item name="exo_duration" type="id"/>
|
||||
<item name="exo_position" type="id"/>
|
||||
<item name="exo_progress" type="id"/>
|
||||
<item name="exo_buffering" type="id"/>
|
||||
<item name="exo_error_message" type="id"/>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user