qualities) {
this.qualities = qualities;
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/SimpleQuality.java
================================================
package com.jarvanmo.exoplayerview.media;
import android.net.Uri;
/**
* Created by mo on 18-2-7.
*
* @author mo
*/
public class SimpleQuality implements ExoMediaSource.Quality {
private CharSequence name;
private Uri uri;
private String quality;
public SimpleQuality(CharSequence name, Uri uri) {
this.name = name;
this.uri = uri;
}
@Override
public CharSequence getDisplayName() {
return name;
}
@Override
public Uri getUri() {
return uri;
}
@Override
public void setUri(Uri uri) {
this.uri = uri;
}
@Override
public void setDisplayName(CharSequence name) {
this.name = name;
}
@Override
public void setQuality(String quality) {
this.quality = quality;
}
@Override
public String getQuality() {
return quality;
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/orientation/OnOrientationChangedListener.java
================================================
package com.jarvanmo.exoplayerview.orientation;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by mo on 18-2-5.
* 剑气纵横三万里 一剑光寒十九洲
*/
public interface OnOrientationChangedListener {
int SENSOR_UNKNOWN = -1;
int SENSOR_PORTRAIT = SENSOR_UNKNOWN + 1;
int SENSOR_LANDSCAPE = SENSOR_PORTRAIT + 1;
@IntDef({SENSOR_UNKNOWN, SENSOR_PORTRAIT, SENSOR_LANDSCAPE})
@Retention(RetentionPolicy.SOURCE)
@interface SensorOrientationType {
}
void onChanged(@SensorOrientationType int orientation);
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/orientation/SensorOrientation.java
================================================
package com.jarvanmo.exoplayerview.orientation;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;
import android.view.OrientationEventListener;
import static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_LANDSCAPE;
import static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_PORTRAIT;
import static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_UNKNOWN;
/**
* Created by mo on 18-2-5.
* 剑气纵横三万里 一剑光寒十九洲
*/
public class SensorOrientation {
private int oldScreenOrientation = SENSOR_UNKNOWN;
private final Context context;
private final OrientationEventListener screenOrientationEventListener;
public SensorOrientation(Context context, OnOrientationChangedListener onOrientationChangedListener) {
this.context = context;
screenOrientationEventListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int orientation) {
if (onOrientationChangedListener == null || !isScreenOpenRotate()) {
return;
}
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
onOrientationChangedListener.onChanged(SENSOR_UNKNOWN);
return; //手机平放时,检测不到有效的角度
}
//只检测是否有四个角度的改变
if (orientation > 350 || orientation < 10) { //0度
orientation = 0;
} else if (orientation > 80 && orientation < 100) { //90度
orientation = 90;
} else if (orientation > 170 && orientation < 190) { //180度
orientation = 180;
} else if (orientation > 260 && orientation < 280) { //270度
orientation = 270;
} else {
return;
}
if (oldScreenOrientation == orientation) {
onOrientationChangedListener.onChanged(SENSOR_UNKNOWN);
return;
}
oldScreenOrientation = orientation;
if (orientation == 0 || orientation == 180) {
onOrientationChangedListener.onChanged(SENSOR_PORTRAIT);
} else {
onOrientationChangedListener.onChanged(SENSOR_LANDSCAPE);
}
}
};
}
private boolean isScreenOpenRotate() {
int gravity = 0;
try {
gravity = Settings.System.getInt(context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION);
} catch (Settings.SettingNotFoundException e) {
Log.e(getClass().getSimpleName(), e.getMessage() + "");
}
return 1 == gravity;
}
public void enable() {
screenOrientationEventListener.enable();
}
public void disable() {
screenOrientationEventListener.disable();
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/ui/ExoVideoPlaybackControlView.java
================================================
package com.jarvanmo.exoplayerview.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.hls.HlsManifest;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.ui.TimeBar;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util;
import com.jarvanmo.exoplayerview.R;
import com.jarvanmo.exoplayerview.extension.MultiQualitySelectorAdapter;
import com.jarvanmo.exoplayerview.gesture.OnVideoGestureChangeListener;
import com.jarvanmo.exoplayerview.gesture.VideoGesture;
import com.jarvanmo.exoplayerview.media.ExoMediaSource;
import com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener;
import com.jarvanmo.exoplayerview.orientation.SensorOrientation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Formatter;
import java.util.Locale;
import static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_LANDSCAPE;
import static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_PORTRAIT;
import static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_UNKNOWN;
/**
* Created by mo on 16-11-7.
*
* @author mo
*
* *
Specifying a custom layout file
* Defining your own {@code exo_video_playback_control_view.xml} is useful to customize the layout of
* ExoVideoPlaybackControlView throughout your application. It's also possible to customize the layout for a
* single instance in a layout file. This is achieved by setting the {@code controller_layout_id}
* attribute on a ExoVideoPlaybackControlView. This will cause the specified layout to be inflated instead
* of {@code exo_video_playback_control_view.xml} for only the instance on which the attribute is set.
*/
public class ExoVideoPlaybackControlView extends FrameLayout {
/**
* to get {@link ExoVideoView}
*/
public interface VideoViewAccessor {
View attachVideoView();
}
/**
* to get {@link Player}
*/
public interface PlayerAccessor {
Player attachPlayer();
}
/**
* Listener to be notified about changes of the visibility of the UI control.
*/
public interface VisibilityListener {
/**
* Called when the visibility changes.
*
* @param visibility The new visibility. Either {@link View#VISIBLE} or {@link View#GONE}.
*/
void onVisibilityChange(int visibility);
}
public interface ExoClickListener {
/***
* called when buttons clicked in controller
* @param view null when back pressed.
* @param isPortrait the controller is portrait or not
* @return will interrupt operation in controller if return true
* **/
boolean onClick(@Nullable View view, boolean isPortrait);
}
public interface OrientationListener {
void onOrientationChanged(@OnOrientationChangedListener.SensorOrientationType int orientation);
}
/**
* The default fast forward increment, in milliseconds.
*/
public static final int DEFAULT_FAST_FORWARD_MS = 15000;
/**
* The default rewind increment, in milliseconds.
*/
public static final int DEFAULT_REWIND_MS = 5000;
/**
* The default show timeout, in milliseconds.
*/
public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000;
/**
* The default repeat toggle modes.
*/
public static final @RepeatModeUtil.RepeatToggleModes
int DEFAULT_REPEAT_TOGGLE_MODES =
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE;
/**
* The maximum number of windows that can be shown in a multi-window time bar.
*/
public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100;
private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
//
//
//
//
//
public static final int CONTROLLER_MODE_NONE = 0b0000;
public static final int CONTROLLER_MODE_ALL = 0b1111;
public static final int CONTROLLER_MODE_TOP = 0b1000;
public static final int CONTROLLER_MODE_TOP_LANDSCAPE = 0b0100;
public static final int CONTROLLER_MODE_BOTTOM = 0b0010;
public static final int CONTROLLER_MODE_BOTTOM_LANDSCAPE = 0b0001;
@IntDef({CONTROLLER_MODE_NONE,
CONTROLLER_MODE_ALL,
CONTROLLER_MODE_TOP, CONTROLLER_MODE_TOP_LANDSCAPE, CONTROLLER_MODE_BOTTOM, CONTROLLER_MODE_BOTTOM_LANDSCAPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ControllerModeType {
}
public static final int CUSTOM_VIEW_TOP = 1;
public static final int CUSTOM_VIEW_TOP_LANDSCAPE = CUSTOM_VIEW_TOP + 1;
public static final int CUSTOM_VIEW_BOTTOM_LANDSCAPE = CUSTOM_VIEW_TOP_LANDSCAPE + 1;
@IntDef({CUSTOM_VIEW_TOP, CUSTOM_VIEW_TOP_LANDSCAPE, CUSTOM_VIEW_BOTTOM_LANDSCAPE})
@Retention(RetentionPolicy.SOURCE)
public @interface CustomViewType {
}
private final ComponentListener componentListener;
private final View previousButton;
private final View nextButton;
private final View playButton;
private final View pauseButton;
private final View fastForwardButton;
private final View rewindButton;
private final ImageView repeatToggleButton;
private final View shuffleButton;
private final TextView durationView;
private final TextView positionView;
private final TimeBar timeBar;
private final StringBuilder formatBuilder;
private final Formatter formatter;
private final Timeline.Period period;
private final Timeline.Window window;
private final Drawable repeatOffButtonDrawable;
private final Drawable repeatOneButtonDrawable;
private final Drawable repeatAllButtonDrawable;
private final String repeatOffButtonContentDescription;
private final String repeatOneButtonContentDescription;
private final String repeatAllButtonContentDescription;
private Player player;
private com.google.android.exoplayer2.ControlDispatcher controlDispatcher;
private VisibilityListener visibilityListener;
private boolean isAttachedToWindow;
private boolean showMultiWindowTimeBar;
private boolean multiWindowTimeBar;
private boolean scrubbing;
private int rewindMs;
private int fastForwardMs;
private int showTimeoutMs;
private @RepeatModeUtil.RepeatToggleModes
int repeatToggleModes;
private boolean showShuffleButton;
private long hideAtMs;
private long[] adGroupTimesMs;
private boolean[] playedAdGroups;
private long[] extraAdGroupTimesMs;
private boolean[] extraPlayedAdGroups;
private final Runnable updateProgressAction = this::updateProgress;
private final Runnable hideAction = this::hide;
private final TimeBar timeBarLandscape;
private final View playButtonLandScape;
private final View pauseButtonLandScape;
private final TextView durationViewLandscape;
private final View enterFullscreen;
private final View exitFullscreen;
private final View exoPlayerControllerTop;
private final View exoPlayerControllerTopLandscape;
private final View exoPlayerControllerBottom;
private final View exoPlayerControllerBottomLandscape;
private final View centerInfoWrapper;
private final TextView centerInfo;
private final TextView exoPlayerVideoName;
private final TextView exoPlayerVideoNameLandscape;
private final TextView exoPlayerCurrentQualityLandscape;
private final ViewGroup topCustomView;
private final ViewGroup topCustomViewLandscape;
private final ViewGroup bottomCustomViewLandscape;
private final TextView centerError;
private final ProgressBar loadingBar;
private final View back;
private final View backLandscape;
private boolean portrait = true;
private SensorOrientation sensorOrientation;
private OrientationListener orientationListener;
private ExoClickListener backListener;
private boolean isHls;
private int displayMode = CONTROLLER_MODE_ALL;
private MultiQualitySelectorAdapter.VisibilityCallback qualityVisibilityCallback;
private VideoViewAccessor videoViewAccessor;
private VideoGesture videoGesture;
public ExoVideoPlaybackControlView(Context context) {
this(context, null);
}
public ExoVideoPlaybackControlView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExoVideoPlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, attrs);
}
public ExoVideoPlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr,
AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_video_playback_control_view;
rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
showShuffleButton = false;
boolean enableGesture = true;
int controllerBackgroundId = 0;
if (playbackAttrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(playbackAttrs,
R.styleable.ExoVideoPlaybackControlView, 0, 0);
try {
rewindMs = a.getInt(R.styleable.ExoVideoPlaybackControlView_rewind_increment, rewindMs);
fastForwardMs = a.getInt(R.styleable.ExoVideoPlaybackControlView_fastforward_increment,
fastForwardMs);
showTimeoutMs = a.getInt(R.styleable.ExoVideoPlaybackControlView_show_timeout, showTimeoutMs);
controllerLayoutId = a.getResourceId(R.styleable.ExoVideoPlaybackControlView_controller_layout_id,
controllerLayoutId);
repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes);
showShuffleButton = a.getBoolean(R.styleable.ExoVideoPlaybackControlView_show_shuffle_button,
showShuffleButton);
displayMode = a.getInt(R.styleable.ExoVideoPlaybackControlView_controller_display_mode, CONTROLLER_MODE_ALL);
controllerBackgroundId = a.getResourceId(R.styleable.ExoVideoPlaybackControlView_controller_background, 0);
enableGesture = a.getBoolean(R.styleable.ExoVideoPlaybackControlView_enableGesture,enableGesture);
} finally {
a.recycle();
}
}
period = new Timeline.Period();
window = new Timeline.Window();
formatBuilder = new StringBuilder();
formatter = new Formatter(formatBuilder, Locale.getDefault());
adGroupTimesMs = new long[0];
playedAdGroups = new boolean[0];
extraAdGroupTimesMs = new long[0];
extraPlayedAdGroups = new boolean[0];
componentListener = new ComponentListener();
controlDispatcher = new com.google.android.exoplayer2.DefaultControlDispatcher();
LayoutInflater.from(context).inflate(controllerLayoutId, this);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
durationView = findViewById(R.id.exo_player_duration);
positionView = findViewById(R.id.exo_player_position);
timeBar = findViewById(R.id.exo_player_progress);
if (timeBar != null) {
timeBar.addListener(componentListener);
}
playButton = findViewById(R.id.exo_player_play);
if (playButton != null) {
playButton.setOnClickListener(componentListener);
}
pauseButton = findViewById(R.id.exo_player_pause);
if (pauseButton != null) {
pauseButton.setOnClickListener(componentListener);
}
previousButton = findViewById(R.id.exo_prev);
if (previousButton != null) {
previousButton.setOnClickListener(componentListener);
}
nextButton = findViewById(R.id.exo_next);
if (nextButton != null) {
nextButton.setOnClickListener(componentListener);
}
rewindButton = findViewById(R.id.exo_rew);
if (rewindButton != null) {
rewindButton.setOnClickListener(componentListener);
}
fastForwardButton = findViewById(R.id.exo_ffwd);
if (fastForwardButton != null) {
fastForwardButton.setOnClickListener(componentListener);
}
repeatToggleButton = findViewById(R.id.exo_repeat_toggle);
if (repeatToggleButton != null) {
repeatToggleButton.setOnClickListener(componentListener);
}
shuffleButton = findViewById(R.id.exo_shuffle);
if (shuffleButton != null) {
shuffleButton.setOnClickListener(componentListener);
}
Resources resources = context.getResources();
repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_off);
repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_one);
repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_all);
repeatOffButtonContentDescription = resources.getString(
R.string.exo_controls_repeat_off_description);
repeatOneButtonContentDescription = resources.getString(
R.string.exo_controls_repeat_one_description);
repeatAllButtonContentDescription = resources.getString(
R.string.exo_controls_repeat_all_description);
durationViewLandscape = findViewById(R.id.exo_player_position_duration_landscape);
timeBarLandscape = findViewById(R.id.exo_player_progress_landscape);
if (timeBarLandscape != null) {
timeBarLandscape.addListener(componentListener);
}
playButtonLandScape = findViewById(R.id.exo_player_play_landscape);
if (playButtonLandScape != null) {
playButtonLandScape.setOnClickListener(componentListener);
}
pauseButtonLandScape = findViewById(R.id.exo_player_pause_landscape);
if (pauseButtonLandScape != null) {
pauseButtonLandScape.setOnClickListener(componentListener);
}
enterFullscreen = findViewById(R.id.exo_player_enter_fullscreen);
if (enterFullscreen != null) {
enterFullscreen.setOnClickListener(componentListener);
}
exitFullscreen = findViewById(R.id.exo_player_exit_fullscreen);
if (exitFullscreen != null) {
exitFullscreen.setOnClickListener(componentListener);
}
centerInfoWrapper = findViewById(R.id.exo_player_center_info_wrapper);
centerInfo = findViewById(R.id.exo_player_center_text);
exoPlayerControllerTop = findViewById(R.id.exo_player_controller_top);
if (exoPlayerControllerTop != null && controllerBackgroundId != 0) {
exoPlayerControllerTop.setBackgroundResource(controllerBackgroundId);
}
exoPlayerControllerTopLandscape = findViewById(R.id.exo_player_controller_top_landscape);
if (exoPlayerControllerTopLandscape != null && controllerBackgroundId != 0) {
exoPlayerControllerTopLandscape.setBackgroundResource(controllerBackgroundId);
}
exoPlayerControllerBottom = findViewById(R.id.exo_player_controller_bottom);
if (exoPlayerControllerBottom != null && controllerBackgroundId != 0) {
exoPlayerControllerBottom.setBackgroundResource(controllerBackgroundId);
}
exoPlayerControllerBottomLandscape = findViewById(R.id.exo_player_controller_bottom_landscape);
if (exoPlayerControllerBottomLandscape != null && controllerBackgroundId != 0) {
exoPlayerControllerBottomLandscape.setBackgroundResource(controllerBackgroundId);
}
exoPlayerVideoName = findViewById(R.id.exo_player_video_name);
if (exoPlayerVideoName != null) {
exoPlayerVideoName.setOnClickListener(componentListener);
}
exoPlayerVideoNameLandscape = findViewById(R.id.exo_player_video_name_landscape);
if (exoPlayerVideoNameLandscape != null) {
exoPlayerVideoNameLandscape.setOnClickListener(componentListener);
}
back = findViewById(R.id.exo_player_controller_back);
if (back != null) {
back.setOnClickListener(componentListener);
}
backLandscape = findViewById(R.id.exo_player_controller_back_landscape);
if(backLandscape != null){
backLandscape.setOnClickListener(componentListener);
}
if (centerInfoWrapper != null) {
setupVideoGesture(enableGesture);
}
exoPlayerCurrentQualityLandscape = findViewById(R.id.exo_player_current_quality_landscape);
if (exoPlayerCurrentQualityLandscape != null) {
exoPlayerCurrentQualityLandscape.setOnClickListener(componentListener);
}
topCustomView = findViewById(R.id.exo_player_controller_top_custom_view);
topCustomViewLandscape = findViewById(R.id.exo_player_controller_top_custom_view_landscape);
bottomCustomViewLandscape = findViewById(R.id.exo_player_controller_bottom_custom_view_landscape);
centerError = findViewById(R.id.exo_player_center_error);
loadingBar = findViewById(R.id.exo_player_loading);
sensorOrientation = new SensorOrientation(getContext(), this::changeOrientation);
showControllerByDisplayMode();
showUtilHideCalled();
}
private void setupVideoGesture(boolean enableGesture) {
OnVideoGestureChangeListener onVideoGestureChangeListener = new OnVideoGestureChangeListener() {
@Override
public void onVolumeChanged(int range, int type) {
show();
int drawableId;
if (type == VOLUME_CHANGED_MUTE) {
drawableId = R.drawable.ic_volume_mute_white_36dp;
} else if (type == VOLUME_CHANGED_INCREMENT) {
drawableId = R.drawable.ic_volume_up_white_36dp;
} else {
drawableId = R.drawable.ic_volume_down_white_36dp;
}
setVolumeOrBrightnessInfo(getContext().getString(R.string.volume_changing, range), drawableId);
}
@Override
public void onBrightnessChanged(int brightnessPercent) {
show();
String info = getContext().getString(R.string.brightness_changing, brightnessPercent);
int drawable = whichBrightnessImageToUse(brightnessPercent);
setVolumeOrBrightnessInfo(info, drawable);
}
@Override
public void onNoGesture() {
if (centerInfo == null) {
return;
}
centerInfo.startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_out));
centerInfo.setVisibility(GONE);
}
@Override
public void onShowSeekSize(long seekSize, boolean fastForward) {
if (isHls) {
return;
}
show();
seekTo(seekSize);
if (centerInfo == null) {
return;
}
if (centerError != null && centerError.getVisibility() == VISIBLE) {
centerError.setVisibility(GONE);
}
centerInfo.setVisibility(VISIBLE);
centerInfo.setText(generateFastForwardOrRewindTxt(seekSize));
int drawableId = fastForward ? R.drawable.ic_fast_forward_white_36dp : R.drawable.ic_fast_rewind_white_36dp;
Drawable drawable = ContextCompat.getDrawable(getContext(), drawableId);
centerInfo.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);
}
};
videoGesture = new VideoGesture(getContext(), onVideoGestureChangeListener, () -> player);
if(!enableGesture){
videoGesture.disable();
}
centerInfoWrapper.setOnClickListener(componentListener);
centerInfoWrapper.setOnTouchListener(videoGesture);
}
private CharSequence generateFastForwardOrRewindTxt(long changingTime) {
long duration = player == null ? 0 : player.getDuration();
String result = Util.getStringForTime(formatBuilder, formatter, changingTime);
result = result + "/";
result = result + Util.getStringForTime(formatBuilder, formatter, duration);
int index = result.indexOf("/");
SpannableString spannableString = new SpannableString(result);
TypedValue typedValue = new TypedValue();
TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorAccent});
int color = a.getColor(0, 0);
a.recycle();
spannableString.setSpan(new ForegroundColorSpan(color), 0, index, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new ForegroundColorSpan(Color.WHITE), index, result.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
return spannableString;
}
@DrawableRes
private int whichBrightnessImageToUse(int brightnessInt) {
if (brightnessInt <= 15) {
return R.drawable.ic_brightness_1_white_36dp;
} else if (brightnessInt <= 30) {
return R.drawable.ic_brightness_2_white_36dp;
} else if (brightnessInt <= 45) {
return R.drawable.ic_brightness_3_white_36dp;
} else if (brightnessInt <= 60) {
return R.drawable.ic_brightness_4_white_36dp;
} else if (brightnessInt <= 75) {
return R.drawable.ic_brightness_5_white_36dp;
} else if (brightnessInt <= 90) {
return R.drawable.ic_brightness_6_white_36dp;
} else {
return R.drawable.ic_brightness_7_white_36dp;
}
}
private void setVolumeOrBrightnessInfo(String txt, @DrawableRes int drawableId) {
if (centerInfo == null) {
return;
}
if (centerError != null && centerError.getVisibility() == VISIBLE) {
centerError.setVisibility(GONE);
}
centerInfo.setVisibility(VISIBLE);
centerInfo.setText(txt);
centerInfo.setTextColor(ContextCompat.getColor(getContext(), android.R.color.white));
centerInfo.setCompoundDrawablesWithIntrinsicBounds(null, ContextCompat.getDrawable(getContext(), drawableId), null, null);
}
@SuppressWarnings("ResourceType")
private static @RepeatModeUtil.RepeatToggleModes
int getRepeatToggleModes(TypedArray a,
@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
return a.getInt(R.styleable.ExoVideoPlaybackControlView_repeat_toggle_modes, repeatToggleModes);
}
/**
* Returns the {@link Player} currently being controlled by this view, or null if no player is
* set.
*/
public Player getPlayer() {
return player;
}
/**
* Sets the {@link Player} to control.
*
* @param player The {@link Player} to control.
*/
public void setPlayer(Player player) {
if (this.player == player) {
return;
}
if (this.player != null) {
this.player.removeListener(componentListener);
}
this.player = player;
if (player != null) {
player.addListener(componentListener);
}
updateAll();
}
/**
* Sets whether the time bar should show all windows, as opposed to just the current one. If the
* timeline has a period with unknown duration or more than
* {@link #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will fall back to showing a
* single window.
*
* @param showMultiWindowTimeBar Whether the time bar should show all windows.
*/
public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
this.showMultiWindowTimeBar = showMultiWindowTimeBar;
updateTimeBarMode();
}
/**
* Sets the millisecond positions of extra ad markers relative to the start of the window (or
* timeline, if in multi-window mode) and whether each extra ad has been played or not. The
* markers are shown in addition to any ad markers for ads in the player's timeline.
*
* @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or
* {@code null} to show no extra ad markers.
* @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad
* markers.
*/
public void setExtraAdGroupMarkers(@Nullable long[] extraAdGroupTimesMs,
@Nullable boolean[] extraPlayedAdGroups) {
if (extraAdGroupTimesMs == null) {
this.extraAdGroupTimesMs = new long[0];
this.extraPlayedAdGroups = new boolean[0];
} else {
Assertions.checkArgument(extraAdGroupTimesMs.length == extraPlayedAdGroups.length);
this.extraAdGroupTimesMs = extraAdGroupTimesMs;
this.extraPlayedAdGroups = extraPlayedAdGroups;
}
updateProgress();
}
/**
* Sets the {@link VisibilityListener}.
*
* @param listener The listener to be notified about visibility changes.
*/
public void setVisibilityListener(VisibilityListener listener) {
this.visibilityListener = listener;
}
/**
* Sets the {@link com.google.android.exoplayer2.ControlDispatcher}.
*
* @param controlDispatcher The {@link com.google.android.exoplayer2.ControlDispatcher}, or null
* to use {@link com.google.android.exoplayer2.DefaultControlDispatcher}.
*/
public void setControlDispatcher(
@Nullable com.google.android.exoplayer2.ControlDispatcher controlDispatcher) {
this.controlDispatcher = controlDispatcher == null
? new com.google.android.exoplayer2.DefaultControlDispatcher() : controlDispatcher;
}
/**
* Sets the rewind increment in milliseconds.
*
* @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
* rewind button to be disabled.
*/
public void setRewindIncrementMs(int rewindMs) {
this.rewindMs = rewindMs;
updateNavigation();
}
/**
* Sets the fast forward increment in milliseconds.
*
* @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
* cause the fast forward button to be disabled.
*/
public void setFastForwardIncrementMs(int fastForwardMs) {
this.fastForwardMs = fastForwardMs;
updateNavigation();
}
/**
* Returns the playback controls timeout. The playback controls are automatically hidden after
* this duration of time has elapsed without user input.
*
* @return The duration in milliseconds. A non-positive value indicates that the controls will
* remain visible indefinitely.
*/
public int getShowTimeoutMs() {
return showTimeoutMs;
}
/**
* Sets the playback controls timeout. The playback controls are automatically hidden after this
* duration of time has elapsed without user input.
*
* @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls
* to remain visible indefinitely.
*/
public void setShowTimeoutMs(int showTimeoutMs) {
this.showTimeoutMs = showTimeoutMs;
}
/**
* Returns which repeat toggle modes are enabled.
*
* @return The currently enabled {@link RepeatModeUtil.RepeatToggleModes}.
*/
public @RepeatModeUtil.RepeatToggleModes
int getRepeatToggleModes() {
return repeatToggleModes;
}
/**
* Sets which repeat toggle modes are enabled.
*
* @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.
*/
public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
this.repeatToggleModes = repeatToggleModes;
if (player != null) {
@Player.RepeatMode int currentMode = player.getRepeatMode();
if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE
&& currentMode != Player.REPEAT_MODE_OFF) {
controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_OFF);
} else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE
&& currentMode == Player.REPEAT_MODE_ALL) {
controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_ONE);
} else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL
&& currentMode == Player.REPEAT_MODE_ONE) {
controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_ALL);
}
}
}
/**
* Returns whether the shuffle button is shown.
*/
public boolean getShowShuffleButton() {
return showShuffleButton;
}
/**
* Sets whether the shuffle button is shown.
*
* @param showShuffleButton Whether the shuffle button is shown.
*/
public void setShowShuffleButton(boolean showShuffleButton) {
this.showShuffleButton = showShuffleButton;
updateShuffleButton();
}
/**
* Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will
* be automatically hidden after this duration of time has elapsed without user input.
*/
public void show() {
if (!isVisible()) {
setVisibility(VISIBLE);
if (visibilityListener != null) {
visibilityListener.onVisibilityChange(getVisibility());
}
if (portrait) {
changeSystemUiVisibilityPortrait();
}
updateAll();
requestPlayPauseFocus();
}
// Call hideAfterTimeout even if already visible to reset the timeout.
hideAfterTimeout();
}
/**
* Hides the controller.
*/
public void hide() {
if (isVisible()) {
setVisibility(GONE);
if (visibilityListener != null) {
visibilityListener.onVisibilityChange(getVisibility());
}
removeCallbacks(updateProgressAction);
removeCallbacks(hideAction);
hideAtMs = C.TIME_UNSET;
if (!portrait) {
changeSystemUiVisibilityLandscape();
}
}
}
/**
* Returns whether the controller is currently visible.
*/
public boolean isVisible() {
return getVisibility() == VISIBLE;
}
private void hideAfterTimeout() {
removeCallbacks(hideAction);
if (showTimeoutMs > 0) {
hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs;
if (isAttachedToWindow) {
postDelayed(hideAction, showTimeoutMs);
}
} else {
hideAtMs = C.TIME_UNSET;
}
}
private void updateAll() {
updatePlayPauseButton();
updateNavigation();
updateRepeatModeButton();
updateShuffleButton();
updateProgress();
}
private void updateNavigation() {
if (!isVisible() || !isAttachedToWindow) {
return;
}
Timeline timeline = player != null ? player.getCurrentTimeline() : null;
boolean haveNonEmptyTimeline = timeline != null && !timeline.isEmpty();
boolean isSeekable = false;
boolean enablePrevious = false;
boolean enableNext = false;
if (haveNonEmptyTimeline && !player.isPlayingAd()) {
int windowIndex = player.getCurrentWindowIndex();
timeline.getWindow(windowIndex, window);
isSeekable = window.isSeekable;
enablePrevious = isSeekable || !window.isDynamic
|| player.getPreviousWindowIndex() != C.INDEX_UNSET;
enableNext = window.isDynamic || player.getNextWindowIndex() != C.INDEX_UNSET;
}
setButtonEnabled(enablePrevious, previousButton);
setButtonEnabled(enableNext, nextButton);
setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton);
setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton);
if (timeBar != null) {
timeBar.setEnabled(isSeekable && !isHls);
}
if (timeBarLandscape != null) {
timeBarLandscape.setEnabled(isSeekable && !isHls);
}
}
private void updatePlayPauseButton() {
if (!isVisible() || !isAttachedToWindow) {
return;
}
boolean requestPlayPauseFocus = false;
boolean playing = player != null && player.getPlayWhenReady();
if (playButton != null) {
requestPlayPauseFocus |= playing && playButton.isFocused();
playButton.setVisibility(playing ? View.GONE : View.VISIBLE);
}
if (pauseButton != null) {
requestPlayPauseFocus |= !playing && pauseButton.isFocused();
pauseButton.setVisibility(!playing ? View.GONE : View.VISIBLE);
}
if (playButtonLandScape != null) {
requestPlayPauseFocus |= playing && playButtonLandScape.isFocused();
playButtonLandScape.setVisibility(playing ? View.GONE : View.VISIBLE);
}
if (pauseButtonLandScape != null) {
requestPlayPauseFocus |= !playing && pauseButtonLandScape.isFocused();
pauseButtonLandScape.setVisibility(!playing ? View.GONE : View.VISIBLE);
}
if (requestPlayPauseFocus) {
requestPlayPauseFocus();
}
}
private void updateRepeatModeButton() {
if (!isVisible() || !isAttachedToWindow || repeatToggleButton == null) {
return;
}
if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) {
repeatToggleButton.setVisibility(View.GONE);
return;
}
if (player == null) {
setButtonEnabled(false, repeatToggleButton);
return;
}
setButtonEnabled(true, repeatToggleButton);
switch (player.getRepeatMode()) {
case Player.REPEAT_MODE_OFF:
repeatToggleButton.setImageDrawable(repeatOffButtonDrawable);
repeatToggleButton.setContentDescription(repeatOffButtonContentDescription);
break;
case Player.REPEAT_MODE_ONE:
repeatToggleButton.setImageDrawable(repeatOneButtonDrawable);
repeatToggleButton.setContentDescription(repeatOneButtonContentDescription);
break;
case Player.REPEAT_MODE_ALL:
repeatToggleButton.setImageDrawable(repeatAllButtonDrawable);
repeatToggleButton.setContentDescription(repeatAllButtonContentDescription);
break;
}
repeatToggleButton.setVisibility(View.VISIBLE);
}
private void updateShuffleButton() {
if (!isVisible() || !isAttachedToWindow || shuffleButton == null) {
return;
}
if (!showShuffleButton) {
shuffleButton.setVisibility(View.GONE);
} else if (player == null) {
setButtonEnabled(false, shuffleButton);
} else {
shuffleButton.setAlpha(player.getShuffleModeEnabled() ? 1f : 0.3f);
shuffleButton.setEnabled(true);
shuffleButton.setVisibility(View.VISIBLE);
}
}
private void updateTimeBarMode() {
if (player == null) {
return;
}
multiWindowTimeBar = showMultiWindowTimeBar
&& canShowMultiWindowTimeBar(player.getCurrentTimeline(), window);
}
private void updateProgress() {
if (!isVisible() || !isAttachedToWindow) {
return;
}
long position = 0;
long bufferedPosition = 0;
long duration = 0;
if (player != null) {
long currentWindowTimeBarOffsetUs = 0;
long durationUs = 0;
int adGroupCount = 0;
Timeline timeline = player.getCurrentTimeline();
if (!timeline.isEmpty()) {
int currentWindowIndex = player.getCurrentWindowIndex();
int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex;
int lastWindowIndex =
multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex;
for (int i = firstWindowIndex; i <= lastWindowIndex; i++) {
if (i == currentWindowIndex) {
currentWindowTimeBarOffsetUs = durationUs;
}
timeline.getWindow(i, window);
if (window.durationUs == C.TIME_UNSET) {
Assertions.checkState(!multiWindowTimeBar);
break;
}
for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) {
timeline.getPeriod(j, period);
int periodAdGroupCount = period.getAdGroupCount();
for (int adGroupIndex = 0; adGroupIndex < periodAdGroupCount; adGroupIndex++) {
long adGroupTimeInPeriodUs = period.getAdGroupTimeUs(adGroupIndex);
if (adGroupTimeInPeriodUs == C.TIME_END_OF_SOURCE) {
if (period.durationUs == C.TIME_UNSET) {
// Don't show ad markers for postrolls in periods with unknown duration.
continue;
}
adGroupTimeInPeriodUs = period.durationUs;
}
long adGroupTimeInWindowUs = adGroupTimeInPeriodUs + period.getPositionInWindowUs();
if (adGroupTimeInWindowUs >= 0 && adGroupTimeInWindowUs <= window.durationUs) {
if (adGroupCount == adGroupTimesMs.length) {
int newLength = adGroupTimesMs.length == 0 ? 1 : adGroupTimesMs.length * 2;
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength);
playedAdGroups = Arrays.copyOf(playedAdGroups, newLength);
}
adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs);
playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex);
adGroupCount++;
}
}
}
durationUs += window.durationUs;
}
}
duration = C.usToMs(durationUs);
position = C.usToMs(currentWindowTimeBarOffsetUs);
bufferedPosition = position;
if (player.isPlayingAd()) {
position += player.getContentPosition();
bufferedPosition = position;
} else {
position += player.getCurrentPosition();
bufferedPosition += player.getBufferedPosition();
}
if (timeBar != null) {
int extraAdGroupCount = extraAdGroupTimesMs.length;
int totalAdGroupCount = adGroupCount + extraAdGroupCount;
if (totalAdGroupCount > adGroupTimesMs.length) {
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, totalAdGroupCount);
playedAdGroups = Arrays.copyOf(playedAdGroups, totalAdGroupCount);
}
System.arraycopy(extraAdGroupTimesMs, 0, adGroupTimesMs, adGroupCount, extraAdGroupCount);
System.arraycopy(extraPlayedAdGroups, 0, playedAdGroups, adGroupCount, extraAdGroupCount);
timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, totalAdGroupCount);
}
}
if (durationView != null && !isHls) {
durationView.setText(Util.getStringForTime(formatBuilder, formatter, duration));
}
if (durationViewLandscape != null && !isHls) {
String positionStr = Util.getStringForTime(formatBuilder, formatter, position);
String durationStr = Util.getStringForTime(formatBuilder, formatter, duration);
durationViewLandscape.setText(positionStr.concat("/").concat(durationStr));
}
if (positionView != null && !scrubbing && !isHls) {
positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));
}
if (timeBar != null && !isHls) {
timeBar.setPosition(position);
timeBar.setBufferedPosition(bufferedPosition);
timeBar.setDuration(duration);
}
if (timeBarLandscape != null && !isHls) {
timeBarLandscape.setPosition(position);
timeBarLandscape.setBufferedPosition(bufferedPosition);
timeBarLandscape.setDuration(duration);
}
// Cancel any pending updates and schedule a new one if necessary.
removeCallbacks(updateProgressAction);
int playbackState = player == null ? Player.STATE_IDLE : player.getPlaybackState();
if (playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED) {
long delayMs;
if (player.getPlayWhenReady() && playbackState == Player.STATE_READY) {
float playbackSpeed = player.getPlaybackParameters().speed;
if (playbackSpeed <= 0.1f) {
delayMs = 1000;
} else if (playbackSpeed <= 5f) {
long mediaTimeUpdatePeriodMs = 1000 / Math.max(1, Math.round(1 / playbackSpeed));
long mediaTimeDelayMs = mediaTimeUpdatePeriodMs - (position % mediaTimeUpdatePeriodMs);
if (mediaTimeDelayMs < (mediaTimeUpdatePeriodMs / 5)) {
mediaTimeDelayMs += mediaTimeUpdatePeriodMs;
}
delayMs = playbackSpeed == 1 ? mediaTimeDelayMs
: (long) (mediaTimeDelayMs / playbackSpeed);
} else {
delayMs = 200;
}
} else {
delayMs = 1000;
}
postDelayed(updateProgressAction, delayMs);
}
}
private void requestPlayPauseFocus() {
boolean playing = player != null && player.getPlayWhenReady();
if (!playing && playButton != null) {
playButton.requestFocus();
} else if (playing && pauseButton != null) {
pauseButton.requestFocus();
}
if (!playing && playButtonLandScape != null) {
playButtonLandScape.requestFocus();
} else if (playing && pauseButtonLandScape != null) {
pauseButtonLandScape.requestFocus();
}
}
private void setButtonEnabled(boolean enabled, View view) {
if (view == null) {
return;
}
view.setEnabled(enabled);
view.setAlpha(enabled ? 1f : 0.3f);
view.setVisibility(VISIBLE);
}
private void previous() {
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return;
}
int windowIndex = player.getCurrentWindowIndex();
timeline.getWindow(windowIndex, window);
int previousWindowIndex = player.getPreviousWindowIndex();
if (previousWindowIndex != C.INDEX_UNSET
&& (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
|| (window.isDynamic && !window.isSeekable))) {
seekTo(previousWindowIndex, C.TIME_UNSET);
} else {
seekTo(0);
}
}
private void next() {
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return;
}
int windowIndex = player.getCurrentWindowIndex();
int nextWindowIndex = player.getNextWindowIndex();
if (nextWindowIndex != C.INDEX_UNSET) {
seekTo(nextWindowIndex, C.TIME_UNSET);
} else if (timeline.getWindow(windowIndex, window, false).isDynamic) {
seekTo(windowIndex, C.TIME_UNSET);
}
}
private void rewind() {
if (rewindMs <= 0) {
return;
}
seekTo(Math.max(player.getCurrentPosition() - rewindMs, 0));
}
private void fastForward() {
if (fastForwardMs <= 0) {
return;
}
long durationMs = player.getDuration();
long seekPositionMs = player.getCurrentPosition() + fastForwardMs;
if (durationMs != C.TIME_UNSET) {
seekPositionMs = Math.min(seekPositionMs, durationMs);
}
seekTo(seekPositionMs);
}
private void seekTo(long positionMs) {
seekTo(player.getCurrentWindowIndex(), positionMs);
}
private void seekTo(int windowIndex, long positionMs) {
boolean dispatched = controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs);
if (!dispatched) {
// The seek wasn't dispatched. If the progress bar was dragged by the user to perform the
// seek then it'll now be in the wrong position. Trigger a progress update to snap it back.
updateProgress();
}
}
private void seekToTimeBarPosition(long positionMs) {
int windowIndex;
Timeline timeline = player.getCurrentTimeline();
if (multiWindowTimeBar && !timeline.isEmpty()) {
int windowCount = timeline.getWindowCount();
windowIndex = 0;
while (true) {
long windowDurationMs = timeline.getWindow(windowIndex, window).getDurationMs();
if (positionMs < windowDurationMs) {
break;
} else if (windowIndex == windowCount - 1) {
// Seeking past the end of the last window should seek to the end of the timeline.
positionMs = windowDurationMs;
break;
}
positionMs -= windowDurationMs;
windowIndex++;
}
} else {
windowIndex = player.getCurrentWindowIndex();
}
seekTo(windowIndex, positionMs);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
sensorOrientation.enable();
isAttachedToWindow = true;
if (hideAtMs != C.TIME_UNSET) {
long delayMs = hideAtMs - SystemClock.uptimeMillis();
if (delayMs <= 0) {
hide();
} else {
postDelayed(hideAction, delayMs);
}
}
updateAll();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
sensorOrientation.disable();
isAttachedToWindow = false;
removeCallbacks(updateProgressAction);
removeCallbacks(hideAction);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event);
}
/**
* Called to process media key events. Any {@link KeyEvent} can be passed but only media key
* events will be handled.
*
* @param event A key event.
* @return Whether the key event was handled.
*/
public boolean dispatchMediaKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (player == null || !isHandledMediaKey(keyCode)) {
return false;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
fastForward();
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) {
rewind();
} else if (event.getRepeatCount() == 0) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
controlDispatcher.dispatchSetPlayWhenReady(player, !player.getPlayWhenReady());
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
controlDispatcher.dispatchSetPlayWhenReady(player, true);
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
controlDispatcher.dispatchSetPlayWhenReady(player, false);
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
next();
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
previous();
break;
default:
break;
}
}
}
return true;
}
public void setBackListener(ExoClickListener backListener) {
this.backListener = backListener;
}
public void setPortrait(boolean portrait) {
this.portrait = portrait;
showControllerByDisplayMode();
}
public boolean isPortrait() {
return portrait;
}
private void toggleControllerOrientation() {
if (orientationListener == null) {
setPortrait(!portrait);
} else {
changeOrientation(portrait ? SENSOR_LANDSCAPE : SENSOR_PORTRAIT);
}
}
public void setOrientationListener(OrientationListener orientationListener) {
this.orientationListener = orientationListener;
}
public void setMediaSource(ExoMediaSource exoMediaSource) {
if (exoPlayerVideoName != null) {
exoPlayerVideoName.setText(exoMediaSource.name());
}
if (exoPlayerVideoNameLandscape != null) {
exoPlayerVideoNameLandscape.setText(exoMediaSource.name());
}
if (centerError != null) {
centerError.setText(null);
centerError.setVisibility(GONE);
}
}
public void setControllerDisplayMode(int displayMode) {
this.displayMode = displayMode;
showControllerByDisplayMode();
}
private void showControllerByDisplayMode() {
if (exoPlayerControllerTop != null) {
boolean showByMode = (displayMode & CONTROLLER_MODE_TOP) == CONTROLLER_MODE_TOP;
if (portrait) {
int visibility = showByMode ? VISIBLE : INVISIBLE;
exoPlayerControllerTop.setVisibility(visibility);
} else {
exoPlayerControllerTop.setVisibility(INVISIBLE);
}
}
if (exoPlayerControllerTopLandscape != null) {
boolean showByMode = (displayMode & CONTROLLER_MODE_TOP_LANDSCAPE) == CONTROLLER_MODE_TOP_LANDSCAPE;
if (portrait) {
exoPlayerControllerTopLandscape.setVisibility(INVISIBLE);
} else {
int visibility = showByMode ? VISIBLE : INVISIBLE;
exoPlayerControllerTopLandscape.setVisibility(visibility);
}
}
if (exoPlayerControllerBottom != null) {
boolean showByMode = (displayMode & CONTROLLER_MODE_BOTTOM) == CONTROLLER_MODE_BOTTOM;
if (portrait) {
int visibility = showByMode ? VISIBLE : INVISIBLE;
exoPlayerControllerBottom.setVisibility(visibility);
} else {
exoPlayerControllerBottom.setVisibility(INVISIBLE);
}
}
if (exoPlayerControllerBottomLandscape != null) {
boolean showByMode = (displayMode & CONTROLLER_MODE_BOTTOM_LANDSCAPE) == CONTROLLER_MODE_BOTTOM_LANDSCAPE;
if (portrait) {
exoPlayerControllerBottomLandscape.setVisibility(INVISIBLE);
} else {
int visibility = showByMode ? VISIBLE : INVISIBLE;
exoPlayerControllerBottomLandscape.setVisibility(visibility);
}
}
if (qualityVisibilityCallback != null) {
qualityVisibilityCallback.shouldChangeVisibility(GONE);
}
}
private synchronized void changeOrientation(@OnOrientationChangedListener.SensorOrientationType int orientation) {
Context context = getContext();
Activity activity;
if (!(context instanceof Activity)) {
return;
}
if (orientationListener == null) {
return;
}
activity = (Activity) context;
switch (orientation) {
case SENSOR_PORTRAIT:
setPortrait(true);
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
changeSystemUiVisibilityPortrait();
break;
case SENSOR_LANDSCAPE:
setPortrait(false);
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
changeSystemUiVisibilityLandscape();
break;
case SENSOR_UNKNOWN:
default:
break;
}
orientationListener.onOrientationChanged(orientation);
}
private void changeSystemUiVisibilityPortrait() {
videoViewAccessor.attachVideoView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
private void changeSystemUiVisibilityLandscape() {
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
if (windowManager == null) {
return;
}
int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
flag |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
videoViewAccessor.attachVideoView().setSystemUiVisibility(flag);
}
public void setVideoViewAccessor(VideoViewAccessor videoViewAccessor) {
this.videoViewAccessor = videoViewAccessor;
}
public void setVisibilityCallback(MultiQualitySelectorAdapter.VisibilityCallback qualityVisibilityCallback) {
this.qualityVisibilityCallback = qualityVisibilityCallback;
}
public void updateQualityDes(CharSequence qualityDes) {
if (exoPlayerCurrentQualityLandscape != null) {
exoPlayerCurrentQualityLandscape.setText(qualityDes);
}
}
/**
* add your view to controller
*
* @param customViewType the target view type
* @param customView the view you want to add
* @param removeViews remove all views in target view before add if true
**/
public void addCustomView(@CustomViewType int customViewType, View customView, boolean removeViews) {
ViewGroup viewGroup = null;
if (customViewType == CUSTOM_VIEW_TOP && topCustomView != null) {
viewGroup = topCustomView;
} else if (customViewType == CUSTOM_VIEW_TOP_LANDSCAPE && topCustomView != null) {
viewGroup = topCustomViewLandscape;
} else if (customViewType == CUSTOM_VIEW_BOTTOM_LANDSCAPE && topCustomView != null) {
viewGroup = bottomCustomViewLandscape;
}
if (viewGroup != null) {
if (removeViews) {
viewGroup.removeAllViews();
}
viewGroup.addView(customView);
}
}
public void addCustomView(@CustomViewType int customViewType, View customView) {
addCustomView(customViewType, customView, false);
}
public void changeWidgetVisibility(int id,int visibility){
View view = findViewById(id);
if(view != null){
view.setVisibility(visibility);
}
}
private void showLoading(boolean isLoading) {
if(loadingBar == null ){
return;
}
if (isLoading) {
loadingBar.setVisibility(View.VISIBLE);
} else {
loadingBar.setVisibility(GONE);
}
}
public void showUtilHideCalled() {
if (!isVisible()) {
setVisibility(VISIBLE);
if (visibilityListener != null) {
visibilityListener.onVisibilityChange(getVisibility());
}
updateAll();
}
}
public void setGestureEnabled(boolean enabled){
if(centerInfoWrapper == null){
return;
}
if (videoGesture == null) {
return;
}
if(enabled){
videoGesture.enable();
}else {
videoGesture.disable();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (backListener != null) {
if (!backListener.onClick(null, portrait)) {
if (portrait) {
return super.onKeyDown(keyCode, event);
} else {
changeOrientation(SENSOR_PORTRAIT);
return true;
}
}
}
}
return super.onKeyDown(keyCode, event);
}
@SuppressLint("InlinedApi")
private static boolean isHandledMediaKey(int keyCode) {
return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
|| keyCode == KeyEvent.KEYCODE_MEDIA_REWIND
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE
|| keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
|| keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS;
}
/**
* Returns whether the specified {@code timeline} can be shown on a multi-window time bar.
*
* @param timeline The {@link Timeline} to check.
* @param window A scratch {@link Timeline.Window} instance.
* @return Whether the specified timeline can be shown on a multi-window time bar.
*/
private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Window window) {
if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) {
return false;
}
int windowCount = timeline.getWindowCount();
for (int i = 0; i < windowCount; i++) {
if (timeline.getWindow(i, window).durationUs == C.TIME_UNSET) {
return false;
}
}
return true;
}
private final class ComponentListener implements
TimeBar.OnScrubListener, OnClickListener,Player.EventListener {
@Override
public void onScrubStart(TimeBar timeBar, long position) {
removeCallbacks(hideAction);
scrubbing = true;
}
@Override
public void onScrubMove(TimeBar timeBar, long position) {
if (positionView != null) {
positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));
}
}
@Override
public void onScrubStop(TimeBar timeBar, long position, boolean canceled) {
scrubbing = false;
if (!canceled && player != null) {
seekToTimeBarPosition(position);
}
hideAfterTimeout();
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState != Player.STATE_IDLE && centerError != null && centerError.getVisibility() == VISIBLE) {
centerError.setVisibility(GONE);
}
if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_BUFFERING) {
removeCallbacks(hideAction);
showUtilHideCalled();
showLoading(true);
} else if (playbackState == Player.STATE_READY && player.getPlayWhenReady() || playbackState == Player.STATE_ENDED) {
showLoading(false);
hide();
}
updatePlayPauseButton();
updateProgress();
}
@Override
public void onRepeatModeChanged(int repeatMode) {
updateRepeatModeButton();
updateNavigation();
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
updateShuffleButton();
updateNavigation();
}
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
updateNavigation();
updateProgress();
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
if (manifest instanceof HlsManifest) {
HlsManifest hlsManifest = (HlsManifest) manifest;
isHls = !hlsManifest.mediaPlaylist.hasEndTag && hlsManifest.mediaPlaylist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
} else {
isHls = false;
}
updateNavigation();
updateTimeBarMode();
updateProgress();
}
@Override
public void onPlayerError(ExoPlaybackException error) {
if(loadingBar != null){
loadingBar.setVisibility(GONE);
}
if (centerError != null) {
String errorText = getResources().getString(R.string.player_error, error.type);
centerError.setText(errorText);
centerError.setVisibility(VISIBLE);
}
}
@Override
public void onClick(View view) {
if (player != null) {
if (nextButton == view) {
next();
} else if (previousButton == view) {
previous();
} else if (fastForwardButton == view) {
fastForward();
} else if (rewindButton == view) {
rewind();
} else if (playButton == view || playButtonLandScape == view) {
controlDispatcher.dispatchSetPlayWhenReady(player, true);
} else if (pauseButton == view || pauseButtonLandScape == view) {
controlDispatcher.dispatchSetPlayWhenReady(player, false);
} else if (repeatToggleButton == view) {
controlDispatcher.dispatchSetRepeatMode(player, RepeatModeUtil.getNextRepeatMode(
player.getRepeatMode(), repeatToggleModes));
} else if (shuffleButton == view) {
controlDispatcher.dispatchSetShuffleModeEnabled(player, !player.getShuffleModeEnabled());
} else if (enterFullscreen == view) {
changeOrientation(SENSOR_LANDSCAPE);
} else if (exitFullscreen == view) {
changeOrientation(SENSOR_PORTRAIT);
} else if (exoPlayerVideoName == view || back == view) {
if (backListener != null) {
if (!backListener.onClick(view, portrait)) {
changeOrientation(SENSOR_LANDSCAPE);
}
}
} else if (exoPlayerVideoNameLandscape == view || backLandscape == view) {
if (backListener != null) {
if (!backListener.onClick(view, portrait)) {
changeOrientation(SENSOR_PORTRAIT);
}
}
} else if (centerInfoWrapper == view) {
playOrPause();
} else if (exoPlayerCurrentQualityLandscape == view && qualityVisibilityCallback != null) {
hide();
qualityVisibilityCallback.shouldChangeVisibility(View.VISIBLE);
}
}
hideAfterTimeout();
}
long[] mHits = new long[2];
private void playOrPause() {
System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);
mHits[mHits.length - 1] = SystemClock.uptimeMillis();
if (500 > (SystemClock.uptimeMillis() - mHits[0])) {
controlDispatcher.dispatchSetPlayWhenReady(player, !player.getPlayWhenReady());
}
}
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/ui/ExoVideoView.java
================================================
package com.jarvanmo.exoplayerview.ui;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util;
import com.jarvanmo.exoplayerview.R;
import com.jarvanmo.exoplayerview.extension.MultiQualitySelectorAdapter;
import com.jarvanmo.exoplayerview.media.ExoMediaSource;
import com.jarvanmo.exoplayerview.media.MediaSourceCreator;
import java.util.List;
import static android.content.Context.AUDIO_SERVICE;
/**
* Created by mo on 16-11-7.
*
* @author mo
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class ExoVideoView extends FrameLayout implements ExoVideoPlaybackControlView.VideoViewAccessor {
private static final int SURFACE_TYPE_NONE = 0;
private static final int SURFACE_TYPE_SURFACE_VIEW = 1;
private static final int SURFACE_TYPE_TEXTURE_VIEW = 2;
private final AspectRatioFrameLayout contentFrame;
private final View shutterView;
private final View surfaceView;
private final ImageView artworkView;
private final SubtitleView subtitleView;
private final ExoVideoPlaybackControlView controller;
private final ComponentListener componentListener;
private final FrameLayout overlayFrameLayout;
private SimpleExoPlayer player;
private boolean useController;
private boolean useArtwork;
private Bitmap defaultArtwork;
private int controllerShowTimeoutMs;
private boolean controllerAutoShow;
private boolean controllerHideOnTouch;
private boolean pausedFromPlayer = false;
private boolean enableMultiQuality = true;
private MultiQualitySelectorAdapter.MultiQualitySelectorNavigator multiQualitySelectorNavigator;
private final AudioManager audioManager;
private AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
resume();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// audioManager.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
audioManager.abandonAudioFocus(afChangeListener);
// Stop playback
stop();
}
}
};
private long lastPlayedPosition = 0L;
private long[] mHits = new long[2];
private int controllerBackgroundId = 0;
public ExoVideoView(Context context) {
this(context, null);
}
public ExoVideoView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExoVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
audioManager = (AudioManager) context.getApplicationContext().getSystemService(AUDIO_SERVICE);
if (isInEditMode()) {
contentFrame = null;
shutterView = null;
surfaceView = null;
artworkView = null;
subtitleView = null;
controller = null;
componentListener = null;
overlayFrameLayout = null;
ImageView logo = new ImageView(context);
if (Util.SDK_INT >= 23) {
configureEditModeLogoV23(getResources(), logo);
} else {
configureEditModeLogo(getResources(), logo);
}
addView(logo);
return;
}
boolean shutterColorSet = false;
int shutterColor = 0;
int playerLayoutId = R.layout.exo_video_view;
boolean useArtwork = true;
int defaultArtworkId = 0;
boolean useController = true;
int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS;
boolean controllerHideOnTouch = true;
boolean controllerAutoShow = true;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.ExoVideoView, 0, 0);
try {
shutterColorSet = a.hasValue(R.styleable.ExoVideoView_shutter_background_color);
shutterColor = a.getColor(R.styleable.ExoVideoView_shutter_background_color,
shutterColor);
playerLayoutId = a.getResourceId(R.styleable.ExoVideoView_player_layout_id,
playerLayoutId);
useArtwork = a.getBoolean(R.styleable.ExoVideoView_use_artwork, useArtwork);
defaultArtworkId = a.getResourceId(R.styleable.ExoVideoView_default_artwork,
defaultArtworkId);
useController = a.getBoolean(R.styleable.ExoVideoView_use_controller, useController);
surfaceType = a.getInt(R.styleable.ExoVideoView_surface_type, surfaceType);
resizeMode = a.getInt(R.styleable.ExoVideoView_resize_mode, resizeMode);
controllerShowTimeoutMs = a.getInt(R.styleable.ExoVideoView_show_timeout,
controllerShowTimeoutMs);
controllerHideOnTouch = a.getBoolean(R.styleable.ExoVideoView_hide_on_touch,
controllerHideOnTouch);
controllerAutoShow = a.getBoolean(R.styleable.ExoVideoView_auto_show,
controllerAutoShow);
enableMultiQuality = a.getBoolean(R.styleable.ExoVideoView_enable_multi_quality, true);
controllerBackgroundId = a.getResourceId(R.styleable.ExoVideoView_controller_background, 0);
} finally {
a.recycle();
}
}
LayoutInflater.from(context).inflate(playerLayoutId, this);
componentListener = new ComponentListener();
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
// Content frame.
contentFrame = findViewById(R.id.exo_player_content_frame);
if (contentFrame != null) {
setResizeModeRaw(contentFrame, resizeMode);
}
// Shutter view.
shutterView = findViewById(R.id.exo_player_shutter);
if (shutterView != null && shutterColorSet) {
shutterView.setBackgroundColor(shutterColor);
}
// Create a surface view and insert it into the content frame, if there is one.
if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
surfaceView = surfaceType == SURFACE_TYPE_TEXTURE_VIEW ? new TextureView(context)
: new SurfaceView(context);
surfaceView.setLayoutParams(params);
contentFrame.addView(surfaceView, 0);
} else {
surfaceView = null;
}
// Overlay frame layout.
overlayFrameLayout = findViewById(R.id.exo_player_overlay);
// Artwork view.
artworkView = findViewById(R.id.exo_player_artwork);
this.useArtwork = useArtwork && artworkView != null;
if (defaultArtworkId != 0) {
defaultArtwork = BitmapFactory.decodeResource(context.getResources(), defaultArtworkId);
setArtworkFromBitmap(defaultArtwork);
}
// Subtitle view.
subtitleView = findViewById(R.id.exo_player_subtitles);
if (subtitleView != null) {
subtitleView.setUserDefaultStyle();
subtitleView.setUserDefaultTextSize();
}
// Playback control view.
ExoVideoPlaybackControlView customController = findViewById(R.id.exo_player_controller);
View controllerPlaceholder = findViewById(R.id.exo_player_controller_placeholder);
if (customController != null) {
this.controller = customController;
} else if (controllerPlaceholder != null) {
// Propagate attrs as playbackAttrs so that PlaybackControlView's custom attributes are
// transferred, but standard FrameLayout attributes (e.g. background) are not.
this.controller = new ExoVideoPlaybackControlView(context, null, 0, attrs);
controller.setLayoutParams(controllerPlaceholder.getLayoutParams());
ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
int controllerIndex = parent.indexOfChild(controllerPlaceholder);
parent.removeView(controllerPlaceholder);
parent.addView(controller, controllerIndex);
} else {
this.controller = null;
}
this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;
this.controllerHideOnTouch = controllerHideOnTouch;
this.controllerAutoShow = controllerAutoShow;
this.useController = useController && controller != null;
if (useController && controller != null) {
controller.show();
controller.setVideoViewAccessor(this);
}
setKeepScreenOn(true);
}
/**
* Switches the view targeted by a given {@link SimpleExoPlayer}.
*
* @param player The player whose target view is being switched.
* @param oldPlayerView The old view to detach from the player.
* @param newPlayerView The new view to attach to the player.
*/
public static void switchTargetView(@NonNull SimpleExoPlayer player,
@Nullable ExoVideoView oldPlayerView, @Nullable ExoVideoView newPlayerView) {
if (oldPlayerView == newPlayerView) {
return;
}
// We attach the new view before detaching the old one because this ordering allows the player
// to swap directly from one surface to another, without transitioning through a state where no
// surface is attached. This is significantly more efficient and achieves a more seamless
// transition when using platform provided video decoders.
if (newPlayerView != null) {
newPlayerView.setPlayer(player);
}
if (oldPlayerView != null) {
oldPlayerView.setPlayer(null);
}
}
/**
* Returns the player currently set on this view, or null if no player is set.
*/
public SimpleExoPlayer getPlayer() {
return player;
}
/**
* Set the {@link SimpleExoPlayer} to use.
*
* To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to
* use {@link #switchTargetView(SimpleExoPlayer, ExoVideoView, ExoVideoView)} rather
* than this method. If you do wish to use this method directly, be sure to attach the player to
* the new view before calling {@code setPlayer(null)} to detach it from the old one.
* This ordering is significantly more efficient and may allow for more seamless transitions.
*
* @param player The {@link SimpleExoPlayer} to use.
*/
public void setPlayer(SimpleExoPlayer player) {
if (this.player == player) {
return;
}
if (this.player != null) {
this.player.removeListener(componentListener);
this.player.removeTextOutput(componentListener);
this.player.removeVideoListener(componentListener);
if (surfaceView instanceof TextureView) {
this.player.clearVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SurfaceView) {
this.player.clearVideoSurfaceView((SurfaceView) surfaceView);
}
}
this.player = player;
if (useController) {
controller.setPlayer(player);
}
if (shutterView != null) {
shutterView.setVisibility(VISIBLE);
}
if (player != null) {
if (surfaceView instanceof TextureView) {
player.setVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SurfaceView) {
player.setVideoSurfaceView((SurfaceView) surfaceView);
}
player.addVideoListener(componentListener);
player.addTextOutput(componentListener);
player.addListener(componentListener);
maybeShowController(false);
updateForCurrentTrackSelections();
} else {
hideController();
hideArtwork();
}
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (surfaceView instanceof SurfaceView) {
// Work around https://github.com/google/ExoPlayer/issues/3160.
surfaceView.setVisibility(visibility);
}
}
/**
* Sets the resize mode.
*
* @param resizeMode The resize mode.
*/
public void setResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) {
Assertions.checkState(contentFrame != null);
contentFrame.setResizeMode(resizeMode);
}
/**
* Returns whether artwork is displayed if present in the media.
*/
public boolean getUseArtwork() {
return useArtwork;
}
/**
* Sets whether artwork is displayed if present in the media.
*
* @param useArtwork Whether artwork is displayed.
*/
public void setUseArtwork(boolean useArtwork) {
Assertions.checkState(!useArtwork || artworkView != null);
if (this.useArtwork != useArtwork) {
this.useArtwork = useArtwork;
updateForCurrentTrackSelections();
}
}
/**
* Returns the default artwork to display.
*/
public Bitmap getDefaultArtwork() {
return defaultArtwork;
}
/**
* Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is
* present in the media.
*
* @param defaultArtwork the default artwork to display.
*/
public void setDefaultArtwork(Bitmap defaultArtwork) {
if (this.defaultArtwork != defaultArtwork) {
this.defaultArtwork = defaultArtwork;
updateForCurrentTrackSelections();
}
}
/**
* Returns whether the playback controls can be shown.
*/
public boolean getUseController() {
return useController;
}
/**
* Sets whether the playback controls can be shown. If set to {@code false} the playback controls
* are never visible and are disconnected from the player.
*
* @param useController Whether the playback controls can be shown.
*/
public void setUseController(boolean useController) {
Assertions.checkState(!useController || controller != null);
if (this.useController == useController) {
return;
}
this.useController = useController;
if (useController) {
controller.setPlayer(player);
} else if (controller != null) {
controller.hide();
controller.setPlayer(null);
}
}
/**
* Sets the background color of the {@code exo_shutter} view.
*
* @param color The background color.
*/
public void setShutterBackgroundColor(int color) {
if (shutterView != null) {
shutterView.setBackgroundColor(color);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (audioManager != null) {
requestAudioFocus();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (audioManager != null) {
audioManager.abandonAudioFocus(afChangeListener);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (player != null && player.isPlayingAd()) {
// Focus any overlay UI now, in case it's provided by a WebView whose contents may update
// dynamically. This is needed to make the "Skip ad" button focused on Android TV when using
// IMA [Internal: b/62371030].
overlayFrameLayout.requestFocus();
return super.dispatchKeyEvent(event);
}
boolean isDpadWhenControlHidden = isDpadKey(event.getKeyCode()) && useController
&& !controller.isVisible();
maybeShowController(true);
return isDpadWhenControlHidden || dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event);
}
/**
* Called to process media key events. Any {@link KeyEvent} can be passed but only media key
* events will be handled. Does nothing if playback controls are disabled.
*
* @param event A key event.
* @return Whether the key event was handled.
*/
public boolean dispatchMediaKeyEvent(KeyEvent event) {
return useController && controller.dispatchMediaKeyEvent(event);
}
/**
* Shows the playback controls. Does nothing if playback controls are disabled.
*
*
The playback controls are automatically hidden during playback after
* {{@link #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not
* started yet, is paused, has ended or failed.
*/
public void showController() {
showController(shouldShowControllerIndefinitely());
}
/**
* Hides the playback controls. Does nothing if playback controls are disabled.
*/
public void hideController() {
if (controller != null) {
controller.hide();
}
}
/**
* Returns the playback controls timeout. The playback controls are automatically hidden after
* this duration of time has elapsed without user input and with playback or buffering in
* progress.
*
* @return The timeout in milliseconds. A non-positive value will cause the controller to remain
* visible indefinitely.
*/
public int getControllerShowTimeoutMs() {
return controllerShowTimeoutMs;
}
/**
* Sets the playback controls timeout. The playback controls are automatically hidden after this
* duration of time has elapsed without user input and with playback or buffering in progress.
*
* @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause
* the controller to remain visible indefinitely.
*/
public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) {
Assertions.checkState(controller != null);
this.controllerShowTimeoutMs = controllerShowTimeoutMs;
}
/**
* Returns whether the playback controls are hidden by touch events.
*/
public boolean getControllerHideOnTouch() {
return controllerHideOnTouch;
}
/**
* Sets whether the playback controls are hidden by touch events.
*
* @param controllerHideOnTouch Whether the playback controls are hidden by touch events.
*/
public void setControllerHideOnTouch(boolean controllerHideOnTouch) {
Assertions.checkState(controller != null);
this.controllerHideOnTouch = controllerHideOnTouch;
}
/**
* Returns whether the playback controls are automatically shown when playback starts, pauses,
* ends, or fails. If set to false, the playback controls can be manually operated with {@link
* #showController()} and {@link #hideController()}.
*/
public boolean getControllerAutoShow() {
return controllerAutoShow;
}
/**
* Sets whether the playback controls are automatically shown when playback starts, pauses, ends,
* or fails. If set to false, the playback controls can be manually operated with {@link
* #showController()} and {@link #hideController()}.
*
* @param controllerAutoShow Whether the playback controls are allowed to show automatically.
*/
public void setControllerAutoShow(boolean controllerAutoShow) {
this.controllerAutoShow = controllerAutoShow;
}
/**
* Set the {@link PlaybackControlView.VisibilityListener}.
*
* @param listener The listener to be notified about visibility changes.
*/
public void setControllerVisibilityListener(ExoVideoPlaybackControlView.VisibilityListener listener) {
Assertions.checkState(controller != null);
controller.setVisibilityListener(listener);
}
/**
* Sets the {@link ControlDispatcher}.
*
* @param controlDispatcher The {@link ControlDispatcher}, or null to use
* {@link PlaybackControlView.DefaultControlDispatcher}.
*/
public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) {
Assertions.checkState(controller != null);
controller.setControlDispatcher(controlDispatcher);
}
/**
* Sets the rewind increment in milliseconds.
*
* @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
* rewind button to be disabled.
*/
public void setRewindIncrementMs(int rewindMs) {
Assertions.checkState(controller != null);
controller.setRewindIncrementMs(rewindMs);
}
/**
* Sets the fast forward increment in milliseconds.
*
* @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
* cause the fast forward button to be disabled.
*/
public void setFastForwardIncrementMs(int fastForwardMs) {
Assertions.checkState(controller != null);
controller.setFastForwardIncrementMs(fastForwardMs);
}
/**
* Sets which repeat toggle modes are enabled.
*
* @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.
*/
public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
Assertions.checkState(controller != null);
controller.setRepeatToggleModes(repeatToggleModes);
}
/**
* Sets whether the shuffle button is shown.
*
* @param showShuffleButton Whether the shuffle button is shown.
*/
public void setShowShuffleButton(boolean showShuffleButton) {
Assertions.checkState(controller != null);
controller.setShowShuffleButton(showShuffleButton);
}
/**
* Sets whether the time bar should show all windows, as opposed to just the current one.
*
* @param showMultiWindowTimeBar Whether to show all windows.
*/
public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
Assertions.checkState(controller != null);
controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar);
}
/**
* Gets the view onto which video is rendered. This is a:
*
* - {@link SurfaceView} by default, or if the {@code surface_type} attribute is set to
* {@code surface_view}.
* - {@link TextureView} if {@code surface_type} is {@code texture_view}.
* - {@code null} if {@code surface_type} is {@code none}.
*
*
* @return The {@link SurfaceView}, {@link TextureView} or {@code null}.
*/
public View getVideoSurfaceView() {
return surfaceView;
}
/**
* Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of
* the player.
*
* @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and
* the overlay is not present.
*/
public FrameLayout getOverlayFrameLayout() {
return overlayFrameLayout;
}
/**
* Gets the {@link SubtitleView}.
*
* @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the
* subtitle view is not present.
*/
public SubtitleView getSubtitleView() {
return subtitleView;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false;
}
if (enableMultiQuality) {
View v = overlayFrameLayout.findViewById(R.id.exo_player_quality_container);
if (v != null && overlayFrameLayout.getVisibility() == VISIBLE) {
return true;
}
}
if (!controller.isVisible()) {
maybeShowController(true);
} else if (controllerHideOnTouch) {
controller.hide();
}
return true;
}
@Override
public boolean onTrackballEvent(MotionEvent ev) {
if (!useController || player == null) {
return false;
}
maybeShowController(true);
return true;
}
/**
* Shows the playback controls, but only if forced or shown indefinitely.
*/
private void maybeShowController(boolean isForced) {
if (isPlayingAd()) {
// Never show the controller if an ad is currently playing.
return;
}
if (useController) {
boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0;
boolean shouldShowIndefinitely = shouldShowControllerIndefinitely();
if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) {
showController(shouldShowIndefinitely);
}
}
}
private boolean shouldShowControllerIndefinitely() {
if (player == null) {
return true;
}
int playbackState = player.getPlaybackState();
return controllerAutoShow && (playbackState == Player.STATE_IDLE
|| playbackState == Player.STATE_ENDED || !player.getPlayWhenReady());
}
private void showController(boolean showIndefinitely) {
if (!useController) {
return;
}
controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs);
controller.show();
}
private boolean isPlayingAd() {
return player != null && player.isPlayingAd() && player.getPlayWhenReady();
}
private void updateForCurrentTrackSelections() {
if (player == null) {
return;
}
TrackSelectionArray selections = player.getCurrentTrackSelections();
for (int i = 0; i < selections.length; i++) {
if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) {
// Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in
// onRenderedFirstFrame().
hideArtwork();
return;
}
}
// Video disabled so the shutter must be closed.
if (shutterView != null) {
shutterView.setVisibility(VISIBLE);
}
// Display artwork if enabled and available, else hide it.
if (useArtwork) {
for (int i = 0; i < selections.length; i++) {
TrackSelection selection = selections.get(i);
if (selection != null) {
for (int j = 0; j < selection.length(); j++) {
Metadata metadata = selection.getFormat(j).metadata;
if (metadata != null && setArtworkFromMetadata(metadata)) {
return;
}
}
}
}
if (setArtworkFromBitmap(defaultArtwork)) {
return;
}
}
// Artwork disabled or unavailable.
hideArtwork();
}
private boolean setArtworkFromMetadata(Metadata metadata) {
for (int i = 0; i < metadata.length(); i++) {
Metadata.Entry metadataEntry = metadata.get(i);
if (metadataEntry instanceof ApicFrame) {
byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData;
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length);
return setArtworkFromBitmap(bitmap);
}
}
return false;
}
private boolean setArtworkFromBitmap(Bitmap bitmap) {
if (bitmap != null) {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
if (bitmapWidth > 0 && bitmapHeight > 0) {
if (contentFrame != null) {
contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight);
}
artworkView.setImageBitmap(bitmap);
artworkView.setVisibility(VISIBLE);
return true;
}
}
return false;
}
private void hideArtwork() {
if (artworkView != null) {
artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference.
artworkView.setVisibility(INVISIBLE);
}
}
@TargetApi(23)
private static void configureEditModeLogoV23(Resources resources, ImageView logo) {
logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null));
logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null));
}
@SuppressWarnings("deprecation")
private static void configureEditModeLogo(Resources resources, ImageView logo) {
logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo));
logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color));
}
@SuppressWarnings("ResourceType")
private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) {
aspectRatioFrame.setResizeMode(resizeMode);
}
@SuppressLint("InlinedApi")
private boolean isDpadKey(int keyCode) {
return keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT
|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT
|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT
|| keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT
|| keyCode == KeyEvent.KEYCODE_DPAD_CENTER;
}
public void play(ExoMediaSource mediaSource) {
play(mediaSource, true, C.TIME_UNSET);
}
public void play(ExoMediaSource mediaSource, boolean playWhenReady) {
play(mediaSource, playWhenReady, C.TIME_UNSET);
}
public void play(ExoMediaSource mediaSource, long where) {
play(mediaSource, true, where);
}
public void play(ExoMediaSource mediaSource, boolean playWhenReady, long where) {
releasePlayer();
MediaSourceCreator creator = new MediaSourceCreator(getContext().getApplicationContext());
createExoPlayer(creator);
if (controller != null) {
controller.setMediaSource(mediaSource);
}
if (enableMultiQuality && mediaSource.qualities() != null && !mediaSource.qualities().isEmpty()) {
addMultiQualitySelector(mediaSource);
}
playInternal(mediaSource, playWhenReady, where, creator);
}
public void pause() {
if (player == null) {
return;
}
if (player.getPlayWhenReady()) {
lastPlayedPosition = player.getCurrentPosition();
player.setPlayWhenReady(false);
pausedFromPlayer = false;
} else {
pausedFromPlayer = true;
}
}
public void resume() {
if (player == null) {
return;
}
if (player.getPlayWhenReady()) {
return;
}
if (!pausedFromPlayer) {
player.seekTo(lastPlayedPosition - 500 < 0 ? 0 : lastPlayedPosition - 500);
player.setPlayWhenReady(true);
}
}
public void stop() {
if (player != null) {
player.stop();
}
}
private void playInternal(ExoMediaSource mediaSource, boolean playWhenReady, long where, MediaSourceCreator creator) {
MediaSource tmp = creator.buildMediaSource(mediaSource.uri(), mediaSource.extension());
player.prepare(tmp);
if (where == C.TIME_UNSET) {
player.seekTo(0L);
} else {
player.seekTo(where);
}
player.setPlayWhenReady(requestAudioFocus() && playWhenReady);
}
private void createExoPlayer(MediaSourceCreator creator) {
if (player != null) {
releasePlayer();
}
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext());
renderersFactory.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
SimpleExoPlayer.Builder builder = new SimpleExoPlayer.Builder(getContext(), renderersFactory);
TrackSelection.Factory adaptiveTrackSelectionFactory =
new AdaptiveTrackSelection.Factory();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(getContext(),new AdaptiveTrackSelection.Factory());
builder.setTrackSelector(new DefaultTrackSelector(getContext(),adaptiveTrackSelectionFactory));
builder.setTrackSelector(trackSelector);
builder.setBandwidthMeter(new DefaultBandwidthMeter.Builder(getContext()).build());
SimpleExoPlayer internalPlayer = builder.build();
internalPlayer.addListener(componentListener);
internalPlayer.addListener(creator.getEventLogger());
internalPlayer.addMetadataOutput(creator.getEventLogger());
setPlayer(internalPlayer);
}
public void releasePlayer() {
if (player != null) {
player.release();
}
player = null;
}
private boolean requestAudioFocus() {
if (audioManager == null) {
return true;
}
// Request audio focus for playback
int result = audioManager.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
public void setBackListener(ExoVideoPlaybackControlView.ExoClickListener backListener) {
if (controller != null) {
controller.setBackListener(backListener);
}
}
public void setOrientationListener(ExoVideoPlaybackControlView.OrientationListener orientationListener) {
if (controller != null) {
controller.setOrientationListener(orientationListener);
}
}
public void setMultiQualitySelectorNavigator(MultiQualitySelectorAdapter.MultiQualitySelectorNavigator navigator) {
this.multiQualitySelectorNavigator = navigator;
}
public boolean isPortrait() {
return controller != null && controller.isPortrait();
}
public void setPortrait(boolean portrait) {
if (controller != null) {
controller.setPortrait(portrait);
}
}
public void changeWidgetVisibility(int id, int visibility) {
if (controller != null) {
controller.changeWidgetVisibility(id, visibility);
}
}
public void setControllerDisplayMode(int displayMode) {
if (controller != null) {
controller.setControllerDisplayMode(displayMode);
}
}
private void addMultiQualitySelector(ExoMediaSource mediaSource) {
for (ExoMediaSource.Quality quality : mediaSource.qualities()) {
if (TextUtils.equals(quality.getUri().toString(), mediaSource.uri().toString())) {
if (controller != null) {
controller.updateQualityDes(quality.getDisplayName());
}
break;
}
}
if (controller != null) {
controller.setVisibilityCallback(visibility -> {
overlayFrameLayout.setVisibility(visibility);
if (visibility != VISIBLE) {
controller.show();
}
});
}
MultiQualitySelectorAdapter adapter = new MultiQualitySelectorAdapter(mediaSource.qualities(), quality -> {
if (player == null) {
return false;
}
if (multiQualitySelectorNavigator == null || !multiQualitySelectorNavigator.onQualitySelected(quality)) {
long current = player.getCurrentPosition();
boolean playWhenReady = player.getPlayWhenReady();
MediaSourceCreator creator = new MediaSourceCreator(getContext().getApplicationContext());
MediaSource tmp = creator.buildMediaSource(quality.getUri(), null);
player.setPlayWhenReady(requestAudioFocus() && playWhenReady);
player.prepare(tmp);
player.seekTo(current);
}
if (controller != null) {
controller.updateQualityDes(quality.getDisplayName());
}
overlayFrameLayout.setVisibility(GONE);
return false;
});
overlayFrameLayout.removeAllViews();
View view = LayoutInflater.from(getContext()).inflate(R.layout.exo_player_quality_selector, null, false);
overlayFrameLayout.setVisibility(View.GONE);
RecyclerView container = view.findViewById(R.id.exo_player_quality_container);
container.setAdapter(adapter);
container.setLayoutManager(new LinearLayoutManager(getContext()));
view.setOnClickListener(v -> overlayFrameLayout.setVisibility(GONE));
View containerWrapper = view.findViewById(R.id.containerWrapper);
if (controllerBackgroundId != 0) {
containerWrapper.setBackgroundResource(controllerBackgroundId);
}
overlayFrameLayout.addView(view);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (controller != null) {
return controller.onKeyDown(keyCode, event);
}
return super.onKeyDown(keyCode, event);
}
@Override
public View attachVideoView() {
return this;
}
/**
* add your view to controller
*
* @param customViewType the target view type
* @param customView the view you want to add
* @param removeViews remove all views in target view before add if true
**/
public void addCustomView(@ExoVideoPlaybackControlView.CustomViewType int customViewType, View customView, boolean removeViews) {
if (useController && controller != null) {
controller.addCustomView(customViewType, customView, removeViews);
}
}
public void addCustomView(@ExoVideoPlaybackControlView.CustomViewType int customViewType, View customView) {
addCustomView(customViewType, customView, false);
}
public void setGestureEnabled(boolean enabled) {
if (controller != null) {
controller.setGestureEnabled(enabled);
}
}
private final class ComponentListener implements TextOutput, Player.EventListener,
com.google.android.exoplayer2.video.VideoListener {
// TextOutput implementation
@Override
public void onCues(List cues) {
if (subtitleView != null) {
subtitleView.onCues(cues);
}
}
// SimpleExoPlayer.VideoInfoListener implementation
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
if (contentFrame != null) {
float aspectRatio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height;
contentFrame.setAspectRatio(aspectRatio);
}
}
@Override
public void onRenderedFirstFrame() {
if (shutterView != null) {
shutterView.setVisibility(INVISIBLE);
}
}
@Override
public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) {
updateForCurrentTrackSelections();
}
// Player.EventListener implementation
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (isPlayingAd()) {
hideController();
} else {
maybeShowController(false);
}
}
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
if (isPlayingAd()) {
hideController();
}
}
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/util/AndroidUtil.java
================================================
/*****************************************************************************
* AndroidUtil.java
*****************************************************************************
* Copyright © 2015 VLC authors, VideoLAN and VideoLabs
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
package com.jarvanmo.exoplayerview.util;
import android.net.Uri;
import android.os.Build;
import java.io.File;
public class AndroidUtil {
public static boolean isJellyBeanMR1OrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
public static boolean isJellyBeanMR2OrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
}
public static boolean isKitKatOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
public static boolean isLolliPopOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static boolean isMarshMallowOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
public static File UriToFile(Uri uri) {
return new File(uri.getPath().replaceFirst("file://", ""));
}
/**
* Quickly converts path to URIs, which are mandatory in libVLC.
*
* @param path The path to be converted.
* @return A URI representation of path
*/
public static Uri PathToUri(String path) {
return Uri.fromFile(new File(path));
}
public static Uri LocationToUri(String location) {
Uri uri = Uri.parse(location);
if (uri.getScheme() == null)
throw new IllegalArgumentException("location has no scheme");
return uri;
}
public static Uri FileToUri(File file) {
return Uri.fromFile(file);
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/util/Permissions.java
================================================
/*
* *************************************************************************
* Permissions.java
* **************************************************************************
* Copyright © 2015 VLC authors and VideoLAN
* Author: Geoffrey Métais
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* ***************************************************************************
*/
package com.jarvanmo.exoplayerview.util;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class Permissions {
public static final int PERMISSION_STORAGE_TAG = 255;
public static final int PERMISSION_SETTINGS_TAG = 254;
public static final int PERMISSION_SYSTEM_RINGTONE = 42;
public static final int PERMISSION_SYSTEM_BRIGHTNESS = 43;
public static final int PERMISSION_SYSTEM_DRAW_OVRLAYS = 44;
/*
* Marshmallow permission system management
*/
@TargetApi(Build.VERSION_CODES.M)
public static boolean canDrawOverlays(Context context) {
return !AndroidUtil.isMarshMallowOrLater() || Settings.canDrawOverlays(context);
}
@TargetApi(Build.VERSION_CODES.M)
public static boolean canWriteSettings(Context context) {
return !AndroidUtil.isMarshMallowOrLater() || Settings.System.canWrite(context);
}
public static boolean canReadStorage(Context context) {
return !AndroidUtil.isMarshMallowOrLater() || ContextCompat.checkSelfPermission(context,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
private static void requestStoragePermission(Activity activity) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_STORAGE_TAG);
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/widget/BatteryLevelView.java
================================================
package com.jarvanmo.exoplayerview.widget;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.BatteryManager;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by mo on 17-4-19.
* Copyright © 2017, cnyanglao, Co,. Ltd. All Rights Reserve
*/
public class BatteryLevelView extends View {
/**
* 画笔信息
*/
private Paint mBatteryPaint;
private Paint mPowerPaint;
private float mBatteryStroke = 2f;
/**
* 屏幕高宽
*/
private int measureWidth;
private int measureHeight;
/**
* 电池参数
*/
private float mBatteryHeight = 30f; // 电池的高度
private float mBatteryWidth = 55f; // 电池的宽度
private float mCapHeight = 15f;
private float mCapWidth = 5f;
/**
* 电池电量
*/
private float mPowerPadding = 1;
private float mPowerHeight = mBatteryHeight - mBatteryStroke
- mPowerPadding * 2; // 电池身体的高度
private float mPowerWidth = mBatteryWidth - mBatteryStroke - mPowerPadding
* 2;// 电池身体的总宽度
private float mPower = 10f;
/**
* 矩形
*/
private RectF mBatteryRect;
private RectF mCapRect;
private RectF mPowerRect;
private boolean mIsCharging = false;
public BatteryLevelView(Context context) {
this(context, null);
}
public BatteryLevelView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BatteryLevelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public void initView() {
mBatteryPaint = new Paint();// 设置电池画笔
mBatteryPaint.setColor(Color.GRAY);
mBatteryPaint.setAntiAlias(true);
mBatteryPaint.setStyle(Paint.Style.STROKE);
mBatteryPaint.setStrokeWidth(mBatteryStroke);
mPowerPaint = new Paint();//设置电量画笔
mPowerPaint.setColor(getPowerColor());
mPowerPaint.setAntiAlias(true);
mPowerPaint.setStyle(Paint.Style.FILL);
mPowerPaint.setStrokeWidth(mBatteryStroke);
mBatteryRect = new RectF(mCapWidth, 0, mBatteryWidth, mBatteryHeight);//设置电池矩形
mCapRect = new RectF(0, (mBatteryHeight - mCapHeight) / 2, mCapWidth,
(mBatteryHeight - mCapHeight) / 2 + mCapHeight);//设置电池盖矩形
mPowerRect = new RectF(mCapWidth + mBatteryStroke / 2 + mPowerPadding
+ mPowerWidth * ((100f - mPower) / 100f), // 需要调整左边的位置
mPowerPadding + mBatteryStroke / 2, // 需要考虑到 画笔的宽度
mBatteryWidth - mPowerPadding * 2, mBatteryStroke / 2
+ mPowerPadding + mPowerHeight);// 设置电量矩形
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(measureWidth / 2, measureHeight / 2);
canvas.drawRoundRect(mBatteryRect, 2f, 2f, mBatteryPaint); // 画电池轮廓需要考虑 画笔的宽度
canvas.drawRoundRect(mCapRect, 2f, 2f, mBatteryPaint);// 画电池盖
canvas.drawRect(mPowerRect, mPowerPaint);// 画电量
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureWidth = MeasureSpec.getSize(widthMeasureSpec);
measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
}
public void setPower(float power) {
mPower = power;
if (mPower < 0) {
mPower = 0;
}
mPowerPaint.setColor(getPowerColor());
mPowerRect = new RectF(mCapWidth + mBatteryStroke / 2 + mPowerPadding
+ mPowerWidth * ((100f - mPower) / 100f), // 需要调整左边的位置
mPowerPadding + mBatteryStroke / 2, // 需要考虑到 画笔的宽度
mBatteryWidth - mPowerPadding * 2, mBatteryStroke / 2
+ mPowerPadding + mPowerHeight);
invalidate();
}
private int getPowerColor() {
if (mIsCharging) {
return Color.GREEN;
}
if (mPower <= 15) {
return Color.RED;
} else if (mPower <= 30) {
return Color.YELLOW;
} else {
return Color.WHITE;
}
}
private BroadcastReceiver mPowerConnectionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
mIsCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float percent = ((float) (level * 100)) / scale;
setPower(percent);
}
};
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getContext().registerReceiver(mPowerConnectionReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getContext().unregisterReceiver(mPowerConnectionReceiver);
}
}
================================================
FILE: exoplayerview/src/main/java/com/jarvanmo/exoplayerview/widget/BatteryStatusView.java
================================================
package com.jarvanmo.exoplayerview.widget;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
import com.jarvanmo.exoplayerview.R;
/**
* Created by mo on 17-4-19.
* Copyright © 2017, cnyanglao, Co,. Ltd. All Rights Reserve
*/
public class BatteryStatusView extends AppCompatImageView {
public BatteryStatusView(Context context) {
this(context, null);
}
public BatteryStatusView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BatteryStatusView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setImageResource(R.drawable.stat_sys_battery);
setImageLevel(100);
}
private BroadcastReceiver mPowerConnectionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
if (isCharging) {
setImageResource(R.drawable.stat_sys_battery_charge);
} else {
setImageResource(R.drawable.stat_sys_battery);
}
setImageLevel(level);
}
};
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getContext().registerReceiver(mPowerConnectionReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getContext().unregisterReceiver(mPowerConnectionReceiver);
}
}
================================================
FILE: exoplayerview/src/main/res/drawable/ic_arrow_back_white_24dp.xml
================================================
================================================
FILE: exoplayerview/src/main/res/drawable/ic_error_outline_white_48dp.xml
================================================
================================================
FILE: exoplayerview/src/main/res/drawable/stat_sys_battery.xml
================================================
================================================
FILE: exoplayerview/src/main/res/drawable/stat_sys_battery_charge.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_playback_controller_bottom.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_playback_controller_bottom_landscape.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_playback_controller_top.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_playback_controller_top_landscape.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_player_quality_selector.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_video_playback_control_view.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/exo_video_view.xml
================================================
================================================
FILE: exoplayerview/src/main/res/layout/item_quality.xml
================================================
================================================
FILE: exoplayerview/src/main/res/values/attrs.xml
================================================
================================================
FILE: exoplayerview/src/main/res/values/colors.xml
================================================
#3D000000
#4DFFFFFF
#FFFFFFFF
#FFFFFFFF
#99000000
#3d000000
#7a000000
#ffffff
#4dffffff
#ffffff
================================================
FILE: exoplayerview/src/main/res/values/dimens.xml
================================================
0dp
15sp
10dp
18sp
================================================
FILE: exoplayerview/src/main/res/values/ids.xml
================================================
================================================
FILE: exoplayerview/src/main/res/values/strings.xml
================================================
ExoPlayerView
00:00
00:00/00:00
AM
PM
battery:%1$d%%
volume:%1$d%%
brightness:%1$d%%
player error:%1$d
================================================
FILE: exoplayerview/src/main/res/values-zh-rCN/strings.xml
================================================
ExoPlayerView
上午
下午
电量:%1$d%%
音量:%1$d%%
亮度:%1$d%%
播放错误:%1$d
================================================
FILE: exoplayerview/src/test/java/com/jarvanmo/exoplayerview/ExampleUnitTest.java
================================================
package com.jarvanmo.exoplayerview;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see Testing documentation
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Thu Jul 23 09:49:01 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':demo'
include ':exoplayerview'