diff --git a/app/build.gradle b/app/build.gradle
index 73f11aab0..0f99854c2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -45,7 +45,7 @@ dependencies {
compile 'com.google.code.gson:gson:2.4'
compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'ch.acra:acra:4.9.0'
- compile 'com.devbrackets.android:exomedia:3.1.1'
+ compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'org.json:json:20160810'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a7dcd9205..3172506f9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,19 +51,7 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleInstance"
- android:theme="@style/PlayerTheme">
-
-
-
-
-
-
-
-
-
-
-
-
+ android:theme="@style/PlayerTheme"/>
+ android:label="@string/popup_mode_share_menu_title">
diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
index a221e4e94..f1e94ea8e 100644
--- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
@@ -51,6 +51,7 @@ import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.info_list.InfoItemBuilder;
+import org.schabi.newpipe.player.AbstractPlayer;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.PlayVideoActivity;
@@ -59,6 +60,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack;
import org.schabi.newpipe.util.PermissionHelper;
+import java.util.ArrayList;
import java.util.Vector;
import static android.app.Activity.RESULT_OK;
@@ -331,10 +333,12 @@ public class VideoItemDetailFragment extends Fragment {
// so, I can notify the service through a broadcast, but the problem is
// when I click in another video, another thumbnail will be load, and will
// notify again, so I send the videoUrl and compare with the service's url
- ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- Intent intent = new Intent(PopupVideoPlayer.InternalListener.ACTION_UPDATE_THUMB);
- intent.putExtra(PopupVideoPlayer.VIDEO_URL, info.webpage_url);
- getContext().sendBroadcast(intent);
+ if (getContext() != null) {
+ ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
+ Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB);
+ intent.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url);
+ getContext().sendBroadcast(intent);
+ }
}
}
@@ -388,13 +392,15 @@ public class VideoItemDetailFragment extends Fragment {
if (streamThumbnail != null)
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- VideoStream selectedVideoStream = info.video_streams.get(selectedStreamId);
Intent i = new Intent(activity, PopupVideoPlayer.class);
- Toast.makeText(activity, "Starting in popup mode", Toast.LENGTH_SHORT).show();
- i.putExtra(PopupVideoPlayer.VIDEO_TITLE, info.title)
- .putExtra(PopupVideoPlayer.STREAM_URL, selectedVideoStream.url)
- .putExtra(PopupVideoPlayer.CHANNEL_NAME, info.uploader)
- .putExtra(PopupVideoPlayer.VIDEO_URL, info.webpage_url);
+ Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
+ i.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
+ .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
+ .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamId)
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams));
+ if (info.start_position > 0) i.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
+
activity.startService(i);
}
});
@@ -784,47 +790,27 @@ public class VideoItemDetailFragment extends Fragment {
builder.create().show();
}
} else {
- if (PreferenceManager.getDefaultSharedPreferences(activity)
- .getBoolean(activity.getString(R.string.use_exoplayer_key), false)) {
-
- // TODO: Fix this mess
- if (streamThumbnail != null)
- ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- // exo player
-
- if(info.dashMpdUrl != null && !info.dashMpdUrl.isEmpty()) {
- // try dash
- Intent intent = new Intent(activity, ExoPlayerActivity.class)
- .setData(Uri.parse(info.dashMpdUrl));
- //.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_DASH);
- startActivity(intent);
- } else if((info.audio_streams != null && !info.audio_streams.isEmpty()) &&
- (info.video_only_streams != null && !info.video_only_streams.isEmpty())) {
- // try smooth streaming
-
- } else {
- //default streaming
- Intent intent = new Intent(activity, ExoPlayerActivity.class)
- .setDataAndType(Uri.parse(selectedVideoStream.url),
- MediaFormat.getMimeById(selectedVideoStream.format))
-
- .putExtra(ExoPlayerActivity.VIDEO_TITLE, info.title)
- .putExtra(ExoPlayerActivity.CHANNEL_NAME, info.uploader);
- //.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_OTHER);
-
- activity.startActivity(intent); // HERE !!!
- }
- //-------------
-
+ Intent intent;
+ if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(activity.getString(R.string.use_exoplayer_key), false)) {
+ // ExoPlayer
+ if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
+ intent = new Intent(activity, ExoPlayerActivity.class)
+ .putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
+ .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
+ .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, actionBarHandler.getSelectedVideoStream())
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams));
+ if (info.start_position > 0) intent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
} else {
// Internal Player
- Intent intent = new Intent(activity, PlayVideoActivity.class)
+ intent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title)
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url)
.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
- activity.startActivity(intent); //also HERE !!!
}
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity.startActivity(intent);
}
// --------------------------------------------
diff --git a/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java b/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
new file mode 100644
index 000000000..a9987bee9
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
@@ -0,0 +1,1106 @@
+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;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.DefaultLoadControl;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
+import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
+import com.google.android.exoplayer2.source.hls.HlsMediaSource;
+import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
+import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
+import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
+import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
+import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
+import com.google.android.exoplayer2.upstream.cache.SimpleCache;
+import com.google.android.exoplayer2.util.Util;
+
+import org.schabi.newpipe.ActivityCommunicator;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.MediaFormat;
+import org.schabi.newpipe.extractor.stream_info.VideoStream;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Common properties of the players
+ *
+ * @author mauriciocolli
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener, SimpleExoPlayer.VideoListener {
+ public static final boolean DEBUG = false;
+ public final String TAG;
+
+ protected Context context;
+ private SharedPreferences sharedPreferences;
+
+ private static int currentState = -1;
+ public static final String ACTION_UPDATE_THUMB = "org.schabi.newpipe.player.AbstractPlayer.UPDATE_THUMBNAIL";
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Intent
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final String VIDEO_URL = "video_url";
+ public static final String VIDEO_STREAMS_LIST = "video_streams_list";
+ public static final String VIDEO_TITLE = "video_title";
+ public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
+ public static final String START_POSITION = "start_position";
+ public static final String CHANNEL_NAME = "channel_name";
+ public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
+
+ private String videoUrl = "";
+ private int videoStartPos = -1;
+ private String videoTitle = "";
+ private Bitmap videoThumbnail;
+ private String channelName = "";
+ private int selectedIndexStream;
+ private ArrayList videoStreamsList;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
+ public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
+ public static final String CACHE_FOLDER_NAME = "exoplayer";
+
+ private boolean startedFromNewPipe = true;
+ private boolean isPrepared = false;
+ private boolean wasPlaying = false;
+ private SimpleExoPlayer simpleExoPlayer;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private MediaSource videoSource;
+ private static CacheDataSourceFactory cacheDataSourceFactory;
+ private static final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
+ private static final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
+
+ private AtomicBoolean isProgressLoopRunning = new AtomicBoolean();
+ private Handler progressLoop;
+ private Runnable progressUpdate;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Repeat
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private RepeatMode currentRepeatMode = RepeatMode.REPEAT_DISABLED;
+
+ public enum RepeatMode {
+ REPEAT_DISABLED,
+ REPEAT_ONE,
+ REPEAT_ALL
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // 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;
+
+ private View topControlsRoot;
+ private TextView qualityTextView;
+ private ImageButton fullScreenButton;
+
+ private ValueAnimator controlViewAnimator;
+
+ private boolean isQualityPopupMenuVisible = false;
+ private boolean qualityChanged = false;
+ private int qualityPopupMenuGroupId = 69;
+ private PopupMenu qualityPopupMenu;
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ public AbstractPlayer(String debugTag, Context context) {
+ this.TAG = debugTag;
+ this.context = context;
+ this.progressLoop = new Handler();
+ this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+ if (cacheDataSourceFactory == null) {
+ DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getPackageName()), bandwidthMeter);
+ File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
+ if (!cacheDir.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ cacheDir.mkdir();
+ }
+
+ Log.d(TAG, "buildMediaSource: cacheDir = " + cacheDir.getAbsolutePath());
+ SimpleCache simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(64 * 1024 * 1024L));
+ cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE, 512 * 1024);
+ }
+ }
+
+ public void setup(View rootView) {
+ initViews(rootView);
+ initListeners();
+ if (simpleExoPlayer == null) initPlayer();
+ else {
+ simpleExoPlayer.addListener(this);
+ simpleExoPlayer.setVideoListener(this);
+ simpleExoPlayer.setVideoSurfaceView(surfaceView);
+ }
+ }
+
+ public void initViews(View rootView) {
+ this.rootView = rootView;
+ this.aspectRatioFrameLayout = (AspectRatioFrameLayout) rootView.findViewById(R.id.aspectRatioLayout);
+ this.surfaceView = (SurfaceView) rootView.findViewById(R.id.surfaceView);
+ this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground);
+ this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
+ this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
+ this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
+ this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot);
+ this.currentDisplaySeek = (TextView) rootView.findViewById(R.id.currentDisplaySeek);
+ this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
+ this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
+ this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
+ this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
+ this.topControlsRoot = rootView.findViewById(R.id.topControls);
+ this.qualityTextView = (TextView) rootView.findViewById(R.id.qualityTextView);
+ this.fullScreenButton = (ImageButton) rootView.findViewById(R.id.fullScreenButton);
+
+ //this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
+ this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) this.qualityPopupMenu = new PopupMenu(context, qualityTextView, Gravity.CENTER | Gravity.BOTTOM);
+ else this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
+
+ ((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
+
+ }
+
+ public void initListeners() {
+ progressUpdate = new Runnable() {
+ @Override
+ public void run() {
+ //if(DEBUG) Log.d(TAG, "progressUpdate run() called");
+ onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
+ if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, 100);
+ }
+ };
+
+ playbackSeekBar.setOnSeekBarChangeListener(this);
+ fullScreenButton.setOnClickListener(this);
+ qualityTextView.setOnClickListener(this);
+ }
+
+ public void initPlayer() {
+ if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
+
+ AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
+ DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
+ DefaultLoadControl loadControl = new DefaultLoadControl();
+
+ simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, defaultTrackSelector, loadControl);
+ simpleExoPlayer.addListener(this);
+ simpleExoPlayer.setVideoListener(this);
+ simpleExoPlayer.setVideoSurfaceView(surfaceView);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void handleIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
+ if (intent == null) return;
+
+ selectedIndexStream = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
+
+ Serializable serializable = intent.getSerializableExtra(VIDEO_STREAMS_LIST);
+
+ if (serializable instanceof ArrayList) videoStreamsList = (ArrayList) serializable;
+ if (serializable instanceof Vector) videoStreamsList = new ArrayList<>((List) serializable);
+
+ videoUrl = intent.getStringExtra(VIDEO_URL);
+ videoTitle = intent.getStringExtra(VIDEO_TITLE);
+ videoStartPos = intent.getIntExtra(START_POSITION, -1);
+ channelName = intent.getStringExtra(CHANNEL_NAME);
+ startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true);
+ try {
+ videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ playVideo(getSelectedStreamUri(), true);
+ }
+
+ public void playVideo(Uri videoURI, boolean autoPlay) {
+ if (DEBUG) Log.d(TAG, "playVideo() called with: videoURI = [" + videoURI + "], autoPlay = [" + autoPlay + "]");
+
+ if (videoURI == null || simpleExoPlayer == null) {
+ onError();
+ return;
+ }
+
+ changeState(STATE_LOADING);
+ isPrepared = false;
+ qualityChanged = false;
+
+ qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
+ buildQualityMenu(qualityPopupMenu);
+
+ videoSource = buildMediaSource(videoURI, MediaFormat.getSuffixById(videoStreamsList.get(selectedIndexStream).format));
+
+ if (simpleExoPlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) simpleExoPlayer.stop();
+ if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
+ simpleExoPlayer.prepare(videoSource);
+ simpleExoPlayer.setPlayWhenReady(autoPlay);
+ }
+
+ public void destroy() {
+ if (DEBUG) Log.d(TAG, "destroy() called");
+ if (simpleExoPlayer != null) {
+ simpleExoPlayer.stop();
+ simpleExoPlayer.release();
+ }
+ if (progressLoop != null) stopProgressLoop();
+ }
+
+ private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
+ if (DEBUG) Log.d(TAG, "buildMediaSource() called with: uri = [" + uri + "], overrideExtension = [" + overrideExtension + "]");
+ int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
+ switch (type) {
+ case C.TYPE_SS:
+ return new SsMediaSource(uri, cacheDataSourceFactory, new DefaultSsChunkSource.Factory(cacheDataSourceFactory), null, null);
+ case C.TYPE_DASH:
+ return new DashMediaSource(uri, cacheDataSourceFactory, new DefaultDashChunkSource.Factory(cacheDataSourceFactory), null, null);
+ case C.TYPE_HLS:
+ return new HlsMediaSource(uri, cacheDataSourceFactory, null, null);
+ case C.TYPE_OTHER:
+ return new ExtractorMediaSource(uri, cacheDataSourceFactory, extractorsFactory, null, null);
+ default: {
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
+ }
+ }
+
+ public void buildQualityMenu(PopupMenu popupMenu) {
+ for (int i = 0; i < videoStreamsList.size(); i++) {
+ VideoStream videoStream = videoStreamsList.get(i);
+ popupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
+ }
+ qualityTextView.setText(videoStreamsList.get(selectedIndexStream).resolution);
+ popupMenu.setOnMenuItemClickListener(this);
+ popupMenu.setOnDismissListener(this);
+
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // States Implementation
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void changeState(int state) {
+ if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
+ currentState = state;
+ switch (state) {
+ case STATE_LOADING:
+ onLoading();
+ break;
+ case STATE_PLAYING:
+ onPlaying();
+ break;
+ case STATE_BUFFERING:
+ onBuffering();
+ break;
+ case STATE_PAUSED:
+ onPaused();
+ break;
+ case STATE_PAUSED_SEEK:
+ onPausedSeek();
+ break;
+ case STATE_COMPLETED:
+ onCompleted();
+ break;
+ }
+ }
+
+ @Override
+ public void onLoading() {
+ if (DEBUG) Log.d(TAG, "onLoading() called");
+
+ if (!isProgressLoopRunning.get()) startProgressLoop();
+
+ showAndAnimateControl(-1, true);
+ playbackSeekBar.setEnabled(true);
+ playbackSeekBar.setProgress(0);
+
+ // 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);
+
+ animateView(endScreen, false, 0, 0);
+ animateView(controlsRoot, false, 0, 0);
+ loadingPanel.setBackgroundColor(Color.BLACK);
+ animateView(loadingPanel, true, 0, 0);
+ animateView(surfaceForeground, true, 100, 0);
+ }
+
+ @Override
+ public void onPlaying() {
+ if (DEBUG) Log.d(TAG, "onPlaying() called");
+ if (!isProgressLoopRunning.get()) startProgressLoop();
+ showAndAnimateControl(-1, true);
+ loadingPanel.setVisibility(View.GONE);
+ animateView(controlsRoot, false, 500, DEFAULT_CONTROLS_HIDE_TIME, true);
+ animateView(currentDisplaySeek, false, 200, 0);
+ }
+
+ @Override
+ public void onBuffering() {
+ if (DEBUG) Log.d(TAG, "onBuffering() called");
+ loadingPanel.setBackgroundColor(Color.TRANSPARENT);
+ animateView(loadingPanel, true, 500, 0);
+ animateView(controlsRoot, false, 0, 0);
+ }
+
+ @Override
+ public void onPaused() {
+ if (DEBUG) Log.d(TAG, "onPaused() called");
+ animateView(controlsRoot, true, 500, 100);
+ loadingPanel.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onPausedSeek() {
+ if (DEBUG) Log.d(TAG, "onPausedSeek() called");
+ showAndAnimateControl(-1, true);
+ }
+
+ @Override
+ public void onCompleted() {
+ if (DEBUG) Log.d(TAG, "onCompleted() called");
+
+ if (isProgressLoopRunning.get()) stopProgressLoop();
+
+ if (videoThumbnail != null) endScreen.setImageBitmap(videoThumbnail);
+ animateView(controlsRoot, true, 500, 0);
+ animateView(endScreen, true, 800, 0);
+ animateView(currentDisplaySeek, false, 200, 0);
+ loadingPanel.setVisibility(View.GONE);
+
+ playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
+ playbackSeekBar.setProgress(playbackSeekBar.getMax());
+ playbackSeekBar.setEnabled(false);
+ playbackEndTime.setText(getTimeString(playbackSeekBar.getMax()));
+ playbackCurrentTime.setText(playbackEndTime.getText());
+ // 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);
+
+ animateView(surfaceForeground, true, 100, 0);
+
+ if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
+ changeState(STATE_LOADING);
+ getPlayer().seekTo(0);
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // ExoPlayer Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onTimelineChanged(Timeline timeline, Object manifest) {
+
+ }
+
+ @Override
+ public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+
+ }
+
+ @Override
+ public void onLoadingChanged(boolean isLoading) {
+ if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
+
+ if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop();
+ else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop();
+ }
+
+ @Override
+ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ if (DEBUG) Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
+ if (getCurrentState() == STATE_PAUSED_SEEK) {
+ if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek");
+ return;
+ }
+
+ switch (playbackState) {
+ case ExoPlayer.STATE_IDLE: // 1
+ isPrepared = false;
+ break;
+ case ExoPlayer.STATE_BUFFERING: // 2
+ if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING);
+ break;
+ case ExoPlayer.STATE_READY: //3
+ if (!isPrepared) {
+ isPrepared = true;
+ onPrepared(playWhenReady);
+ break;
+ }
+ if (currentState == STATE_PAUSED_SEEK) break;
+ changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
+ break;
+ case ExoPlayer.STATE_ENDED: // 4
+ changeState(STATE_COMPLETED);
+ isPrepared = false;
+ break;
+ }
+ }
+
+ @Override
+ public void onPlayerError(ExoPlaybackException error) {
+ if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
+ onError();
+ }
+
+ @Override
+ public void onPositionDiscontinuity() {
+ if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called");
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // ExoPlayer Video Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @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() {
+ animateView(surfaceForeground, false, 100, 0);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // General Player
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public abstract void onError();
+
+ public void onPrepared(boolean playWhenReady) {
+ if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
+
+ if (videoStartPos > 0) {
+ playbackSeekBar.setProgress(videoStartPos);
+ playbackCurrentTime.setText(getTimeString(videoStartPos));
+ videoStartPos = -1;
+ }
+
+ playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
+ playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
+
+ changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
+ }
+
+ public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
+ if (!isPrepared) return;
+ 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 + "]");
+ }
+ }
+
+ public void onUpdateThumbnail(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called with: intent = [" + intent + "]");
+ if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return;
+ videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
+ }
+
+ public void onVideoPlayPause() {
+ if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
+ if (currentState == STATE_COMPLETED) {
+ changeState(STATE_LOADING);
+ if (qualityChanged) playVideo(getSelectedStreamUri(), true);
+ simpleExoPlayer.seekTo(0);
+ return;
+ }
+ simpleExoPlayer.setPlayWhenReady(!isPlaying());
+ }
+
+ public void onFastRewind() {
+ if (DEBUG) Log.d(TAG, "onFastRewind() called");
+ seekBy(-FAST_FORWARD_REWIND_AMOUNT);
+ showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
+ animateView(controlsRoot, false, 100, 0);
+ }
+
+ public void onFastForward() {
+ if (DEBUG) Log.d(TAG, "onFastForward() called");
+ seekBy(FAST_FORWARD_REWIND_AMOUNT);
+ showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
+ animateView(controlsRoot, false, 100, 0);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // OnClick related
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onClick(View v) {
+ if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
+ if (v.getId() == fullScreenButton.getId()) {
+ onFullScreenButtonClicked();
+ } else if (v.getId() == qualityTextView.getId()) {
+ onQualitySelectorClicked();
+ }
+ }
+
+ /**
+ * Called when an item of the quality selector is selected
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
+ if (selectedIndexStream == menuItem.getItemId()) return true;
+ setVideoStartPos((int) getPlayer().getCurrentPosition());
+
+ if (!(getCurrentState() == STATE_COMPLETED)) playVideo(Uri.parse(getVideoStreamsList().get(menuItem.getItemId()).url), wasPlaying);
+ else qualityChanged = true;
+
+ selectedIndexStream = menuItem.getItemId();
+ qualityTextView.setText(menuItem.getTitle());
+ return true;
+ }
+
+ /**
+ * Called when the quality selector is dismissed
+ */
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
+ isQualityPopupMenuVisible = false;
+ qualityTextView.setText(videoStreamsList.get(selectedIndexStream).resolution);
+ }
+
+ public abstract void onFullScreenButtonClicked();
+
+ public void onQualitySelectorClicked() {
+ if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
+ qualityPopupMenu.show();
+ isQualityPopupMenuVisible = true;
+ animateView(getControlsRoot(), true, 300, 0);
+
+ VideoStream videoStream = videoStreamsList.get(selectedIndexStream);
+ qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
+ wasPlaying = isPlaying();
+ }
+
+ public void onRepeatClicked() {
+ if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
+ // TODO: implement repeat all when playlist is implemented
+
+ // Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
+ setCurrentRepeatMode(getCurrentRepeatMode() == RepeatMode.REPEAT_DISABLED ?
+ RepeatMode.REPEAT_ONE :
+ RepeatMode.REPEAT_DISABLED);
+
+ if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getCurrentRepeatMode().name());
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // 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);
+
+ wasPlaying = isPlaying();
+ if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
+
+ animateView(controlsRoot, true, 0, 0);
+ animateView(currentDisplaySeek, true, 300, 0);
+ }
+
+ @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()));
+ animateView(currentDisplaySeek, false, 200, 0);
+
+ if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING);
+ if (!isProgressLoopRunning.get()) startProgressLoop();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private static final StringBuilder stringBuilder = new StringBuilder();
+ private static final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
+
+ public String getTimeString(int milliSeconds) {
+ long seconds = (milliSeconds % 60000L) / 1000L;
+ long minutes = (milliSeconds % 3600000L) / 60000L;
+ long hours = (milliSeconds % 86400000L) / 3600000L;
+ long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
+
+ stringBuilder.setLength(0);
+ return days > 0 ? formatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
+ : hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
+ : formatter.format("%02d:%02d", minutes, seconds).toString();
+ }
+
+ 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)
+ ).setDuration(300);
+ 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();
+ }
+
+ public void animateView(View view, boolean enterOrExit, long duration, long delay) {
+ animateView(view, enterOrExit, duration, delay, null, false);
+ }
+
+ public void animateView(View view, boolean enterOrExit, long duration, long delay, boolean hideUi) {
+ animateView(view, enterOrExit, duration, delay, null, hideUi);
+ }
+
+ public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
+ animateView(view, enterOrExit, duration, delay, execOnEnd, false);
+ }
+
+ /**
+ * Animate the view
+ *
+ * @param view view that will be animated
+ * @param enterOrExit true to enter, false to exit
+ * @param duration how long the animation will take, in milliseconds
+ * @param delay how long the animation will wait to start, in milliseconds
+ * @param execOnEnd runnable that will be executed when the animation ends
+ * @param hideUi need to hide ui when animation ends,
+ * just a helper for classes extending this
+ */
+ public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) {
+ if (DEBUG) {
+ Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]");
+ }
+ if (view.getVisibility() == View.VISIBLE && enterOrExit) {
+ if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.VISIBLE);
+ view.setAlpha(1f);
+ if (execOnEnd != null) execOnEnd.run();
+ return;
+ } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
+ if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.GONE);
+ view.setAlpha(0f);
+ if (execOnEnd != null) execOnEnd.run();
+ return;
+ }
+
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.VISIBLE);
+
+ if (view == controlsRoot) {
+ if (enterOrExit) {
+ view.animate().alpha(1f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ }).start();
+ } else {
+ view.animate().alpha(0f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ })
+ .start();
+ }
+ return;
+ }
+
+ if (enterOrExit) {
+ view.setAlpha(0f);
+ view.setScaleX(.8f);
+ view.setScaleY(.8f);
+ view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ }).start();
+ } else {
+ view.setAlpha(1f);
+ view.setScaleX(1f);
+ view.setScaleY(1f);
+ view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ })
+ .start();
+ }
+ }
+
+ private void seekBy(int milliSeconds) {
+ if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
+ if (simpleExoPlayer == null) return;
+ int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds);
+ simpleExoPlayer.seekTo(progress);
+ }
+
+ public boolean isPlaying() {
+ return simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY && simpleExoPlayer.getPlayWhenReady();
+ }
+
+ public boolean isQualityMenuVisible() {
+ return isQualityPopupMenuVisible;
+ }
+
+ private void startProgressLoop() {
+ progressLoop.removeCallbacksAndMessages(null);
+ isProgressLoopRunning.set(true);
+ progressLoop.post(progressUpdate);
+ }
+
+ private void stopProgressLoop() {
+ isProgressLoopRunning.set(false);
+ progressLoop.removeCallbacksAndMessages(null);
+ }
+
+ public void tryDeleteCacheFiles(Context context) {
+ File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
+
+ if (cacheDir.exists()) {
+ try {
+ if (cacheDir.isDirectory()) {
+ for (File file : cacheDir.listFiles()) {
+ try {
+ if (DEBUG) Log.d(TAG, "tryDeleteCacheFiles: " + file.getAbsolutePath() + " deleted = " + file.delete());
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Getters and Setters
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public SimpleExoPlayer getPlayer() {
+ return simpleExoPlayer;
+ }
+
+ public SharedPreferences getSharedPreferences() {
+ return sharedPreferences;
+ }
+
+ public AspectRatioFrameLayout getAspectRatioFrameLayout() {
+ return aspectRatioFrameLayout;
+ }
+
+ public SurfaceView getSurfaceView() {
+ return surfaceView;
+ }
+
+ public RepeatMode getCurrentRepeatMode() {
+ return currentRepeatMode;
+ }
+
+ public void setCurrentRepeatMode(RepeatMode mode) {
+ currentRepeatMode = mode;
+ }
+
+ public boolean wasPlaying() {
+ return wasPlaying;
+ }
+
+ public int getCurrentState() {
+ return currentState;
+ }
+
+ public Uri getSelectedStreamUri() {
+ return Uri.parse(videoStreamsList.get(selectedIndexStream).url);
+ }
+
+ public int getQualityPopupMenuGroupId() {
+ return qualityPopupMenuGroupId;
+ }
+
+ public String getVideoUrl() {
+ return videoUrl;
+ }
+
+ public void setVideoUrl(String videoUrl) {
+ this.videoUrl = videoUrl;
+ }
+
+ public int getVideoStartPos() {
+ return videoStartPos;
+ }
+
+ public void setVideoStartPos(int videoStartPos) {
+ this.videoStartPos = videoStartPos;
+ }
+
+ public String getVideoTitle() {
+ return videoTitle;
+ }
+
+ public void setVideoTitle(String videoTitle) {
+ this.videoTitle = videoTitle;
+ }
+
+ public Bitmap getVideoThumbnail() {
+ return videoThumbnail;
+ }
+
+ public void setVideoThumbnail(Bitmap videoThumbnail) {
+ this.videoThumbnail = videoThumbnail;
+ }
+
+ public String getChannelName() {
+ return channelName;
+ }
+
+ public void setChannelName(String channelName) {
+ this.channelName = channelName;
+ }
+
+ public int getSelectedIndexStream() {
+ return selectedIndexStream;
+ }
+
+ public void setSelectedIndexStream(int selectedIndexStream) {
+ this.selectedIndexStream = selectedIndexStream;
+ }
+
+ public ArrayList getVideoStreamsList() {
+ return videoStreamsList;
+ }
+
+ public void setVideoStreamsList(ArrayList videoStreamsList) {
+ this.videoStreamsList = videoStreamsList;
+ }
+
+ public boolean isStartedFromNewPipe() {
+ return startedFromNewPipe;
+ }
+
+ public void setStartedFromNewPipe(boolean startedFromNewPipe) {
+ this.startedFromNewPipe = startedFromNewPipe;
+ }
+
+ 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 ImageButton getFullScreenButton() {
+ return fullScreenButton;
+ }
+
+ public PopupMenu getQualityPopupMenu() {
+ return qualityPopupMenu;
+ }
+
+ public View getSurfaceForeground() {
+ return surfaceForeground;
+ }
+
+ public TextView getCurrentDisplaySeek() {
+ return currentDisplaySeek;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
index d1c53d85a..88a5a914d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -26,7 +26,6 @@ import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.util.NavStack;
import java.io.IOException;
@@ -343,7 +342,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
- (R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
+ (R.drawable.ic_pause_white, "Pause", playPI).build();
*/
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
@@ -465,7 +464,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
RemoteViews views = getContentView(), bigViews = getBigContentView();
int imageSrc;
if(isPlaying) {
- imageSrc = R.drawable.ic_pause_white_24dp;
+ imageSrc = R.drawable.ic_pause_white;
} else {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
index c868bb722..58e93fc61 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
@@ -1,220 +1,559 @@
package org.schabi.newpipe.player;
import android.app.Activity;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
+import android.widget.PopupMenu;
import android.widget.SeekBar;
-
-import com.devbrackets.android.exomedia.listener.OnCompletionListener;
-import com.devbrackets.android.exomedia.listener.OnPreparedListener;
-import com.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener;
-import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
-import com.devbrackets.android.exomedia.ui.widget.VideoControlsMobile;
+import android.widget.TextView;
+import android.widget.Toast;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.PermissionHelper;
+import org.schabi.newpipe.util.ThemeHelper;
-public class ExoPlayerActivity extends Activity implements OnPreparedListener, OnCompletionListener {
- private static final String TAG = "ExoPlayerActivity";
- private static final boolean DEBUG = false;
- private EMVideoView videoView;
- private CustomVideoControls videoControls;
+/**
+ * Activity Player implementing AbstractPlayer
+ *
+ * @author mauriciocolli
+ */
+public class ExoPlayerActivity extends Activity {
+ private static final String TAG = ".ExoPlayerActivity";
+ private static final boolean DEBUG = AbstractPlayer.DEBUG;
- public static final String VIDEO_TITLE = "video_title";
- public static final String CHANNEL_NAME = "channel_name";
- private String videoTitle = "";
- private volatile String channelName = "";
- private int lastPosition;
- private boolean isFinished;
+ private AudioManager audioManager;
+ private BroadcastReceiver broadcastReceiver;
+ private GestureDetector gestureDetector;
+
+ private final Runnable hideUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hideSystemUi();
+ }
+ };
+ private boolean activityPaused;
+
+ private AbstractPlayerImpl playerImpl;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Activity LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
@Override
- public void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+ ThemeHelper.setTheme(this, false);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ if (getIntent() == null) {
+ Toast.makeText(this, R.string.general_error, Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
setContentView(R.layout.activity_exo_player);
- videoView = (EMVideoView) findViewById(R.id.emVideoView);
+ playerImpl = new AbstractPlayerImpl();
+ playerImpl.setup(findViewById(android.R.id.content));
+ initReceiver();
+ playerImpl.handleIntent(getIntent());
}
@Override
- protected void onStart() {
- super.onStart();
- Intent intent = getIntent();
- videoTitle = intent.getStringExtra(VIDEO_TITLE);
- channelName = intent.getStringExtra(CHANNEL_NAME);
- videoView.setOnPreparedListener(this);
- videoView.setOnCompletionListener(this);
- videoView.setVideoURI(intent.getData());
-
- videoControls = new CustomVideoControls(this);
- videoControls.setTitle(videoTitle);
- videoControls.setSubTitle(channelName);
-
- //We don't need these button until the playlist or queue is implemented
- videoControls.setNextButtonRemoved(true);
- videoControls.setPreviousButtonRemoved(true);
-
- videoControls.setVisibilityListener(new VideoControlsVisibilityListener() {
- @Override
- public void onControlsShown() {
- if (DEBUG) Log.d(TAG, "------------ onControlsShown() called");
- showSystemUi();
- }
-
- @Override
- public void onControlsHidden() {
- if (DEBUG) Log.d(TAG, "------------ onControlsHidden() called");
- hideSystemUi();
- }
- });
- videoView.setControls(videoControls);
+ protected void onNewIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
+ super.onNewIntent(intent);
+ playerImpl.handleIntent(intent);
}
@Override
- public void onPrepared() {
- if (DEBUG) Log.d(TAG, "onPrepared() called");
- videoView.start();
+ public void onBackPressed() {
+ if (DEBUG) Log.d(TAG, "onBackPressed() called");
+ super.onBackPressed();
+ if (playerImpl.isStartedFromNewPipe()) NavStack.getInstance().openDetailActivity(this, playerImpl.getVideoUrl(), 0);
+ if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
}
@Override
- public void onCompletion() {
- if (DEBUG) Log.d(TAG, "onCompletion() called");
-// videoView.getVideoControls().setButtonListener();
- //videoView.restart();
- videoControls.setRewindButtonRemoved(true);
- videoControls.setFastForwardButtonRemoved(true);
- isFinished = true;
- videoControls.getSeekBar().setEnabled(false);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- videoView.stopPlayback();
- lastPosition = videoView.getCurrentPosition();
+ protected void onStop() {
+ super.onStop();
+ if (DEBUG) Log.d(TAG, "onStop() called");
+ activityPaused = true;
+ playerImpl.destroy();
+ playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
}
@Override
protected void onResume() {
super.onResume();
- if (lastPosition > 0) videoView.seekTo(lastPosition);
+ if (DEBUG) Log.d(TAG, "onResume() called");
+ if (activityPaused) {
+ //playerImpl.getPlayer().setPlayWhenReady(true);
+ playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
+ playerImpl.initPlayer();
+ playerImpl.playVideo(playerImpl.getSelectedStreamUri(), false);
+ activityPaused = false;
+ }
}
@Override
protected void onDestroy() {
super.onDestroy();
- videoView.stopPlayback();
+ if (DEBUG) Log.d(TAG, "onDestroy() called");
+ if (playerImpl != null) playerImpl.destroy();
+ if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void initReceiver() {
+ if (DEBUG) Log.d(TAG, "initReceiver() called");
+ broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
+ switch (intent.getAction()) {
+ case AbstractPlayer.ACTION_UPDATE_THUMB:
+ playerImpl.onUpdateThumbnail(intent);
+ break;
+ }
+ }
+ };
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
+ registerReceiver(broadcastReceiver, intentFilter);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
private void showSystemUi() {
if (DEBUG) Log.d(TAG, "showSystemUi() called");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ );
+ } else getWindow().getDecorView().setSystemUiVisibility(0);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- getWindow().getDecorView().setSystemUiVisibility(0);
}
private void hideSystemUi() {
if (DEBUG) Log.d(TAG, "hideSystemUi() called");
- if (android.os.Build.VERSION.SDK_INT >= 17) {
- getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
+ int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_IMMERSIVE
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) visibility |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ getWindow().getDecorView().setSystemUiVisibility(visibility);
}
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
- private class CustomVideoControls extends VideoControlsMobile {
- protected static final int FAST_FORWARD_REWIND_AMOUNT = 8000;
+ ///////////////////////////////////////////////////////////////////////////
- protected ImageButton fastForwardButton;
- protected ImageButton rewindButton;
+ @SuppressWarnings({"unused", "WeakerAccess"})
+ private class AbstractPlayerImpl extends AbstractPlayer {
+ private TextView titleTextView;
+ private TextView channelTextView;
+ private TextView volumeTextView;
+ private TextView brightnessTextView;
+ private ImageButton repeatButton;
- public CustomVideoControls(Context context) {
- super(context);
+ private ImageButton playPauseButton;
+
+ AbstractPlayerImpl() {
+ super("AbstractPlayerImpl" + ExoPlayerActivity.TAG, ExoPlayerActivity.this);
}
@Override
- protected int getLayoutResource() {
- return R.layout.exomedia_custom_controls;
+ public void initViews(View rootView) {
+ super.initViews(rootView);
+ this.titleTextView = (TextView) rootView.findViewById(R.id.titleTextView);
+ this.channelTextView = (TextView) rootView.findViewById(R.id.channelTextView);
+ this.volumeTextView = (TextView) rootView.findViewById(R.id.volumeTextView);
+ this.brightnessTextView = (TextView) rootView.findViewById(R.id.brightnessTextView);
+ this.repeatButton = (ImageButton) rootView.findViewById(R.id.repeatButton);
+
+ this.playPauseButton = (ImageButton) rootView.findViewById(R.id.playPauseButton);
+
+ // Due to a bug on lower API, lets set the alpha instead of using a drawable
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) repeatButton.setImageAlpha(77);
+ else { //noinspection deprecation
+ repeatButton.setAlpha(77);
+ }
+
}
@Override
- protected void retrieveViews() {
- super.retrieveViews();
- rewindButton = (ImageButton) findViewById(R.id.exomedia_controls_frewind_btn);
- fastForwardButton = (ImageButton) findViewById(R.id.exomedia_controls_fforward_btn);
+ public void initListeners() {
+ super.initListeners();
+
+ MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
+ gestureDetector = new GestureDetector(context, listener);
+ gestureDetector.setIsLongpressEnabled(false);
+ playerImpl.getRootView().setOnTouchListener(listener);
+
+ repeatButton.setOnClickListener(this);
+ playPauseButton.setOnClickListener(this);
}
@Override
- protected void registerListeners() {
- super.registerListeners();
- rewindButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onRewindClicked();
- }
- });
- fastForwardButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onFastForwardClicked();
- }
- });
- }
-
- public boolean onFastForwardClicked() {
- if (videoView == null) return false;
-
- int newPosition = videoView.getCurrentPosition() + FAST_FORWARD_REWIND_AMOUNT;
- if (newPosition > seekBar.getMax()) newPosition = seekBar.getMax();
-
- performSeek(newPosition);
- return true;
- }
-
- public boolean onRewindClicked() {
- if (videoView == null) return false;
-
- int newPosition = videoView.getCurrentPosition() - FAST_FORWARD_REWIND_AMOUNT;
- if (newPosition < 0) newPosition = 0;
-
- performSeek(newPosition);
- return true;
+ public void handleIntent(Intent intent) {
+ super.handleIntent(intent);
+ titleTextView.setText(getVideoTitle());
+ channelTextView.setText(getChannelName());
}
@Override
- public void setFastForwardButtonRemoved(boolean removed) {
- fastForwardButton.setVisibility(removed ? View.GONE : View.VISIBLE);
+ public void playVideo(Uri videoURI, boolean autoPlay) {
+ super.playVideo(videoURI, autoPlay);
+ playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white);
}
@Override
- public void setRewindButtonRemoved(boolean removed) {
- rewindButton.setVisibility(removed ? View.GONE : View.VISIBLE);
+ public void onFullScreenButtonClicked() {
+ if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
+ if (playerImpl.getPlayer() == null) return;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && !PermissionHelper.checkSystemAlertWindowPermission(ExoPlayerActivity.this)) {
+ Toast.makeText(ExoPlayerActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ Intent i = new Intent(ExoPlayerActivity.this, PopupVideoPlayer.class);
+ i.putExtra(AbstractPlayer.VIDEO_TITLE, getVideoTitle())
+ .putExtra(AbstractPlayer.CHANNEL_NAME, getChannelName())
+ .putExtra(AbstractPlayer.VIDEO_URL, getVideoUrl())
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, getSelectedIndexStream())
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, getVideoStreamsList())
+ .putExtra(AbstractPlayer.START_POSITION, ((int) getPlayer().getCurrentPosition()));
+ context.startService(i);
+ ((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
+ if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
+ ExoPlayerActivity.this.finish();
}
@Override
- protected void onPlayPauseClick() {
- super.onPlayPauseClick();
- if (videoView == null) return;
- if (DEBUG) Log.d(TAG, "onPlayPauseClick() called" + videoView.getDuration() + " position= " + videoView.getCurrentPosition());
- if (isFinished) {
- videoView.restart();
- setRewindButtonRemoved(false);
- setFastForwardButtonRemoved(false);
- isFinished = false;
- seekBar.setEnabled(true);
+ @SuppressWarnings("deprecation")
+ public void onRepeatClicked() {
+ super.onRepeatClicked();
+ if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
+ switch (getCurrentRepeatMode()) {
+ case REPEAT_DISABLED:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) repeatButton.setImageAlpha(77);
+ else repeatButton.setAlpha(77);
+
+ break;
+ case REPEAT_ONE:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) repeatButton.setImageAlpha(255);
+ else repeatButton.setAlpha(255);
+
+ break;
+ case REPEAT_ALL:
+ // Waiting :)
+ break;
}
}
- private void performSeek(int newPosition) {
- internalListener.onSeekEnded(newPosition);
+ @Override
+ public void onClick(View v) {
+ super.onClick(v);
+ if (v.getId() == repeatButton.getId()) onRepeatClicked();
+ else if (v.getId() == playPauseButton.getId()) onVideoPlayPause();
+
+ if (getCurrentState() != STATE_COMPLETED) {
+ animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() {
+ @Override
+ public void run() {
+ if (getCurrentState() == STATE_PLAYING && !playerImpl.isQualityMenuVisible()) {
+ animateView(playerImpl.getControlsRoot(), false, 300, DEFAULT_CONTROLS_HIDE_TIME, true);
+ }
+ }
+ }, false);
+ }
}
- public SeekBar getSeekBar() {
- return seekBar;
+ @Override
+ public void onVideoPlayPause() {
+ super.onVideoPlayPause();
+ if (getPlayer().getPlayWhenReady()) {
+ animateView(playPauseButton, false, 80, 0, new Runnable() {
+ @Override
+ public void run() {
+ playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ animateView(playPauseButton, true, 200, 0);
+ }
+ });
+ } else {
+ animateView(playPauseButton, false, 80, 0, new Runnable() {
+ @Override
+ public void run() {
+ playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
+ animateView(playPauseButton, true, 200, 0);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ super.onStopTrackingTouch(seekBar);
+ if (playerImpl.wasPlaying()) {
+ hideSystemUi();
+ playerImpl.getControlsRoot().setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ super.onDismiss(menu);
+ if (isPlaying()) animateView(getControlsRoot(), false, 500, 0, true);
+ }
+
+ @Override
+ public void onError() {
+ Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // States
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onLoading() {
+ super.onLoading();
+ hideSystemUi();
+ playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ }
+
+ @Override
+ public void onPaused() {
+ super.onPaused();
+ animateView(playPauseButton, true, 100, 0);
+ showSystemUi();
+ }
+
+ @Override
+ public void onPausedSeek() {
+ super.onPausedSeek();
+ animateView(playPauseButton, false, 100, 0);
+ }
+
+ @Override
+ public void onPlaying() {
+ super.onPlaying();
+ animateView(playPauseButton, true, 500, 0);
+ }
+
+ @Override
+ public void onCompleted() {
+ if (getCurrentRepeatMode() == RepeatMode.REPEAT_ONE) {
+ playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ } else {
+ showSystemUi();
+ animateView(playPauseButton, false, 0, 0, new Runnable() {
+ @Override
+ public void run() {
+ playPauseButton.setImageResource(R.drawable.ic_replay_white);
+ animateView(playPauseButton, true, 300, 0);
+ }
+ });
+ }
+ super.onCompleted();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void animateView(View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) {
+ //if (execOnEnd == null) playerImpl.setDefaultAnimationEnd(hideUiRunnable);
+
+ if (hideUi && execOnEnd != null) {
+ Runnable combinedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ execOnEnd.run();
+ hideUiRunnable.run();
+ }
+ };
+ super.animateView(view, enterOrExit, duration, delay, combinedRunnable, true);
+ } else super.animateView(view, enterOrExit, duration, delay, hideUi ? hideUiRunnable : execOnEnd, hideUi);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Getters
+ ///////////////////////////////////////////////////////////////////////////
+
+ public TextView getTitleTextView() {
+ return titleTextView;
+ }
+
+ public TextView getChannelTextView() {
+ return channelTextView;
+ }
+
+ public TextView getVolumeTextView() {
+ return volumeTextView;
+ }
+
+ public TextView getBrightnessTextView() {
+ return brightnessTextView;
+ }
+
+ public ImageButton getRepeatButton() {
+ return repeatButton;
+ }
+
+ public ImageButton getPlayPauseButton() {
+ return playPauseButton;
}
}
-}
+
+ private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
+ private boolean isMoving;
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
+ if (!playerImpl.isPlaying()) return false;
+ if (e.getX() > playerImpl.getRootView().getWidth() / 2) playerImpl.onFastForward();
+ else playerImpl.onFastRewind();
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
+ if (playerImpl.getCurrentState() != StateInterface.STATE_PLAYING) return true;
+
+ if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true);
+ else {
+ playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
+ @Override
+ public void run() {
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
+ }
+ });
+ showSystemUi();
+ }
+ return true;
+ }
+
+ private final float stepsBrightness = 21, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
+ private float currentBrightness = .5f;
+
+ private int currentVolume, maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+ private final String brightnessUnicode = new String(Character.toChars(0x2600));
+ // private final String volumeUnicode = new String(Character.toChars(0x1F50A));
+ private final String volumeUnicode = new String(Character.toChars(0x1F508));
+
+
+ private final int MOVEMENT_THRESHOLD = 40;
+ private final int eventsThreshold = 3;
+ private boolean triggered = false;
+ private int eventsNum;
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && false) Log.d(TAG, "ExoPlayerActivity.onScroll = " +
+ ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
+ ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
+ ", distanceXy = [" + distanceX + ", " + distanceY + "]");
+ float abs = Math.abs(e2.getY() - e1.getY());
+ if (!triggered) {
+ triggered = abs > MOVEMENT_THRESHOLD;
+ return false;
+ }
+
+ if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == StateInterface.STATE_COMPLETED) return false;
+ isMoving = true;
+// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
+ boolean up = distanceY > 0;
+
+
+ if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
+ currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + (up ? 1 : -1);
+ if (currentVolume >= maxVolume) currentVolume = maxVolume;
+ if (currentVolume <= 0) currentVolume = 0;
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
+
+ if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
+ playerImpl.getVolumeTextView().setText(volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%");
+
+ if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), true, 200, 0);
+ if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);
+ } else {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ currentBrightness += up ? stepBrightness : -stepBrightness;
+ if (currentBrightness >= 1f) currentBrightness = 1f;
+ if (currentBrightness <= minBrightness) currentBrightness = minBrightness;
+
+ lp.screenBrightness = currentBrightness;
+ getWindow().setAttributes(lp);
+ if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness);
+ int brightnessNormalized = Math.round(currentBrightness * 100);
+
+ playerImpl.getBrightnessTextView().setText(brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%");
+
+ if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), true, 200, 0);
+ if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
+ }
+ return true;
+ }
+
+ private void onScrollEnd() {
+ if (DEBUG) Log.d(TAG, "onScrollEnd() called");
+ triggered = false;
+ eventsNum = 0;
+ /* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
+ if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/
+ if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200);
+ if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
+
+ if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ gestureDetector.onTouchEvent(event);
+ if (event.getAction() == MotionEvent.ACTION_UP && isMoving) {
+ isMoving = false;
+ onScrollEnd();
+ }
+ return true;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java
index f13e73b49..49da537ac 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java
@@ -5,9 +5,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
-import android.graphics.drawable.Drawable;
-import android.media.MediaPlayer;
import android.media.AudioManager;
+import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -28,7 +27,6 @@ import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.VideoView;
-import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
/**
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index e396ac1b6..661cb1632 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -1,10 +1,5 @@
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.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -13,17 +8,15 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.PixelFormat;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
-import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
@@ -31,17 +24,10 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
+import android.widget.PopupMenu;
import android.widget.RemoteViews;
-import android.widget.SeekBar;
import android.widget.Toast;
-import com.devbrackets.android.exomedia.listener.OnCompletionListener;
-import com.devbrackets.android.exomedia.listener.OnErrorListener;
-import com.devbrackets.android.exomedia.listener.OnPreparedListener;
-import com.devbrackets.android.exomedia.listener.OnSeekCompletionListener;
-import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
-import com.devbrackets.android.exomedia.util.Repeater;
-import com.devbrackets.android.exomedia.util.TimeFormatUtil;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
@@ -56,210 +42,204 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
-import org.schabi.newpipe.player.popup.PopupViewHolder;
-import org.schabi.newpipe.player.popup.StateInterface;
import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.ThemeHelper;
import java.io.IOException;
+import java.util.ArrayList;
-public class PopupVideoPlayer extends Service implements StateInterface {
+/**
+ * Service Popup Player implementing AbstractPlayer
+ *
+ * @author mauriciocolli
+ */
+public class PopupVideoPlayer extends Service {
private static final String TAG = ".PopupVideoPlayer";
- private static final boolean DEBUG = false;
- private static int CURRENT_STATE = -1;
+ private static final boolean DEBUG = AbstractPlayer.DEBUG;
private static final int NOTIFICATION_ID = 40028922;
- protected static final int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
- protected static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
+ public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
+ public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
+ public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
+ public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
private BroadcastReceiver broadcastReceiver;
- private InternalListener internalListener;
private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector;
- private ValueAnimator controlViewAnimator;
- private PopupViewHolder viewHolder;
- private EMVideoView emVideoView;
private float screenWidth, screenHeight;
private float popupWidth, popupHeight;
- private float currentPopupHeight = 200;
+ private float currentPopupHeight = 110.0f * Resources.getSystem().getDisplayMetrics().density;
//private float minimumHeight = 100; // TODO: Use it when implementing the resize of the popup
- public static final String VIDEO_URL = "video_url";
- public static final String STREAM_URL = "stream_url";
- public static final String VIDEO_TITLE = "video_title";
- public static final String CHANNEL_NAME = "channel_name";
-
+ private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
- private Uri streamUri;
- private String videoUrl = "";
- private String videoTitle = "";
- private volatile String channelName = "";
private ImageLoader imageLoader = ImageLoader.getInstance();
- private DisplayImageOptions displayImageOptions =
- new DisplayImageOptions.Builder().cacheInMemory(true).build();
- private volatile Bitmap videoThumbnail;
+ private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
- private Repeater progressPollRepeater = new Repeater();
- private SharedPreferences sharedPreferences;
+ private AbstractPlayerImpl playerImpl;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Service LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate() {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
- internalListener = new InternalListener();
- viewHolder = new PopupViewHolder(null);
- progressPollRepeater.setRepeatListener(internalListener);
- progressPollRepeater.setRepeaterDelay(500);
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
initReceiver();
+
+ playerImpl = new AbstractPlayerImpl();
+ ThemeHelper.setTheme(this, false);
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public int onStartCommand(final Intent intent, int flags, int startId) {
+ if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
+ if (playerImpl.getPlayer() == null) initPopup();
+ if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
+
+ if (imageLoader != null) imageLoader.clearMemoryCache();
+ if (intent.getStringExtra(NavStack.URL) != null) {
+ playerImpl.setStartedFromNewPipe(false);
+ Thread fetcher = new Thread(new FetcherRunnable(intent));
+ fetcher.start();
+ } else {
+ playerImpl.setStartedFromNewPipe(true);
+ playerImpl.handleIntent(intent);
+ }
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ updateScreenSize();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy() called");
+ stopForeground(true);
+ if (playerImpl != null) {
+ playerImpl.destroy();
+ if (playerImpl.getRootView() != null) windowManager.removeView(playerImpl.getRootView());
+ }
+ if (imageLoader != null) imageLoader.clearMemoryCache();
+ if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
+ if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
private void initReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG)
- Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
+ if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
switch (intent.getAction()) {
- case InternalListener.ACTION_CLOSE:
- internalListener.onVideoClose();
+ case ACTION_CLOSE:
+ onVideoClose();
break;
- case InternalListener.ACTION_PLAY_PAUSE:
- internalListener.onVideoPlayPause();
+ case ACTION_PLAY_PAUSE:
+ playerImpl.onVideoPlayPause();
break;
- case InternalListener.ACTION_OPEN_DETAIL:
- internalListener.onOpenDetail(PopupVideoPlayer.this, videoUrl);
+ case ACTION_OPEN_DETAIL:
+ onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl());
break;
- case InternalListener.ACTION_UPDATE_THUMB:
- internalListener.onUpdateThumbnail(intent);
+ case ACTION_REPEAT:
+ playerImpl.onRepeatClicked();
+ break;
+ case AbstractPlayer.ACTION_UPDATE_THUMB:
+ playerImpl.onUpdateThumbnail(intent);
break;
}
}
};
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(InternalListener.ACTION_CLOSE);
- intentFilter.addAction(InternalListener.ACTION_PLAY_PAUSE);
- intentFilter.addAction(InternalListener.ACTION_OPEN_DETAIL);
- intentFilter.addAction(InternalListener.ACTION_UPDATE_THUMB);
+ intentFilter.addAction(ACTION_CLOSE);
+ intentFilter.addAction(ACTION_PLAY_PAUSE);
+ intentFilter.addAction(ACTION_OPEN_DETAIL);
+ intentFilter.addAction(ACTION_REPEAT);
+ intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
registerReceiver(broadcastReceiver, intentFilter);
}
- @SuppressLint({"RtlHardcoded"})
+ @SuppressLint("RtlHardcoded")
private void initPopup() {
if (DEBUG) Log.d(TAG, "initPopup() called");
View rootView = View.inflate(this, R.layout.player_popup, null);
- viewHolder = new PopupViewHolder(rootView);
- viewHolder.getPlaybackSeekBar().setOnSeekBarChangeListener(internalListener);
- emVideoView = viewHolder.getVideoView();
- emVideoView.setOnPreparedListener(internalListener);
- emVideoView.setOnCompletionListener(internalListener);
- emVideoView.setOnErrorListener(internalListener);
- emVideoView.setOnSeekCompletionListener(internalListener);
+ playerImpl.setup(rootView);
+
+ updateScreenSize();
windowLayoutParams = new WindowManager.LayoutParams(
(int) getMinimumVideoWidth(currentPopupHeight), (int) currentPopupHeight,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
+
windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
gestureDetector = new GestureDetector(this, listener);
gestureDetector.setIsLongpressEnabled(false);
rootView.setOnTouchListener(listener);
- updateScreenSize();
-
+ playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width);
+ playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height);
windowManager.addView(rootView, windowLayoutParams);
}
- @Override
- public int onStartCommand(final Intent intent, int flags, int startId) {
- if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
- if (emVideoView == null) initPopup();
-
- if (intent.getStringExtra(NavStack.URL) != null) {
- Thread fetcher = new Thread(new FetcherRunnable(intent));
- fetcher.start();
- } else {
- if (imageLoader != null) imageLoader.clearMemoryCache();
- streamUri = Uri.parse(intent.getStringExtra(STREAM_URL));
- videoUrl = intent.getStringExtra(VIDEO_URL);
- videoTitle = intent.getStringExtra(VIDEO_TITLE);
- channelName = intent.getStringExtra(CHANNEL_NAME);
- try {
- videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
- } catch (Exception e) {
- e.printStackTrace();
- }
- playVideo(streamUri);
- }
- return START_NOT_STICKY;
- }
-
- private float getMinimumVideoWidth(float height) {
- float width = height * (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
- if (DEBUG) Log.d(TAG, "getMinimumVideoWidth() called with: height = [" + height + "], returned: " + width);
- return width;
- }
-
- private void updateScreenSize() {
- DisplayMetrics metrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(metrics);
-
- screenWidth = metrics.widthPixels;
- screenHeight = metrics.heightPixels;
- if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight);
- }
-
- private void seekBy(int milliSeconds) {
- if (emVideoView == null) return;
- int progress = emVideoView.getCurrentPosition() + milliSeconds;
- emVideoView.seekTo(progress);
- }
-
- private void playVideo(Uri videoURI) {
- if (DEBUG) Log.d(TAG, "playVideo() called with: streamUri = [" + streamUri + "]");
-
- changeState(STATE_LOADING);
-
- windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
- windowManager.updateViewLayout(viewHolder.getRootView(), windowLayoutParams);
-
- if (videoURI == null || emVideoView == null || viewHolder.getRootView() == null) {
- Toast.makeText(this, "Failed to play this video", Toast.LENGTH_SHORT).show();
- stopSelf();
- return;
- }
- if (emVideoView.isPlaying()) emVideoView.stopPlayback();
- emVideoView.setVideoURI(videoURI);
-
- notBuilder = createNotification();
- startForeground(NOTIFICATION_ID, notBuilder.build());
- notificationManager.notify(NOTIFICATION_ID, this.notBuilder.build());
- }
+ /*//////////////////////////////////////////////////////////////////////////
+ // Notification
+ //////////////////////////////////////////////////////////////////////////*/
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
- if (videoThumbnail != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
+
+ if (playerImpl.getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
else notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
+ notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
+ notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getChannelName());
+
notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(InternalListener.ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationStop,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(InternalListener.ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
- notRemoteView.setTextViewText(R.id.notificationSongName, videoTitle);
- notRemoteView.setTextViewText(R.id.notificationArtist, channelName);
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(InternalListener.ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
+ notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat,
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
+
+ switch (playerImpl.getCurrentRepeatMode()) {
+ case REPEAT_DISABLED:
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
+ break;
+ case REPEAT_ONE:
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
+ break;
+ case REPEAT_ALL:
+ // Waiting :)
+ break;
+ }
return new NotificationCompat.Builder(this)
.setOngoing(true)
- .setSmallIcon(R.drawable.ic_play_arrow_white_48dp)
+ .setSmallIcon(R.drawable.ic_play_arrow_white)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notRemoteView);
}
@@ -276,384 +256,175 @@ public class PopupVideoPlayer extends Service implements StateInterface {
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
- /**
- * 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
- */
- private 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 (viewHolder.getControlAnimationView().getVisibility() == View.VISIBLE) {
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(viewHolder.getControlAnimationView(),
- PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
- PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
- ).setDuration(300);
- controlViewAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- viewHolder.getControlAnimationView().setVisibility(View.GONE);
- }
- });
- controlViewAnimator.start();
- }
- return;
- }
+ /*//////////////////////////////////////////////////////////////////////////
+ // Misc
+ //////////////////////////////////////////////////////////////////////////*/
- float scaleFrom = goneOnEnd ? 1f : 1f, scaleTo = goneOnEnd ? 1.8f : 1.4f;
- float alphaFrom = goneOnEnd ? 1f : 0f, alphaTo = goneOnEnd ? 0f : 1f;
-
-
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(viewHolder.getControlAnimationView(),
- 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) viewHolder.getControlAnimationView().setVisibility(View.GONE);
- else viewHolder.getControlAnimationView().setVisibility(View.VISIBLE);
- }
- });
-
-
- viewHolder.getControlAnimationView().setVisibility(View.VISIBLE);
- viewHolder.getControlAnimationView().setImageDrawable(ContextCompat.getDrawable(PopupVideoPlayer.this, drawableId));
- controlViewAnimator.start();
+ public void onVideoClose() {
+ if (DEBUG) Log.d(TAG, "onVideoClose() called");
+ stopSelf();
}
- /**
- * Animate the view
- *
- * @param enterOrExit true to enter, false to exit
- * @param duration how long the animation will take, in milliseconds
- * @param delay how long the animation will wait to start, in milliseconds
- */
- private void animateView(final View view, final boolean enterOrExit, long duration, long delay) {
- if (DEBUG) Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "]");
- if (view.getVisibility() == View.VISIBLE && enterOrExit) {
- if (DEBUG) Log.d(TAG, "animateLoadingPanel() > view.getVisibility() == View.VISIBLE && enterOrExit");
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
- return;
- }
-
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
-
- if (view == viewHolder.getControlsRoot()) {
- if (enterOrExit) {
- view.setAlpha(0f);
- view.animate().alpha(1f).setDuration(duration).setStartDelay(delay).setListener(null).start();
- } else {
- view.setAlpha(1f);
- view.animate().alpha(0f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.GONE);
- }
- })
- .start();
- }
- return;
- }
-
- if (enterOrExit) {
- view.setAlpha(0f);
- view.setScaleX(.8f);
- view.setScaleY(.8f);
- view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay).setListener(null).start();
- } else {
- view.setAlpha(1f);
- view.setScaleX(1f);
- view.setScaleY(1f);
- view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.GONE);
- }
- })
- .start();
- }
+ public void onOpenDetail(Context context, String videoUrl) {
+ if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
+ Intent i = new Intent(context, VideoItemDetailActivity.class);
+ i.putExtra(NavStack.SERVICE_ID, 0)
+ .putExtra(NavStack.URL, videoUrl)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(i);
+ //NavStack.getInstance().openDetailActivity(context, videoUrl, 0);
}
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- updateScreenSize();
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private float getMinimumVideoWidth(float height) {
+ float width = height * (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
+ if (DEBUG) Log.d(TAG, "getMinimumVideoWidth() called with: height = [" + height + "], returned: " + width);
+ return width;
}
- @Override
- public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy() called");
- stopForeground(true);
- if (emVideoView != null) emVideoView.stopPlayback();
- if (imageLoader != null) imageLoader.clearMemoryCache();
- if (viewHolder.getRootView() != null) windowManager.removeView(viewHolder.getRootView());
- if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
- if (progressPollRepeater != null) {
- progressPollRepeater.stop();
- progressPollRepeater.setRepeatListener(null);
- }
- if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
- }
+ private void updateScreenSize() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(metrics);
- @Override
- public IBinder onBind(Intent intent) {
- return null;
+ screenWidth = metrics.widthPixels;
+ screenHeight = metrics.heightPixels;
+ if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight);
}
- ///////////////////////////////////////////////////////////////////////////
- // States Implementation
///////////////////////////////////////////////////////////////////////////
- @Override
- public void changeState(int state) {
- if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
- CURRENT_STATE = state;
- switch (state) {
- case STATE_LOADING:
- onLoading();
- break;
- case STATE_PLAYING:
- onPlaying();
- break;
- case STATE_PAUSED:
- onPaused();
- break;
- case STATE_PAUSED_SEEK:
- onPausedSeek();
- break;
- case STATE_COMPLETED:
- onCompleted();
- break;
+ private class AbstractPlayerImpl extends AbstractPlayer {
+ AbstractPlayerImpl() {
+ super("AbstractPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
}
- }
-
- @Override
- public void onLoading() {
- if (DEBUG) Log.d(TAG, "onLoading() called");
- updateNotification(R.drawable.ic_play_arrow_white_48dp);
-
- showAndAnimateControl(-1, true);
- viewHolder.getPlaybackSeekBar().setEnabled(true);
- viewHolder.getPlaybackSeekBar().setProgress(0);
- viewHolder.getLoadingPanel().setBackgroundColor(Color.BLACK);
- animateView(viewHolder.getLoadingPanel(), true, 500, 0);
- viewHolder.getEndScreen().setVisibility(View.GONE);
- viewHolder.getControlsRoot().setVisibility(View.GONE);
- }
-
- @Override
- public void onPlaying() {
- if (DEBUG) Log.d(TAG, "onPlaying() called");
- updateNotification(R.drawable.ic_pause_white_24dp);
-
- showAndAnimateControl(-1, true);
- viewHolder.getLoadingPanel().setVisibility(View.GONE);
- animateView(viewHolder.getControlsRoot(), false, 500, DEFAULT_CONTROLS_HIDE_TIME);
- }
-
- @Override
- public void onPaused() {
- if (DEBUG) Log.d(TAG, "onPaused() called");
- updateNotification(R.drawable.ic_play_arrow_white_48dp);
-
- showAndAnimateControl(R.drawable.ic_play_arrow_white_48dp, false);
- animateView(viewHolder.getControlsRoot(), true, 500, 100);
- viewHolder.getLoadingPanel().setVisibility(View.GONE);
- }
-
- @Override
- public void onPausedSeek() {
- if (DEBUG) Log.d(TAG, "onPausedSeek() called");
- updateNotification(R.drawable.ic_play_arrow_white_48dp);
-
- showAndAnimateControl(-1, true);
- viewHolder.getLoadingPanel().setBackgroundColor(Color.TRANSPARENT);
- animateView(viewHolder.getLoadingPanel(), true, 300, 0);
- }
-
- @Override
- public void onCompleted() {
- if (DEBUG) Log.d(TAG, "onCompleted() called");
- updateNotification(R.drawable.ic_replay_white);
- showAndAnimateControl(R.drawable.ic_replay_white, false);
- animateView(viewHolder.getControlsRoot(), true, 500, 0);
- animateView(viewHolder.getEndScreen(), true, 200, 0);
- viewHolder.getLoadingPanel().setVisibility(View.GONE);
- viewHolder.getPlaybackSeekBar().setEnabled(false);
- viewHolder.getPlaybackCurrentTime().setText(viewHolder.getPlaybackEndTime().getText());
- if (videoThumbnail != null) viewHolder.getEndScreen().setImageBitmap(videoThumbnail);
- }
-
- /**
- * This class joins all the necessary listeners
- */
- @SuppressWarnings({"WeakerAccess"})
- public class InternalListener implements SeekBar.OnSeekBarChangeListener, OnPreparedListener, OnSeekCompletionListener, OnCompletionListener, OnErrorListener, Repeater.RepeatListener {
- public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
- public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
- public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
- public static final String ACTION_UPDATE_THUMB = "org.schabi.newpipe.player.PopupVideoPlayer.UPDATE_THUMBNAIL";
@Override
- public void onPrepared() {
- if (DEBUG) Log.d(TAG, "onPrepared() called");
- viewHolder.getPlaybackSeekBar().setMax(emVideoView.getDuration());
- viewHolder.getPlaybackEndTime().setText(TimeFormatUtil.formatMs(emVideoView.getDuration()));
+ public void playVideo(Uri videoURI, boolean autoPlay) {
+ super.playVideo(videoURI, autoPlay);
- changeState(STATE_PLAYING);
- progressPollRepeater.start();
- emVideoView.start();
+ windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
+ windowManager.updateViewLayout(getRootView(), windowLayoutParams);
+ notBuilder = createNotification();
+ startForeground(NOTIFICATION_ID, notBuilder.build());
+ notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
- public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
- if (viewHolder.isControlsVisible() && CURRENT_STATE != STATE_PAUSED_SEEK) {
- viewHolder.getPlaybackSeekBar().setProgress(currentProgress);
- viewHolder.getPlaybackCurrentTime().setText(TimeFormatUtil.formatMs(currentProgress));
- viewHolder.getPlaybackSeekBar().setSecondaryProgress((int) (viewHolder.getPlaybackSeekBar().getMax() * ((float) bufferPercent / 100)));
+ @Override
+ public void onFullScreenButtonClicked() {
+ if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
+ Intent intent;
+ //if (getSharedPreferences().getBoolean(getResources().getString(R.string.use_exoplayer_key), false)) {
+ // TODO: Remove this check when ExoPlayer is the default
+ // For now just disable the non-exoplayer player
+ //noinspection ConstantConditions,ConstantIfStatement
+ if (true) {
+ intent = new Intent(PopupVideoPlayer.this, ExoPlayerActivity.class)
+ .putExtra(AbstractPlayer.VIDEO_TITLE, getVideoTitle())
+ .putExtra(AbstractPlayer.VIDEO_URL, getVideoUrl())
+ .putExtra(AbstractPlayer.CHANNEL_NAME, getChannelName())
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, getSelectedIndexStream())
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, getVideoStreamsList())
+ .putExtra(AbstractPlayer.START_POSITION, ((int) getPlayer().getCurrentPosition()));
+ if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(AbstractPlayer.STARTED_FROM_NEWPIPE, false);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ } else {
+ intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
+ .putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
+ .putExtra(PlayVideoActivity.STREAM_URL, getSelectedStreamUri().toString())
+ .putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
+ .putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
- if (DEBUG && bufferPercent % 10 == 0) { //Limit log
- Log.d(TAG, "updateProgress() called with: isVisible = " + viewHolder.isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
+ context.startActivity(intent);
+ stopSelf();
+ }
+
+ @Override
+ public void onRepeatClicked() {
+ super.onRepeatClicked();
+ switch (getCurrentRepeatMode()) {
+ case REPEAT_DISABLED:
+ // Drawable didn't work on low API :/
+ //notRemoteView.setImageViewResource(R.id.notificationRepeat, R.drawable.ic_repeat_disabled_white);
+ // Set the icon to 30% opacity - 255 (max) * .3
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
+ break;
+ case REPEAT_ONE:
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
+ break;
+ case REPEAT_ALL:
+ // Waiting :)
+ break;
}
- }
-
- public void onOpenDetail(Context context, String videoUrl) {
- if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
- Intent i = new Intent(context, VideoItemDetailActivity.class);
- i.putExtra(NavStack.SERVICE_ID, 0);
- i.putExtra(NavStack.URL, videoUrl);
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(i);
- //NavStack.getInstance().openDetailActivity(context, videoUrl, 0);
- }
-
- public void onUpdateThumbnail(Intent intent) {
- if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called");
- if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return;
- videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
- if (videoThumbnail != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
updateNotification(-1);
}
- public void onVideoClose() {
- if (DEBUG) Log.d(TAG, "onVideoClose() called");
+ @Override
+ public void onUpdateThumbnail(Intent intent) {
+ super.onUpdateThumbnail(intent);
+ if (getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, getVideoThumbnail());
+ updateNotification(-1);
+ }
+
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ super.onDismiss(menu);
+ if (isPlaying()) animateView(getControlsRoot(), false, 500, 0);
+ }
+
+ @Override
+ public void onError() {
+ Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
stopSelf();
}
- public void onVideoPlayPause() {
- if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
- if (CURRENT_STATE == STATE_COMPLETED) {
- changeState(STATE_LOADING);
- emVideoView.restart();
- return;
- }
- if (emVideoView.isPlaying()) {
- emVideoView.pause();
- progressPollRepeater.stop();
- internalListener.onRepeat();
- changeState(STATE_PAUSED);
- } else {
- emVideoView.start();
- progressPollRepeater.start();
- changeState(STATE_PLAYING);
- }
- }
+ /*//////////////////////////////////////////////////////////////////////////
+ // States
+ //////////////////////////////////////////////////////////////////////////*/
- public void onFastRewind() {
- if (DEBUG) Log.d(TAG, "onFastRewind() called");
- seekBy(-FAST_FORWARD_REWIND_AMOUNT);
- internalListener.onRepeat();
- changeState(STATE_PAUSED_SEEK);
-
- showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
- }
-
- public void onFastForward() {
- if (DEBUG) Log.d(TAG, "onFastForward() called");
- seekBy(FAST_FORWARD_REWIND_AMOUNT);
- internalListener.onRepeat();
- changeState(STATE_PAUSED_SEEK);
-
- showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
+ @Override
+ public void onLoading() {
+ super.onLoading();
+ updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
- public void onSeekComplete() {
- if (DEBUG) Log.d(TAG, "onSeekComplete() called");
-
- if (!emVideoView.isPlaying()) emVideoView.start();
- changeState(STATE_PLAYING);
- /*if (emVideoView.isPlaying()) changeState(STATE_PLAYING);
- else changeState(STATE_PAUSED);*/
+ public void onPlaying() {
+ super.onPlaying();
+ updateNotification(R.drawable.ic_pause_white);
}
@Override
- public void onCompletion() {
- if (DEBUG) Log.d(TAG, "onCompletion() called");
- changeState(STATE_COMPLETED);
- progressPollRepeater.stop();
+ public void onBuffering() {
+ super.onBuffering();
+ updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
- public boolean onError() {
- if (DEBUG) Log.d(TAG, "onError() called");
- stopSelf();
- return true;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // SeekBar Listener
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (DEBUG) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + progress + "], fromUser = [" + fromUser + "]");
- viewHolder.getPlaybackCurrentTime().setText(TimeFormatUtil.formatMs(progress));
+ public void onPaused() {
+ super.onPaused();
+ updateNotification(R.drawable.ic_play_arrow_white);
+ showAndAnimateControl(R.drawable.ic_play_arrow_white, false);
}
@Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
-
- changeState(STATE_PAUSED_SEEK);
- if (emVideoView.isPlaying()) emVideoView.pause();
- animateView(viewHolder.getControlsRoot(), true, 300, 0);
- viewHolder.getControlsRoot().setAlpha(1f);
+ public void onPausedSeek() {
+ super.onPausedSeek();
+ updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (DEBUG) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + seekBar.getProgress() + "]");
- emVideoView.seekTo(seekBar.getProgress());
-
+ public void onCompleted() {
+ super.onCompleted();
+ updateNotification(R.drawable.ic_replay_white);
+ showAndAnimateControl(R.drawable.ic_replay_white, false);
}
- ///////////////////////////////////////////////////////////////////////////
- // Repeater Listener
- ///////////////////////////////////////////////////////////////////////////
-
- /**
- * Don't mistake this with anything related to the player itself, it's the {@link Repeater.RepeatListener#onRepeat}
- * It's used for pool the progress of the video
- */
- @Override
- public void onRepeat() {
- onUpdateProgress(emVideoView.getCurrentPosition(), emVideoView.getDuration(), emVideoView.getBufferPercentage());
- }
}
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
@@ -663,42 +434,33 @@ public class PopupVideoPlayer extends Service implements StateInterface {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
- if (!emVideoView.isPlaying()) return false;
- if (e.getX() > popupWidth / 2) internalListener.onFastForward();
- else internalListener.onFastRewind();
+ if (!playerImpl.isPlaying()) return false;
+ if (e.getX() > popupWidth / 2) playerImpl.onFastForward();
+ else playerImpl.onFastRewind();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
- if (emVideoView == null) return false;
- internalListener.onVideoPlayPause();
+ if (playerImpl.getPlayer() == null) return false;
+ playerImpl.onVideoPlayPause();
return true;
}
-
@Override
public boolean onDown(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
initialPopupX = windowLayoutParams.x;
initialPopupY = windowLayoutParams.y;
- popupWidth = viewHolder.getRootView().getWidth();
- popupHeight = viewHolder.getRootView().getHeight();
+ popupWidth = playerImpl.getRootView().getWidth();
+ popupHeight = playerImpl.getRootView().getHeight();
return false;
}
- @Override
- public void onShowPress(MotionEvent e) {
- if (DEBUG) Log.d(TAG, "onShowPress() called with: e = [" + e + "]");
- /*viewHolder.getControlsRoot().animate().setListener(null).cancel();
- viewHolder.getControlsRoot().setAlpha(1f);
- viewHolder.getControlsRoot().setVisibility(View.VISIBLE);*/
- animateView(viewHolder.getControlsRoot(), true, 200, 0);
- }
-
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f) playerImpl.animateView(playerImpl.getControlsRoot(), true, 30, 0);
isMoving = true;
float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX);
float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY);
@@ -712,20 +474,21 @@ public class PopupVideoPlayer extends Service implements StateInterface {
windowLayoutParams.x = (int) posX;
windowLayoutParams.y = (int) posY;
- if (DEBUG) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && false) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]" +
", posXy = [" + posX + ", " + posY + "]" +
", popupWh rootView.get wh = [" + popupWidth + " x " + popupHeight + "]");
- windowManager.updateViewLayout(viewHolder.getRootView(), windowLayoutParams);
+ windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
return true;
}
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
- if (viewHolder.isControlsVisible() && CURRENT_STATE == STATE_PLAYING) {
- animateView(viewHolder.getControlsRoot(), false, 300, DEFAULT_CONTROLS_HIDE_TIME);
+ if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
}
}
@@ -763,48 +526,56 @@ public class PopupVideoPlayer extends Service implements StateInterface {
if (service == null) return;
streamExtractor = service.getExtractorInstance(intent.getStringExtra(NavStack.URL));
StreamInfo info = StreamInfo.getVideoInfo(streamExtractor);
- String defaultResolution = sharedPreferences.getString(
+ String defaultResolution = playerImpl.getSharedPreferences().getString(
getResources().getString(R.string.default_resolution_key),
getResources().getString(R.string.default_resolution_value));
- String chosen = "", secondary = "", fallback = "";
+ VideoStream chosen = null, secondary = null, fallback = null;
+ playerImpl.setVideoStreamsList(info.video_streams instanceof ArrayList
+ ? (ArrayList) info.video_streams
+ : new ArrayList<>(info.video_streams));
+
for (VideoStream item : info.video_streams) {
if (DEBUG && printStreams) {
- Log.d(TAG, "StreamExtractor: current Item"
+ Log.d(TAG, "FetcherRunnable.StreamExtractor: current Item"
+ ", item.resolution = " + item.resolution
+ ", item.format = " + item.format
+ ", item.url = " + item.url);
}
if (defaultResolution.equals(item.resolution)) {
if (item.format == MediaFormat.MPEG_4.id) {
- chosen = item.url;
- if (DEBUG)
- Log.d(TAG, "StreamExtractor: CHOSEN item"
- + ", item.resolution = " + item.resolution
- + ", item.format = " + item.format
- + ", item.url = " + item.url);
- } else if (item.format == 2) secondary = item.url;
- else fallback = item.url;
-
+ chosen = item;
+ if (DEBUG) Log.d(TAG, "FetcherRunnable.StreamExtractor: CHOSEN item, item.resolution = " + item.resolution + ", item.format = " + item.format + ", item.url = " + item.url);
+ } else if (item.format == 2) secondary = item;
+ else fallback = item;
}
}
- if (!chosen.trim().isEmpty()) streamUri = Uri.parse(chosen);
- else if (!secondary.trim().isEmpty()) streamUri = Uri.parse(secondary);
- else if (!fallback.trim().isEmpty()) streamUri = Uri.parse(fallback);
- else streamUri = Uri.parse(info.video_streams.get(0).url);
- if (DEBUG && printStreams) Log.d(TAG, "StreamExtractor: chosen = " + chosen
+ int selectedIndexStream;
+
+ if (chosen != null) selectedIndexStream = info.video_streams.indexOf(chosen);
+ else if (secondary != null) selectedIndexStream = info.video_streams.indexOf(secondary);
+ else if (fallback != null) selectedIndexStream = info.video_streams.indexOf(fallback);
+ else selectedIndexStream = 0;
+
+ playerImpl.setSelectedIndexStream(selectedIndexStream);
+
+ if (DEBUG && printStreams) Log.d(TAG, "FetcherRunnable.StreamExtractor: chosen = " + chosen
+ "\n, secondary = " + secondary
+ "\n, fallback = " + fallback
+ "\n, info.video_streams.get(0).url = " + info.video_streams.get(0).url);
- videoUrl = info.webpage_url;
- videoTitle = info.title;
- channelName = info.uploader;
+
+ playerImpl.setVideoUrl(info.webpage_url);
+ playerImpl.setVideoTitle(info.title);
+ playerImpl.setChannelName(info.uploader);
+ if (info.start_position > 0) playerImpl.setVideoStartPos(info.start_position * 1000);
+ else playerImpl.setVideoStartPos(-1);
+
mainHandler.post(new Runnable() {
@Override
public void run() {
- playVideo(streamUri);
+ playerImpl.playVideo(playerImpl.getSelectedStreamUri(), true);
}
});
imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() {
@@ -813,9 +584,10 @@ public class PopupVideoPlayer extends Service implements StateInterface {
mainHandler.post(new Runnable() {
@Override
public void run() {
- videoThumbnail = loadedImage;
- if (videoThumbnail != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
+ playerImpl.setVideoThumbnail(loadedImage);
+ if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
updateNotification(-1);
+ ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = loadedImage;
}
});
}
@@ -841,4 +613,5 @@ public class PopupVideoPlayer extends Service implements StateInterface {
}
}
}
-}
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/player/popup/StateInterface.java b/app/src/main/java/org/schabi/newpipe/player/StateInterface.java
similarity index 71%
rename from app/src/main/java/org/schabi/newpipe/player/popup/StateInterface.java
rename to app/src/main/java/org/schabi/newpipe/player/StateInterface.java
index 94ea41470..7b3681ab8 100644
--- a/app/src/main/java/org/schabi/newpipe/player/popup/StateInterface.java
+++ b/app/src/main/java/org/schabi/newpipe/player/StateInterface.java
@@ -1,8 +1,9 @@
-package org.schabi.newpipe.player.popup;
+package org.schabi.newpipe.player;
public interface StateInterface {
int STATE_LOADING = 123;
- int STATE_PLAYING = 125;
+ int STATE_PLAYING = 124;
+ int STATE_BUFFERING = 125;
int STATE_PAUSED = 126;
int STATE_PAUSED_SEEK = 127;
int STATE_COMPLETED = 128;
@@ -11,6 +12,7 @@ public interface StateInterface {
void onLoading();
void onPlaying();
+ void onBuffering();
void onPaused();
void onPausedSeek();
void onCompleted();
diff --git a/app/src/main/java/org/schabi/newpipe/player/popup/PopupViewHolder.java b/app/src/main/java/org/schabi/newpipe/player/popup/PopupViewHolder.java
deleted file mode 100644
index 22895668e..000000000
--- a/app/src/main/java/org/schabi/newpipe/player/popup/PopupViewHolder.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.schabi.newpipe.player.popup;
-
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.os.Build;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
-
-import org.schabi.newpipe.R;
-
-public class PopupViewHolder {
- private View rootView;
- private EMVideoView videoView;
- private View loadingPanel;
- private ImageView endScreen;
- private ImageView controlAnimationView;
- private LinearLayout controlsRoot;
- private SeekBar playbackSeekBar;
- private TextView playbackCurrentTime;
- private TextView playbackEndTime;
-
- public PopupViewHolder(View rootView) {
- if (rootView == null) return;
- this.rootView = rootView;
- this.videoView = (EMVideoView) rootView.findViewById(R.id.popupVideoView);
- this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
- this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
- this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
- this.controlsRoot = (LinearLayout) rootView.findViewById(R.id.playbackControlRoot);
- this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
- this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
- this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
- doModifications();
- }
-
- private void doModifications() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
- playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
- }
-
- public boolean isControlsVisible() {
- return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
- }
-
- public boolean isVisible(View view) {
- return view != null && view.getVisibility() == View.VISIBLE;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // GETTERS
- ///////////////////////////////////////////////////////////////////////////
-
- public View getRootView() {
- return rootView;
- }
-
- public EMVideoView getVideoView() {
- return videoView;
- }
-
- public View getLoadingPanel() {
- return loadingPanel;
- }
-
- public ImageView getEndScreen() {
- return endScreen;
- }
-
- public ImageView getControlAnimationView() {
- return controlAnimationView;
- }
-
- public LinearLayout getControlsRoot() {
- return controlsRoot;
- }
-
- public SeekBar getPlaybackSeekBar() {
- return playbackSeekBar;
- }
-
- public TextView getPlaybackCurrentTime() {
- return playbackCurrentTime;
- }
-
- public TextView getPlaybackEndTime() {
- return playbackEndTime;
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
index 6bf261e14..cb3869c01 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
@@ -19,17 +19,14 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
-import org.schabi.newpipe.ChannelActivity;
+import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.info_list.InfoItemBuilder;
-import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.info_list.InfoListAdapter;
+import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack;
import java.util.EnumSet;
diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png
rename to app/src/main/res/drawable-hdpi/ic_close_white.png
diff --git a/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png
deleted file mode 100644
index ceb1a1eeb..000000000
Binary files a/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-hdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..159bea7fd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-hdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..9b8131124
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_pause_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
rename to app/src/main/res/drawable-hdpi/ic_pause_white.png
diff --git a/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 4d2ea05c4..000000000
Binary files a/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-hdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-hdpi/ic_repeat_white.png b/app/src/main/res/drawable-hdpi/ic_repeat_white.png
new file mode 100644
index 000000000..5de7a2951
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_white.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png
rename to app/src/main/res/drawable-mdpi/ic_close_white.png
diff --git a/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png
deleted file mode 100644
index af7f8288d..000000000
Binary files a/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-mdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..364bad0b8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-mdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..4423c7ce9
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_pause_white.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
rename to app/src/main/res/drawable-mdpi/ic_pause_white.png
diff --git a/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 2272d478c..000000000
Binary files a/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-mdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-mdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-mdpi/ic_repeat_white.png b/app/src/main/res/drawable-mdpi/ic_repeat_white.png
new file mode 100644
index 000000000..ad8b8c0df
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_close_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png
rename to app/src/main/res/drawable-xhdpi/ic_close_white.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..ef360fe40
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-xhdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..c1dcfb290
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_pause_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png
rename to app/src/main/res/drawable-xhdpi/ic_pause_white.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xhdpi/ic_repeat_white.png
new file mode 100644
index 000000000..c13d00242
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white.png b/app/src/main/res/drawable-xxhdpi/ic_close_white.png
new file mode 100644
index 000000000..4927bc242
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..b7f4133fd
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..a0a1b4d4f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_white.png b/app/src/main/res/drawable-xxhdpi/ic_pause_white.png
new file mode 100644
index 000000000..3ea7e03e5
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png
new file mode 100644
index 000000000..bf7607966
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white.png b/app/src/main/res/drawable-xxxhdpi/ic_close_white.png
new file mode 100644
index 000000000..1ab231275
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..b47b3f8bd
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..ea9f18ae6
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_white.png b/app/src/main/res/drawable-xxxhdpi/ic_pause_white.png
new file mode 100644
index 000000000..76482b1fd
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pause_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png
new file mode 100644
index 000000000..a59db47ee
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable/popup_controls_bg.xml b/app/src/main/res/drawable/player_controls_bg.xml
similarity index 69%
rename from app/src/main/res/drawable/popup_controls_bg.xml
rename to app/src/main/res/drawable/player_controls_bg.xml
index d04812bd8..9c9468112 100644
--- a/app/src/main/res/drawable/popup_controls_bg.xml
+++ b/app/src/main/res/drawable/player_controls_bg.xml
@@ -1,8 +1,7 @@
diff --git a/app/src/main/res/drawable/player_top_controls_bg.xml b/app/src/main/res/drawable/player_top_controls_bg.xml
new file mode 100644
index 000000000..b7cdecc87
--- /dev/null
+++ b/app/src/main/res/drawable/player_top_controls_bg.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_exo_player.xml b/app/src/main/res/layout/activity_exo_player.xml
index 5653532ec..72f8aa897 100644
--- a/app/src/main/res/layout/activity_exo_player.xml
+++ b/app/src/main/res/layout/activity_exo_player.xml
@@ -1,15 +1,303 @@
-
-
+
+ android:background="@android:color/black"
+ android:gravity="center">
-
+ android:layout_height="match_parent"
+ android:layout_gravity="center">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/exomedia_custom_controls.xml b/app/src/main/res/layout/exomedia_custom_controls.xml
deleted file mode 100644
index dedaf7908..000000000
--- a/app/src/main/res/layout/exomedia_custom_controls.xml
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_notification.xml b/app/src/main/res/layout/player_notification.xml
index 43ac993ca..22a60418b 100644
--- a/app/src/main/res/layout/player_notification.xml
+++ b/app/src/main/res/layout/player_notification.xml
@@ -65,7 +65,7 @@
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
- android:src="@drawable/ic_pause_white_24dp" />
+ android:src="@drawable/ic_pause_white" />
+ android:src="@drawable/ic_close_white" />
diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml
index 0fb6a8eb2..4a81d2ca3 100644
--- a/app/src/main/res/layout/player_notification_expanded.xml
+++ b/app/src/main/res/layout/player_notification_expanded.xml
@@ -58,7 +58,7 @@
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
- android:src="@drawable/ic_close_white_24dp" />
+ android:src="@drawable/ic_close_white" />
diff --git a/app/src/main/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml
index 6d1860408..a3b2b80b6 100644
--- a/app/src/main/res/layout/player_popup.xml
+++ b/app/src/main/res/layout/player_popup.xml
@@ -4,18 +4,29 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@android:color/black"
android:gravity="center">
-
+ android:layout_gravity="center">
-
+
+
+
+
+
+ tools:ignore="ContentDescription"
+ tools:visibility="visible"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:orientation="horizontal"
+ android:weightSum="5">
+
+ tools:ignore="ContentDescription"
+ tools:visibility="visible"/>
-
-
-
-
-
-
-
-
-
-
+ tools:ignore="RtlHardcoded"
+ tools:text="1:06:29"
+ tools:visibility="visible"/>
+ tools:text="a long, long, long, long, long title"/>
+ tools:text="a long, long artist"/>
+
+
+ android:padding="5dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_close_white"
+ tools:ignore="ContentDescription,RtlHardcoded"/>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 263c6bc14..f91c51f3f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -25,6 +25,7 @@
Settings
Use external video player
Use external audio player
+ NewPipe Popup mode
Video download path
Path to store downloaded videos in.
@@ -75,6 +76,7 @@
Other
%1$s - NewPipe
Playing in background
+ Playing in popup mode
https://www.c3s.cc/
Play
Content
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 606da4eb0..9cf8160c3 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -2,7 +2,7 @@
-