2017-09-03 08:04:18 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
|
|
|
* VideoPlayer.java is part of NewPipe
|
|
|
|
*
|
|
|
|
* License: GPL-3.0+
|
|
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
package org.schabi.newpipe.player;
|
|
|
|
|
|
|
|
import android.animation.Animator;
|
|
|
|
import android.animation.AnimatorListenerAdapter;
|
|
|
|
import android.animation.ObjectAnimator;
|
|
|
|
import android.animation.PropertyValuesHolder;
|
|
|
|
import android.animation.ValueAnimator;
|
|
|
|
import android.content.Context;
|
2017-10-25 06:47:14 +02:00
|
|
|
import android.content.Intent;
|
2017-03-27 06:08:16 +02:00
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.Color;
|
|
|
|
import android.graphics.PorterDuff;
|
2018-02-03 23:39:03 +01:00
|
|
|
import android.net.Uri;
|
2017-03-27 06:08:16 +02:00
|
|
|
import android.os.Build;
|
2017-05-08 15:33:26 +02:00
|
|
|
import android.os.Handler;
|
2018-02-09 22:26:03 +01:00
|
|
|
import android.preference.PreferenceManager;
|
2017-10-10 02:25:45 +02:00
|
|
|
import android.support.annotation.NonNull;
|
2017-09-24 22:44:31 +02:00
|
|
|
import android.support.annotation.Nullable;
|
2017-03-27 06:08:16 +02:00
|
|
|
import android.support.v4.content.ContextCompat;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuItem;
|
|
|
|
import android.view.SurfaceView;
|
|
|
|
import android.view.View;
|
|
|
|
import android.widget.ImageView;
|
|
|
|
import android.widget.PopupMenu;
|
|
|
|
import android.widget.ProgressBar;
|
|
|
|
import android.widget.SeekBar;
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
2017-09-24 06:50:32 +02:00
|
|
|
import com.google.android.exoplayer2.C;
|
2018-02-03 23:39:03 +01:00
|
|
|
import com.google.android.exoplayer2.Format;
|
2017-08-18 20:07:57 +02:00
|
|
|
import com.google.android.exoplayer2.Player;
|
2017-03-27 06:08:16 +02:00
|
|
|
import com.google.android.exoplayer2.source.MediaSource;
|
2017-04-12 08:07:15 +02:00
|
|
|
import com.google.android.exoplayer2.source.MergingMediaSource;
|
2018-02-09 21:43:24 +01:00
|
|
|
import com.google.android.exoplayer2.source.TrackGroup;
|
2018-02-03 23:39:03 +01:00
|
|
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
|
|
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
2017-03-27 06:08:16 +02:00
|
|
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
2018-02-03 23:39:03 +01:00
|
|
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
2018-02-27 04:57:59 +01:00
|
|
|
import com.google.android.exoplayer2.video.VideoListener;
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
import org.schabi.newpipe.R;
|
|
|
|
import org.schabi.newpipe.extractor.MediaFormat;
|
2018-02-03 23:39:03 +01:00
|
|
|
import org.schabi.newpipe.extractor.Subtitles;
|
2017-10-15 07:54:25 +02:00
|
|
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
2017-09-01 21:10:36 +02:00
|
|
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
2018-02-26 00:10:11 +01:00
|
|
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
2017-09-05 21:27:12 +02:00
|
|
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
2018-02-03 23:39:03 +01:00
|
|
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
2017-10-10 02:25:45 +02:00
|
|
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
2017-05-08 15:33:26 +02:00
|
|
|
import org.schabi.newpipe.util.AnimationUtils;
|
2017-09-04 19:23:56 +02:00
|
|
|
import org.schabi.newpipe.util.ListHelper;
|
2017-03-27 06:08:16 +02:00
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
|
|
|
|
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
2017-10-28 19:08:01 +02:00
|
|
|
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
|
|
|
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
2017-05-08 15:33:26 +02:00
|
|
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
/**
|
2017-04-17 06:19:53 +02:00
|
|
|
* Base for <b>video</b> players
|
2017-03-27 06:08:16 +02:00
|
|
|
*
|
|
|
|
* @author mauriciocolli
|
|
|
|
*/
|
2017-04-17 06:19:53 +02:00
|
|
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
2017-12-23 17:34:47 +01:00
|
|
|
public abstract class VideoPlayer extends BasePlayer
|
2018-02-27 04:57:59 +01:00
|
|
|
implements VideoListener,
|
2017-12-23 17:34:47 +01:00
|
|
|
SeekBar.OnSeekBarChangeListener,
|
|
|
|
View.OnClickListener,
|
|
|
|
Player.EventListener,
|
|
|
|
PopupMenu.OnMenuItemClickListener,
|
|
|
|
PopupMenu.OnDismissListener {
|
2017-04-17 06:19:53 +02:00
|
|
|
public static final boolean DEBUG = BasePlayer.DEBUG;
|
2017-03-27 06:08:16 +02:00
|
|
|
public final String TAG;
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Player
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2018-02-09 21:43:24 +01:00
|
|
|
protected static final int RENDERER_UNAVAILABLE = -1;
|
2018-03-04 05:58:53 +01:00
|
|
|
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
2017-09-03 08:04:18 +02:00
|
|
|
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
private ArrayList<VideoStream> availableStreams;
|
|
|
|
private int selectedStreamIndex;
|
|
|
|
|
|
|
|
protected String playbackQuality;
|
|
|
|
|
2017-09-20 06:46:16 +02:00
|
|
|
protected boolean wasPlaying = false;
|
2017-03-27 06:08:16 +02:00
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Views
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
private View rootView;
|
|
|
|
|
|
|
|
private AspectRatioFrameLayout aspectRatioFrameLayout;
|
|
|
|
private SurfaceView surfaceView;
|
|
|
|
private View surfaceForeground;
|
|
|
|
|
|
|
|
private View loadingPanel;
|
|
|
|
private ImageView endScreen;
|
|
|
|
private ImageView controlAnimationView;
|
|
|
|
|
|
|
|
private View controlsRoot;
|
|
|
|
private TextView currentDisplaySeek;
|
|
|
|
|
|
|
|
private View bottomControlsRoot;
|
|
|
|
private SeekBar playbackSeekBar;
|
|
|
|
private TextView playbackCurrentTime;
|
|
|
|
private TextView playbackEndTime;
|
2018-02-27 04:57:59 +01:00
|
|
|
private TextView playbackLiveSync;
|
2017-10-23 03:58:01 +02:00
|
|
|
private TextView playbackSpeedTextView;
|
2017-03-27 06:08:16 +02:00
|
|
|
|
|
|
|
private View topControlsRoot;
|
|
|
|
private TextView qualityTextView;
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
private SubtitleView subtitleView;
|
|
|
|
|
|
|
|
private TextView resizeView;
|
|
|
|
private TextView captionTextView;
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
private ValueAnimator controlViewAnimator;
|
2017-05-08 15:33:26 +02:00
|
|
|
private Handler controlsVisibilityHandler = new Handler();
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-11-09 11:29:02 +01:00
|
|
|
boolean isSomePopupMenuVisible = false;
|
2017-03-27 06:08:16 +02:00
|
|
|
private int qualityPopupMenuGroupId = 69;
|
|
|
|
private PopupMenu qualityPopupMenu;
|
|
|
|
|
2017-06-26 04:41:52 +02:00
|
|
|
private int playbackSpeedPopupMenuGroupId = 79;
|
|
|
|
private PopupMenu playbackSpeedPopupMenu;
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
private int captionPopupMenuGroupId = 89;
|
|
|
|
private PopupMenu captionPopupMenu;
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
public VideoPlayer(String debugTag, Context context) {
|
|
|
|
super(context);
|
2017-03-27 06:08:16 +02:00
|
|
|
this.TAG = debugTag;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setup(View rootView) {
|
|
|
|
initViews(rootView);
|
2017-04-17 06:19:53 +02:00
|
|
|
setup();
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void initViews(View rootView) {
|
|
|
|
this.rootView = rootView;
|
2017-09-03 08:04:18 +02:00
|
|
|
this.aspectRatioFrameLayout = rootView.findViewById(R.id.aspectRatioLayout);
|
|
|
|
this.surfaceView = rootView.findViewById(R.id.surfaceView);
|
2017-03-27 06:08:16 +02:00
|
|
|
this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground);
|
2017-04-26 21:32:20 +02:00
|
|
|
this.loadingPanel = rootView.findViewById(R.id.loading_panel);
|
2017-09-03 08:04:18 +02:00
|
|
|
this.endScreen = rootView.findViewById(R.id.endScreen);
|
|
|
|
this.controlAnimationView = rootView.findViewById(R.id.controlAnimationView);
|
2017-03-27 06:08:16 +02:00
|
|
|
this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot);
|
2017-09-03 08:04:18 +02:00
|
|
|
this.currentDisplaySeek = rootView.findViewById(R.id.currentDisplaySeek);
|
|
|
|
this.playbackSeekBar = rootView.findViewById(R.id.playbackSeekBar);
|
|
|
|
this.playbackCurrentTime = rootView.findViewById(R.id.playbackCurrentTime);
|
|
|
|
this.playbackEndTime = rootView.findViewById(R.id.playbackEndTime);
|
2018-02-27 04:57:59 +01:00
|
|
|
this.playbackLiveSync = rootView.findViewById(R.id.playbackLiveSync);
|
2017-10-23 03:58:01 +02:00
|
|
|
this.playbackSpeedTextView = rootView.findViewById(R.id.playbackSpeed);
|
2017-03-27 06:08:16 +02:00
|
|
|
this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
|
|
|
|
this.topControlsRoot = rootView.findViewById(R.id.topControls);
|
2017-09-03 08:04:18 +02:00
|
|
|
this.qualityTextView = rootView.findViewById(R.id.qualityTextView);
|
2018-02-09 22:26:03 +01:00
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
this.subtitleView = rootView.findViewById(R.id.subtitleView);
|
2018-02-09 22:26:03 +01:00
|
|
|
final String captionSizeKey = PreferenceManager.getDefaultSharedPreferences(context)
|
|
|
|
.getString(context.getString(R.string.caption_size_key),
|
|
|
|
context.getString(R.string.caption_size_default));
|
|
|
|
setupSubtitleView(subtitleView, captionSizeKey);
|
2018-02-03 23:39:03 +01:00
|
|
|
|
|
|
|
this.resizeView = rootView.findViewById(R.id.resizeTextView);
|
|
|
|
resizeView.setText(PlayerHelper.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
|
|
|
|
|
|
|
|
this.captionTextView = rootView.findViewById(R.id.captionTextView);
|
2017-03-27 06:08:16 +02:00
|
|
|
|
|
|
|
//this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
|
|
|
|
|
2017-09-03 08:04:18 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
|
|
|
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
2017-03-27 06:08:16 +02:00
|
|
|
this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
|
|
|
|
|
2017-03-27 15:12:22 +02:00
|
|
|
this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
|
2017-10-23 03:58:01 +02:00
|
|
|
this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView);
|
2018-02-03 23:39:03 +01:00
|
|
|
this.captionPopupMenu = new PopupMenu(context, captionTextView);
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel))
|
|
|
|
.getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2018-02-09 22:26:03 +01:00
|
|
|
protected abstract void setupSubtitleView(@NonNull SubtitleView view,
|
|
|
|
@NonNull String captionSizeKey);
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
2017-03-27 06:08:16 +02:00
|
|
|
public void initListeners() {
|
2017-04-17 06:19:53 +02:00
|
|
|
super.initListeners();
|
2017-03-27 06:08:16 +02:00
|
|
|
playbackSeekBar.setOnSeekBarChangeListener(this);
|
2017-10-23 03:58:01 +02:00
|
|
|
playbackSpeedTextView.setOnClickListener(this);
|
2017-03-27 06:08:16 +02:00
|
|
|
qualityTextView.setOnClickListener(this);
|
2018-02-03 23:39:03 +01:00
|
|
|
captionTextView.setOnClickListener(this);
|
|
|
|
resizeView.setOnClickListener(this);
|
2018-02-27 04:57:59 +01:00
|
|
|
playbackLiveSync.setOnClickListener(this);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
2017-03-27 06:08:16 +02:00
|
|
|
public void initPlayer() {
|
2017-04-17 06:19:53 +02:00
|
|
|
super.initPlayer();
|
2018-02-03 23:39:03 +01:00
|
|
|
|
|
|
|
// Setup video view
|
2017-03-27 06:08:16 +02:00
|
|
|
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
2017-10-03 08:38:46 +02:00
|
|
|
simpleExoPlayer.addVideoListener(this);
|
2017-09-24 06:50:32 +02:00
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
// Setup subtitle view
|
|
|
|
simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues));
|
|
|
|
|
|
|
|
// Setup audio session with onboard equalizer
|
2017-10-13 02:02:07 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= 21) {
|
|
|
|
trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context));
|
|
|
|
}
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
@Override
|
|
|
|
public void handleIntent(final Intent intent) {
|
|
|
|
if (intent == null) return;
|
|
|
|
|
|
|
|
if (intent.hasExtra(PLAYBACK_QUALITY)) {
|
|
|
|
setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
|
|
|
|
}
|
|
|
|
|
|
|
|
super.handleIntent(intent);
|
|
|
|
}
|
|
|
|
|
2017-10-03 08:38:46 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// UI Builders
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-10-23 03:58:01 +02:00
|
|
|
public void buildQualityMenu() {
|
|
|
|
if (qualityPopupMenu == null) return;
|
2017-10-03 08:38:46 +02:00
|
|
|
|
|
|
|
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
2017-10-23 03:58:01 +02:00
|
|
|
for (int i = 0; i < availableStreams.size(); i++) {
|
|
|
|
VideoStream videoStream = availableStreams.get(i);
|
2018-02-27 04:57:59 +01:00
|
|
|
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE,
|
|
|
|
MediaFormat.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
|
2017-10-03 08:38:46 +02:00
|
|
|
}
|
2018-02-18 06:01:02 +01:00
|
|
|
if (getSelectedVideoStream() != null) {
|
|
|
|
qualityTextView.setText(getSelectedVideoStream().resolution);
|
|
|
|
}
|
2017-10-03 08:38:46 +02:00
|
|
|
qualityPopupMenu.setOnMenuItemClickListener(this);
|
|
|
|
qualityPopupMenu.setOnDismissListener(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void buildPlaybackSpeedMenu() {
|
|
|
|
if (playbackSpeedPopupMenu == null) return;
|
|
|
|
|
|
|
|
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
|
|
|
|
for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
|
|
|
|
playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i]));
|
|
|
|
}
|
2017-10-23 03:58:01 +02:00
|
|
|
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
2017-10-03 08:38:46 +02:00
|
|
|
playbackSpeedPopupMenu.setOnMenuItemClickListener(this);
|
|
|
|
playbackSpeedPopupMenu.setOnDismissListener(this);
|
|
|
|
}
|
|
|
|
|
2018-02-09 21:43:24 +01:00
|
|
|
private void buildCaptionMenu(final List<String> availableLanguages) {
|
2018-02-03 23:39:03 +01:00
|
|
|
if (captionPopupMenu == null) return;
|
|
|
|
captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
|
|
|
|
|
2018-02-07 22:11:19 +01:00
|
|
|
// Add option for turning off caption
|
2018-02-03 23:39:03 +01:00
|
|
|
MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
2018-02-07 22:11:19 +01:00
|
|
|
0, Menu.NONE, R.string.caption_none);
|
2018-02-03 23:39:03 +01:00
|
|
|
captionOffItem.setOnMenuItemClickListener(menuItem -> {
|
|
|
|
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
2018-02-09 21:43:24 +01:00
|
|
|
if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
|
2018-02-03 23:39:03 +01:00
|
|
|
trackSelector.setRendererDisabled(textRendererIndex, true);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2018-02-07 22:11:19 +01:00
|
|
|
// Add all available captions
|
2018-02-09 21:43:24 +01:00
|
|
|
for (int i = 0; i < availableLanguages.size(); i++) {
|
|
|
|
final String captionLanguage = availableLanguages.get(i);
|
2018-02-03 23:39:03 +01:00
|
|
|
MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
|
|
|
|
i + 1, Menu.NONE, captionLanguage);
|
|
|
|
captionItem.setOnMenuItemClickListener(menuItem -> {
|
|
|
|
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
2018-02-09 21:43:24 +01:00
|
|
|
if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
|
2018-02-26 00:10:11 +01:00
|
|
|
trackSelector.setPreferredTextLanguage(captionLanguage);
|
2018-02-09 21:43:24 +01:00
|
|
|
trackSelector.setRendererDisabled(textRendererIndex, false);
|
2018-02-03 23:39:03 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
captionPopupMenu.setOnDismissListener(this);
|
|
|
|
}
|
2017-09-25 05:14:58 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Playback Listener
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-10-23 04:21:19 +02:00
|
|
|
protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
|
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
|
|
|
|
|
2018-03-03 23:24:21 +01:00
|
|
|
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
|
|
|
@Nullable final StreamInfo info,
|
|
|
|
final int newPlayQueueIndex,
|
|
|
|
final boolean hasPlayQueueItemChanged) {
|
2017-10-23 03:58:01 +02:00
|
|
|
qualityTextView.setVisibility(View.GONE);
|
|
|
|
playbackSpeedTextView.setVisibility(View.GONE);
|
2017-09-05 00:38:58 +02:00
|
|
|
|
2018-02-27 04:57:59 +01:00
|
|
|
playbackEndTime.setVisibility(View.GONE);
|
|
|
|
playbackLiveSync.setVisibility(View.GONE);
|
|
|
|
|
2018-02-26 00:10:11 +01:00
|
|
|
final StreamType streamType = info == null ? StreamType.NONE : info.getStreamType();
|
2017-09-03 04:30:34 +02:00
|
|
|
|
2018-02-26 00:10:11 +01:00
|
|
|
switch (streamType) {
|
2018-02-27 04:57:59 +01:00
|
|
|
case AUDIO_STREAM:
|
|
|
|
surfaceView.setVisibility(View.GONE);
|
2018-03-01 02:45:05 +01:00
|
|
|
playbackEndTime.setVisibility(View.VISIBLE);
|
2018-02-27 04:57:59 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case AUDIO_LIVE_STREAM:
|
|
|
|
surfaceView.setVisibility(View.GONE);
|
2018-03-01 02:45:05 +01:00
|
|
|
playbackLiveSync.setVisibility(View.VISIBLE);
|
|
|
|
break;
|
|
|
|
|
2018-02-27 04:57:59 +01:00
|
|
|
case LIVE_STREAM:
|
2018-03-01 02:45:05 +01:00
|
|
|
surfaceView.setVisibility(View.VISIBLE);
|
2018-02-27 04:57:59 +01:00
|
|
|
playbackLiveSync.setVisibility(View.VISIBLE);
|
|
|
|
break;
|
|
|
|
|
2018-02-26 00:10:11 +01:00
|
|
|
case VIDEO_STREAM:
|
|
|
|
if (info.video_streams.size() + info.video_only_streams.size() == 0) break;
|
|
|
|
|
|
|
|
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
|
|
|
info.video_streams, info.video_only_streams, false);
|
|
|
|
availableStreams = new ArrayList<>(videos);
|
|
|
|
if (playbackQuality == null) {
|
|
|
|
selectedStreamIndex = getDefaultResolutionIndex(videos);
|
|
|
|
} else {
|
|
|
|
selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
|
|
|
}
|
|
|
|
|
|
|
|
buildQualityMenu();
|
|
|
|
qualityTextView.setVisibility(View.VISIBLE);
|
|
|
|
|
2018-02-27 04:57:59 +01:00
|
|
|
surfaceView.setVisibility(View.VISIBLE);
|
2018-02-26 00:10:11 +01:00
|
|
|
default:
|
2018-02-27 04:57:59 +01:00
|
|
|
playbackEndTime.setVisibility(View.VISIBLE);
|
2018-02-26 00:10:11 +01:00
|
|
|
break;
|
2017-10-23 03:58:01 +02:00
|
|
|
}
|
2018-02-18 06:01:02 +01:00
|
|
|
|
|
|
|
buildPlaybackSpeedMenu();
|
|
|
|
playbackSpeedTextView.setVisibility(View.VISIBLE);
|
2017-09-03 04:30:34 +02:00
|
|
|
}
|
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
@Override
|
2017-10-29 20:39:53 +01:00
|
|
|
@Nullable
|
2017-10-23 03:58:01 +02:00
|
|
|
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
2018-02-26 00:10:11 +01:00
|
|
|
final MediaSource liveSource = super.sourceOf(item, info);
|
|
|
|
if (liveSource != null) return liveSource;
|
|
|
|
|
2018-02-18 06:01:02 +01:00
|
|
|
List<MediaSource> mediaSources = new ArrayList<>();
|
2017-10-23 03:58:01 +02:00
|
|
|
|
2018-02-18 06:01:02 +01:00
|
|
|
// Create video stream source
|
|
|
|
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
|
|
|
info.video_streams, info.video_only_streams, false);
|
2017-10-29 20:39:53 +01:00
|
|
|
final int index;
|
2018-02-18 06:01:02 +01:00
|
|
|
if (videos.isEmpty()) {
|
|
|
|
index = -1;
|
|
|
|
} else if (playbackQuality == null) {
|
2017-10-29 20:39:53 +01:00
|
|
|
index = getDefaultResolutionIndex(videos);
|
2017-10-23 03:58:01 +02:00
|
|
|
} else {
|
2017-10-29 20:39:53 +01:00
|
|
|
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
2017-09-05 21:27:12 +02:00
|
|
|
}
|
2018-02-18 06:01:02 +01:00
|
|
|
final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null;
|
|
|
|
if (video != null) {
|
|
|
|
final MediaSource streamSource = buildMediaSource(video.getUrl(),
|
2018-02-27 04:57:59 +01:00
|
|
|
PlayerHelper.cacheKeyOf(info, video),
|
2018-02-18 06:01:02 +01:00
|
|
|
MediaFormat.getSuffixById(video.getFormatId()));
|
|
|
|
mediaSources.add(streamSource);
|
|
|
|
}
|
2018-02-03 23:39:03 +01:00
|
|
|
|
|
|
|
// Create optional audio stream source
|
2018-02-18 06:01:02 +01:00
|
|
|
final List<AudioStream> audioStreams = info.getAudioStreams();
|
|
|
|
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
|
|
|
|
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
|
|
|
// Use the audio stream if there is no video stream, or
|
|
|
|
// Merge with audio stream in case if video does not contain audio
|
|
|
|
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
|
2018-02-03 23:39:03 +01:00
|
|
|
final MediaSource audioSource = buildMediaSource(audio.getUrl(),
|
2018-02-27 04:57:59 +01:00
|
|
|
PlayerHelper.cacheKeyOf(info, audio),
|
2018-02-03 23:39:03 +01:00
|
|
|
MediaFormat.getSuffixById(audio.getFormatId()));
|
|
|
|
mediaSources.add(audioSource);
|
|
|
|
}
|
|
|
|
|
2018-02-18 06:01:02 +01:00
|
|
|
// If there is no audio or video sources, then this media source cannot be played back
|
|
|
|
if (mediaSources.isEmpty()) return null;
|
|
|
|
// Below are auxiliary media sources
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
// Create subtitle sources
|
|
|
|
for (final Subtitles subtitle : info.getSubtitles()) {
|
|
|
|
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
|
2018-02-20 13:58:51 +01:00
|
|
|
if (mimeType == null || context == null) continue;
|
2018-02-03 23:39:03 +01:00
|
|
|
|
|
|
|
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
|
2018-02-20 13:58:51 +01:00
|
|
|
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
|
2018-02-27 04:57:59 +01:00
|
|
|
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
|
|
|
|
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
|
2018-02-03 23:39:03 +01:00
|
|
|
mediaSources.add(textSource);
|
|
|
|
}
|
2017-10-23 03:58:01 +02:00
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
if (mediaSources.size() == 1) {
|
|
|
|
return mediaSources.get(0);
|
|
|
|
} else {
|
|
|
|
return new MergingMediaSource(mediaSources.toArray(
|
|
|
|
new MediaSource[mediaSources.size()]));
|
|
|
|
}
|
2017-09-03 04:30:34 +02:00
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// States Implementation
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@Override
|
2017-09-14 17:44:09 +02:00
|
|
|
public void onBlocked() {
|
2017-09-15 04:52:40 +02:00
|
|
|
super.onBlocked();
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
2018-03-04 05:58:53 +01:00
|
|
|
animateView(controlsRoot, false, DEFAULT_CONTROLS_DURATION);
|
2017-05-08 15:33:26 +02:00
|
|
|
|
2017-09-15 04:52:40 +02:00
|
|
|
playbackSeekBar.setEnabled(false);
|
2017-03-27 06:08:16 +02:00
|
|
|
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
|
2017-09-03 08:04:18 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
|
|
|
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(endScreen, false, 0);
|
2017-03-27 06:08:16 +02:00
|
|
|
loadingPanel.setBackgroundColor(Color.BLACK);
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(loadingPanel, true, 0);
|
|
|
|
animateView(surfaceForeground, true, 100);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPlaying() {
|
2017-09-15 04:52:40 +02:00
|
|
|
super.onPlaying();
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
showAndAnimateControl(-1, true);
|
2017-09-15 04:52:40 +02:00
|
|
|
|
|
|
|
playbackSeekBar.setEnabled(true);
|
|
|
|
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
|
|
|
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
loadingPanel.setVisibility(View.GONE);
|
2018-03-04 05:58:53 +01:00
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
2017-09-15 04:52:40 +02:00
|
|
|
animateView(endScreen, false, 0);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onBuffering() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onBuffering() called");
|
|
|
|
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(loadingPanel, true, 500);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPaused() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPaused() called");
|
2017-05-08 15:33:26 +02:00
|
|
|
showControls(400);
|
2017-03-27 06:08:16 +02:00
|
|
|
loadingPanel.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPausedSeek() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPausedSeek() called");
|
|
|
|
showAndAnimateControl(-1, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCompleted() {
|
2017-09-15 04:52:40 +02:00
|
|
|
super.onCompleted();
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
showControls(500);
|
|
|
|
animateView(endScreen, true, 800);
|
|
|
|
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
2017-03-27 06:08:16 +02:00
|
|
|
loadingPanel.setVisibility(View.GONE);
|
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(surfaceForeground, true, 100);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// ExoPlayer Video Listener
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
@Override
|
|
|
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
|
|
|
super.onTracksChanged(trackGroups, trackSelections);
|
2018-02-09 21:43:24 +01:00
|
|
|
onTextTrackUpdate();
|
2018-02-03 23:39:03 +01:00
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
@Override
|
|
|
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
|
|
|
if (DEBUG) {
|
|
|
|
Log.d(TAG, "onVideoSizeChanged() called with: width / height = [" + width + " / " + height + " = " + (((float) width) / height) + "], unappliedRotationDegrees = [" + unappliedRotationDegrees + "], pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
|
|
|
}
|
|
|
|
aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRenderedFirstFrame() {
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(surfaceForeground, false, 100);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2018-02-09 21:43:24 +01:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// ExoPlayer Track Updates
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
private void onTextTrackUpdate() {
|
|
|
|
final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
|
|
|
|
|
|
|
|
if (captionTextView == null) return;
|
|
|
|
if (trackSelector == null || trackSelector.getCurrentMappedTrackInfo() == null ||
|
|
|
|
textRenderer == RENDERER_UNAVAILABLE) {
|
|
|
|
captionTextView.setVisibility(View.GONE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final TrackGroupArray textTracks = trackSelector.getCurrentMappedTrackInfo()
|
|
|
|
.getTrackGroups(textRenderer);
|
|
|
|
|
|
|
|
// Extract all loaded languages
|
|
|
|
List<String> availableLanguages = new ArrayList<>(textTracks.length);
|
|
|
|
for (int i = 0; i < textTracks.length; i++) {
|
|
|
|
final TrackGroup textTrack = textTracks.get(i);
|
|
|
|
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
|
|
|
|
availableLanguages.add(textTrack.getFormat(0).language);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize mismatching language strings
|
2018-02-26 00:10:11 +01:00
|
|
|
final String preferredLanguage = trackSelector.getPreferredTextLanguage();
|
2018-02-09 21:43:24 +01:00
|
|
|
|
|
|
|
// Build UI
|
|
|
|
buildCaptionMenu(availableLanguages);
|
2018-02-26 00:10:11 +01:00
|
|
|
if (trackSelector.getRendererDisabled(textRenderer) || preferredLanguage == null ||
|
|
|
|
!availableLanguages.contains(preferredLanguage)) {
|
2018-02-09 21:43:24 +01:00
|
|
|
captionTextView.setText(R.string.caption_none);
|
|
|
|
} else {
|
2018-02-26 00:10:11 +01:00
|
|
|
captionTextView.setText(preferredLanguage);
|
2018-02-09 21:43:24 +01:00
|
|
|
}
|
|
|
|
captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);
|
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// General Player
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
2017-03-27 06:08:16 +02:00
|
|
|
public void onPrepared(boolean playWhenReady) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
|
|
|
|
|
|
|
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
|
|
|
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
2017-10-23 03:58:01 +02:00
|
|
|
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
super.onPrepared(playWhenReady);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
|
|
|
public void destroy() {
|
|
|
|
super.destroy();
|
|
|
|
if (endScreen != null) endScreen.setImageBitmap(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-03-27 06:08:16 +02:00
|
|
|
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
|
|
|
if (!isPrepared) return;
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-09-04 04:15:11 +02:00
|
|
|
if (duration != playbackSeekBar.getMax()) {
|
|
|
|
playbackEndTime.setText(getTimeString(duration));
|
2017-09-05 21:27:12 +02:00
|
|
|
playbackSeekBar.setMax(duration);
|
2017-09-04 04:15:11 +02:00
|
|
|
}
|
2017-03-27 06:08:16 +02:00
|
|
|
if (currentState != STATE_PAUSED) {
|
|
|
|
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
|
|
|
|
playbackCurrentTime.setText(getTimeString(currentProgress));
|
|
|
|
}
|
|
|
|
if (simpleExoPlayer.isLoading() || bufferPercent > 90) {
|
|
|
|
playbackSeekBar.setSecondaryProgress((int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100)));
|
|
|
|
}
|
|
|
|
if (DEBUG && bufferPercent % 20 == 0) { //Limit log
|
|
|
|
Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
2018-03-03 20:42:23 +01:00
|
|
|
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
|
|
|
super.onLoadingComplete(imageUri, view, loadedImage);
|
|
|
|
if (loadedImage != null) endScreen.setImageBitmap(loadedImage);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
protected void onFullScreenButtonClicked() {
|
|
|
|
if (!isPlayerReady()) return;
|
2017-09-11 02:43:21 +02:00
|
|
|
|
2017-09-14 17:44:09 +02:00
|
|
|
changeState(STATE_BLOCKED);
|
2017-09-07 22:01:02 +02:00
|
|
|
}
|
2017-04-17 06:19:53 +02:00
|
|
|
|
|
|
|
@Override
|
2017-03-27 06:08:16 +02:00
|
|
|
public void onFastRewind() {
|
2017-04-17 06:19:53 +02:00
|
|
|
super.onFastRewind();
|
2017-03-27 06:08:16 +02:00
|
|
|
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
|
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
2017-03-27 06:08:16 +02:00
|
|
|
public void onFastForward() {
|
2017-04-17 06:19:53 +02:00
|
|
|
super.onFastForward();
|
2017-03-27 06:08:16 +02:00
|
|
|
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// OnClick related
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onClick(View v) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
2017-11-09 11:29:02 +01:00
|
|
|
if (v.getId() == qualityTextView.getId()) {
|
2017-03-27 06:08:16 +02:00
|
|
|
onQualitySelectorClicked();
|
2017-10-23 03:58:01 +02:00
|
|
|
} else if (v.getId() == playbackSpeedTextView.getId()) {
|
2017-06-26 04:41:52 +02:00
|
|
|
onPlaybackSpeedClicked();
|
2018-02-03 23:39:03 +01:00
|
|
|
} else if (v.getId() == resizeView.getId()) {
|
|
|
|
onResizeClicked();
|
|
|
|
} else if (v.getId() == captionTextView.getId()) {
|
|
|
|
onCaptionClicked();
|
2018-02-27 04:57:59 +01:00
|
|
|
} else if (v.getId() == playbackLiveSync.getId()) {
|
|
|
|
seekToDefault();
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-06-26 04:41:52 +02:00
|
|
|
* Called when an item of the quality selector or the playback speed selector is selected
|
2017-03-27 06:08:16 +02:00
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
2017-09-03 08:04:18 +02:00
|
|
|
if (DEBUG)
|
|
|
|
Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-06-26 04:41:52 +02:00
|
|
|
if (qualityPopupMenuGroupId == menuItem.getGroupId()) {
|
2017-10-25 06:47:14 +02:00
|
|
|
final int menuItemIndex = menuItem.getItemId();
|
|
|
|
if (selectedStreamIndex == menuItemIndex ||
|
|
|
|
availableStreams == null || availableStreams.size() <= menuItemIndex) return true;
|
2017-06-26 04:41:52 +02:00
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
final String newResolution = availableStreams.get(menuItemIndex).resolution;
|
2017-10-23 03:58:01 +02:00
|
|
|
setRecovery();
|
2017-10-25 06:47:14 +02:00
|
|
|
setPlaybackQuality(newResolution);
|
|
|
|
reload();
|
2017-09-26 04:48:27 +02:00
|
|
|
|
2017-10-23 03:58:01 +02:00
|
|
|
qualityTextView.setText(menuItem.getTitle());
|
2017-06-26 04:41:52 +02:00
|
|
|
return true;
|
|
|
|
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
|
|
|
|
int speedIndex = menuItem.getItemId();
|
|
|
|
float speed = PLAYBACK_SPEEDS[speedIndex];
|
|
|
|
|
|
|
|
setPlaybackSpeed(speed);
|
2017-10-23 03:58:01 +02:00
|
|
|
playbackSpeedTextView.setText(formatSpeed(speed));
|
2017-06-26 04:41:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-07-10 03:38:47 +02:00
|
|
|
* Called when some popup menu is dismissed
|
2017-03-27 06:08:16 +02:00
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void onDismiss(PopupMenu menu) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
|
2017-07-10 03:38:47 +02:00
|
|
|
isSomePopupMenuVisible = false;
|
2018-02-18 06:01:02 +01:00
|
|
|
if (getSelectedVideoStream() != null) {
|
|
|
|
qualityTextView.setText(getSelectedVideoStream().resolution);
|
|
|
|
}
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void onQualitySelectorClicked() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
|
|
|
|
qualityPopupMenu.show();
|
2017-07-10 03:38:47 +02:00
|
|
|
isSomePopupMenuVisible = true;
|
2018-03-04 05:58:53 +01:00
|
|
|
showControls(DEFAULT_CONTROLS_DURATION);
|
2017-03-27 06:08:16 +02:00
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
final VideoStream videoStream = getSelectedVideoStream();
|
2018-02-18 06:01:02 +01:00
|
|
|
if (videoStream != null) {
|
|
|
|
final String qualityText = MediaFormat.getNameById(videoStream.getFormatId()) + " "
|
|
|
|
+ videoStream.resolution;
|
|
|
|
qualityTextView.setText(qualityText);
|
|
|
|
}
|
|
|
|
|
2017-09-14 17:44:09 +02:00
|
|
|
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-06-26 04:41:52 +02:00
|
|
|
private void onPlaybackSpeedClicked() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called");
|
|
|
|
playbackSpeedPopupMenu.show();
|
2017-07-10 03:38:47 +02:00
|
|
|
isSomePopupMenuVisible = true;
|
2018-03-04 05:58:53 +01:00
|
|
|
showControls(DEFAULT_CONTROLS_DURATION);
|
2017-06-26 04:41:52 +02:00
|
|
|
}
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
private void onCaptionClicked() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onCaptionClicked() called");
|
|
|
|
captionPopupMenu.show();
|
|
|
|
isSomePopupMenuVisible = true;
|
2018-03-04 05:58:53 +01:00
|
|
|
showControls(DEFAULT_CONTROLS_DURATION);
|
2018-02-03 23:39:03 +01:00
|
|
|
}
|
|
|
|
|
2018-02-11 04:33:48 +01:00
|
|
|
private void onResizeClicked() {
|
|
|
|
if (getAspectRatioFrameLayout() != null && context != null) {
|
|
|
|
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
|
|
|
|
final int newResizeMode = nextResizeMode(currentResizeMode);
|
|
|
|
getAspectRatioFrameLayout().setResizeMode(newResizeMode);
|
|
|
|
getResizeView().setText(PlayerHelper.resizeTypeOf(context, newResizeMode));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode);
|
2017-03-27 06:08:16 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// SeekBar Listener
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
|
|
|
if (DEBUG && fromUser) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + progress + "]");
|
|
|
|
//if (fromUser) playbackCurrentTime.setText(getTimeString(progress));
|
|
|
|
if (fromUser) currentDisplaySeek.setText(getTimeString(progress));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
|
|
|
|
if (getCurrentState() != STATE_PAUSED_SEEK) changeState(STATE_PAUSED_SEEK);
|
|
|
|
|
2017-09-14 17:44:09 +02:00
|
|
|
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
2017-03-27 06:08:16 +02:00
|
|
|
if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
|
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
showControls(0);
|
2018-03-04 05:58:53 +01:00
|
|
|
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true,
|
|
|
|
DEFAULT_CONTROLS_DURATION);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]");
|
|
|
|
|
|
|
|
simpleExoPlayer.seekTo(seekBar.getProgress());
|
|
|
|
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true);
|
|
|
|
|
|
|
|
playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
|
2017-05-08 15:33:26 +02:00
|
|
|
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
2017-03-27 06:08:16 +02:00
|
|
|
|
|
|
|
if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING);
|
2017-09-07 22:01:02 +02:00
|
|
|
if (!isProgressLoopRunning()) startProgressLoop();
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Utils
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2018-02-03 23:39:03 +01:00
|
|
|
public int getRendererIndex(final int trackIndex) {
|
2018-02-09 21:43:24 +01:00
|
|
|
if (simpleExoPlayer == null) return RENDERER_UNAVAILABLE;
|
2017-10-15 07:54:25 +02:00
|
|
|
|
|
|
|
for (int t = 0; t < simpleExoPlayer.getRendererCount(); t++) {
|
2018-02-03 23:39:03 +01:00
|
|
|
if (simpleExoPlayer.getRendererType(t) == trackIndex) {
|
2017-10-15 07:54:25 +02:00
|
|
|
return t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-09 21:43:24 +01:00
|
|
|
return RENDERER_UNAVAILABLE;
|
2017-10-15 07:54:25 +02:00
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
public boolean isControlsVisible() {
|
|
|
|
return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show a animation, and depending on goneOnEnd, will stay on the screen or be gone
|
|
|
|
*
|
|
|
|
* @param drawableId the drawable that will be used to animate, pass -1 to clear any animation that is visible
|
|
|
|
* @param goneOnEnd will set the animation view to GONE on the end of the animation
|
|
|
|
*/
|
|
|
|
public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
|
|
|
|
if (DEBUG) Log.d(TAG, "showAndAnimateControl() called with: drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
|
|
|
|
if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
|
|
|
|
if (DEBUG) Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
|
|
|
|
controlViewAnimator.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (drawableId == -1) {
|
|
|
|
if (controlAnimationView.getVisibility() == View.VISIBLE) {
|
|
|
|
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
|
|
|
PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
|
|
|
|
PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
|
|
|
|
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
|
2018-03-04 05:58:53 +01:00
|
|
|
).setDuration(DEFAULT_CONTROLS_DURATION);
|
2017-03-27 06:08:16 +02:00
|
|
|
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
|
|
|
controlAnimationView.setVisibility(View.GONE);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
controlViewAnimator.start();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
float scaleFrom = goneOnEnd ? 1f : 1f, scaleTo = goneOnEnd ? 1.8f : 1.4f;
|
|
|
|
float alphaFrom = goneOnEnd ? 1f : 0f, alphaTo = goneOnEnd ? 0f : 1f;
|
|
|
|
|
|
|
|
|
|
|
|
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
|
|
|
|
PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
|
|
|
|
PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
|
|
|
|
PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
|
|
|
|
);
|
|
|
|
controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
|
|
|
|
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
|
|
|
|
@Override
|
|
|
|
public void onAnimationEnd(Animator animation) {
|
|
|
|
if (goneOnEnd) controlAnimationView.setVisibility(View.GONE);
|
|
|
|
else controlAnimationView.setVisibility(View.VISIBLE);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
controlAnimationView.setVisibility(View.VISIBLE);
|
|
|
|
controlAnimationView.setImageDrawable(ContextCompat.getDrawable(context, drawableId));
|
|
|
|
controlViewAnimator.start();
|
|
|
|
}
|
|
|
|
|
2017-07-10 03:38:47 +02:00
|
|
|
public boolean isSomePopupMenuVisible() {
|
|
|
|
return isSomePopupMenuVisible;
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
public void showControlsThenHide() {
|
|
|
|
if (DEBUG) Log.d(TAG, "showControlsThenHide() called");
|
2018-03-04 05:58:53 +01:00
|
|
|
animateView(controlsRoot, true, DEFAULT_CONTROLS_DURATION, 0,
|
|
|
|
() -> hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME));
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
public void showControls(long duration) {
|
|
|
|
if (DEBUG) Log.d(TAG, "showControls() called");
|
|
|
|
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
|
|
|
animateView(controlsRoot, true, duration);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
public void hideControls(final long duration, long delay) {
|
|
|
|
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
|
|
|
|
controlsVisibilityHandler.removeCallbacksAndMessages(null);
|
2018-03-04 05:58:53 +01:00
|
|
|
controlsVisibilityHandler.postDelayed(
|
|
|
|
() -> animateView(controlsRoot, false, duration), delay);
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Getters and Setters
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-10-25 06:47:14 +02:00
|
|
|
public void setPlaybackQuality(final String quality) {
|
|
|
|
this.playbackQuality = quality;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getPlaybackQuality() {
|
|
|
|
return playbackQuality;
|
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
|
|
|
return aspectRatioFrameLayout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public SurfaceView getSurfaceView() {
|
|
|
|
return surfaceView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean wasPlaying() {
|
|
|
|
return wasPlaying;
|
|
|
|
}
|
|
|
|
|
2018-02-18 06:01:02 +01:00
|
|
|
@Nullable
|
2017-09-24 06:50:32 +02:00
|
|
|
public VideoStream getSelectedVideoStream() {
|
2018-02-18 06:01:02 +01:00
|
|
|
return (selectedStreamIndex >= 0 && availableStreams != null &&
|
|
|
|
availableStreams.size() > selectedStreamIndex) ?
|
|
|
|
availableStreams.get(selectedStreamIndex) : null;
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|
|
|
|
|
2017-05-08 15:33:26 +02:00
|
|
|
public Handler getControlsVisibilityHandler() {
|
|
|
|
return controlsVisibilityHandler;
|
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
public View getRootView() {
|
|
|
|
return rootView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setRootView(View rootView) {
|
|
|
|
this.rootView = rootView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getLoadingPanel() {
|
|
|
|
return loadingPanel;
|
|
|
|
}
|
|
|
|
|
|
|
|
public ImageView getEndScreen() {
|
|
|
|
return endScreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
public ImageView getControlAnimationView() {
|
|
|
|
return controlAnimationView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getControlsRoot() {
|
|
|
|
return controlsRoot;
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getBottomControlsRoot() {
|
|
|
|
return bottomControlsRoot;
|
|
|
|
}
|
|
|
|
|
|
|
|
public SeekBar getPlaybackSeekBar() {
|
|
|
|
return playbackSeekBar;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TextView getPlaybackCurrentTime() {
|
|
|
|
return playbackCurrentTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TextView getPlaybackEndTime() {
|
|
|
|
return playbackEndTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
public View getTopControlsRoot() {
|
|
|
|
return topControlsRoot;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TextView getQualityTextView() {
|
|
|
|
return qualityTextView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public PopupMenu getQualityPopupMenu() {
|
|
|
|
return qualityPopupMenu;
|
|
|
|
}
|
|
|
|
|
2017-11-09 11:29:02 +01:00
|
|
|
public PopupMenu getPlaybackSpeedPopupMenu() {
|
|
|
|
return playbackSpeedPopupMenu;
|
|
|
|
}
|
|
|
|
|
2017-03-27 06:08:16 +02:00
|
|
|
public View getSurfaceForeground() {
|
|
|
|
return surfaceForeground;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TextView getCurrentDisplaySeek() {
|
|
|
|
return currentDisplaySeek;
|
|
|
|
}
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2018-02-07 22:11:19 +01:00
|
|
|
public SubtitleView getSubtitleView() {
|
|
|
|
return subtitleView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TextView getResizeView() {
|
|
|
|
return resizeView;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TextView getCaptionTextView() {
|
|
|
|
return captionTextView;
|
|
|
|
}
|
2017-03-27 06:08:16 +02:00
|
|
|
}
|