- * PopupVideoPlayer.java is part of NewPipe
- *
- * License: GPL-3.0+
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.schabi.newpipe.player;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.SuppressLint;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.PixelFormat;
-import android.os.Build;
-import android.os.IBinder;
-import android.preference.PreferenceManager;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.AnticipateInterpolator;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.PopupMenu;
-import android.widget.RemoteViews;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.text.CaptionStyleCompat;
-import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
-import com.google.android.exoplayer2.ui.SubtitleView;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import com.nostra13.universalimageloader.core.assist.FailReason;
-
-import org.schabi.newpipe.BuildConfig;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.stream.VideoStream;
-import org.schabi.newpipe.player.event.PlayerEventListener;
-import org.schabi.newpipe.player.helper.PlayerHelper;
-import org.schabi.newpipe.player.resolver.MediaSourceTag;
-import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
-import org.schabi.newpipe.util.ListHelper;
-import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.ThemeHelper;
-
-import java.util.List;
-
-import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
-import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
-import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
-import static org.schabi.newpipe.util.AnimationUtils.animateView;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
-/**
- * Service Popup Player implementing {@link VideoPlayer}.
- *
- * @author mauriciocolli
- */
-public final class PopupVideoPlayer extends Service {
- 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_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
- private static final String TAG = ".PopupVideoPlayer";
- private static final boolean DEBUG = BasePlayer.DEBUG;
- private static final int NOTIFICATION_ID = 40028922;
- private static final String POPUP_SAVED_WIDTH = "popup_saved_width";
- private static final String POPUP_SAVED_X = "popup_saved_x";
- private static final String POPUP_SAVED_Y = "popup_saved_y";
-
- private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300;
-
- private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
- private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
-
- private WindowManager windowManager;
- private WindowManager.LayoutParams popupLayoutParams;
- private GestureDetector popupGestureDetector;
-
- private View closeOverlayView;
- private FloatingActionButton closeOverlayButton;
-
- private int tossFlingVelocity;
-
- private float screenWidth;
- private float screenHeight;
- private float popupWidth;
- private float popupHeight;
-
- private float minimumWidth;
- private float minimumHeight;
- private float maximumWidth;
- private float maximumHeight;
-
- private NotificationManager notificationManager;
- private NotificationCompat.Builder notBuilder;
- private RemoteViews notRemoteView;
-
- private VideoPlayerImpl playerImpl;
- private boolean isPopupClosing = false;
-
- /*//////////////////////////////////////////////////////////////////////////
- // Service-Activity Binder
- //////////////////////////////////////////////////////////////////////////*/
-
- private PlayerEventListener activityListener;
- private IBinder mBinder;
-
- /*//////////////////////////////////////////////////////////////////////////
- // Service LifeCycle
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onCreate() {
- assureCorrectAppLanguage(this);
- windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
- notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
-
- playerImpl = new VideoPlayerImpl(this);
- ThemeHelper.setTheme(this);
-
- mBinder = new PlayerServiceBinder(playerImpl);
- }
-
- @Override
- public int onStartCommand(final Intent intent, final int flags, final int startId) {
- if (DEBUG) {
- Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], "
- + "flags = [" + flags + "], startId = [" + startId + "]");
- }
- if (playerImpl.getPlayer() == null) {
- initPopup();
- initPopupCloseOverlay();
- }
-
- playerImpl.handleIntent(intent);
-
- return START_NOT_STICKY;
- }
-
- @Override
- public void onConfigurationChanged(final Configuration newConfig) {
- assureCorrectAppLanguage(this);
- if (DEBUG) {
- Log.d(TAG, "onConfigurationChanged() called with: "
- + "newConfig = [" + newConfig + "]");
- }
- updateScreenSize();
- updatePopupSize(popupLayoutParams.width, -1);
- checkPopupPositionBounds();
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) {
- Log.d(TAG, "onDestroy() called");
- }
- closePopup();
- }
-
- @Override
- protected void attachBaseContext(final Context base) {
- super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
- }
-
- @Override
- public IBinder onBind(final Intent intent) {
- return mBinder;
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Init
- //////////////////////////////////////////////////////////////////////////*/
-
- @SuppressLint("RtlHardcoded")
- private void initPopup() {
- if (DEBUG) {
- Log.d(TAG, "initPopup() called");
- }
- View rootView = View.inflate(this, R.layout.player_popup, null);
- playerImpl.setup(rootView);
-
- tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
-
- updateScreenSize();
-
- final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(this);
- final float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- popupWidth = popupRememberSizeAndPos
- ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
-
- final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O
- ? WindowManager.LayoutParams.TYPE_PHONE
- : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
- popupLayoutParams = new WindowManager.LayoutParams(
- (int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
- layoutParamType,
- IDLE_WINDOW_FLAGS,
- PixelFormat.TRANSLUCENT);
- popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
- popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-
- int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
- int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
- popupLayoutParams.x = popupRememberSizeAndPos
- ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
- popupLayoutParams.y = popupRememberSizeAndPos
- ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
-
- checkPopupPositionBounds();
-
- PopupWindowGestureListener listener = new PopupWindowGestureListener();
- popupGestureDetector = new GestureDetector(this, listener);
- rootView.setOnTouchListener(listener);
-
- playerImpl.getLoadingPanel().setMinimumWidth(popupLayoutParams.width);
- playerImpl.getLoadingPanel().setMinimumHeight(popupLayoutParams.height);
- windowManager.addView(rootView, popupLayoutParams);
- }
-
- @SuppressLint("RtlHardcoded")
- private void initPopupCloseOverlay() {
- if (DEBUG) {
- Log.d(TAG, "initPopupCloseOverlay() called");
- }
- closeOverlayView = View.inflate(this, R.layout.player_popup_close_overlay, null);
- closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton);
-
- final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O
- ? WindowManager.LayoutParams.TYPE_PHONE
- : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
- final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-
- WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
- layoutParamType,
- flags,
- PixelFormat.TRANSLUCENT);
- closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
- closeOverlayLayoutParams.softInputMode = WindowManager
- .LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-
- closeOverlayButton.setVisibility(View.GONE);
- windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Notification
- //////////////////////////////////////////////////////////////////////////*/
-
- private void resetNotification() {
- notBuilder = createNotification();
- }
-
- private NotificationCompat.Builder createNotification() {
- notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
- R.layout.player_popup_notification);
-
- notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
- notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
- notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());
-
- notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
- 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(ACTION_CLOSE),
- PendingIntent.FLAG_UPDATE_CURRENT));
- notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT),
- PendingIntent.FLAG_UPDATE_CURRENT));
-
- // Starts popup player activity -- attempts to unlock lockscreen
- final Intent intent = NavigationHelper.getPopupPlayerActivityIntent(this);
- notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
- PendingIntent.getActivity(this, NOTIFICATION_ID, intent,
- PendingIntent.FLAG_UPDATE_CURRENT));
-
- setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode());
-
- NotificationCompat.Builder builder = new NotificationCompat
- .Builder(this, getString(R.string.notification_channel_id))
- .setOngoing(true)
- .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .setContent(notRemoteView);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
- builder.setPriority(NotificationCompat.PRIORITY_MAX);
- }
- return builder;
- }
-
- /**
- * Updates the notification, and the play/pause button in it.
- * Used for changes on the remoteView
- *
- * @param drawableId if != -1, sets the drawable with that id on the play/pause button
- */
- private void updateNotification(final int drawableId) {
- if (DEBUG) {
- Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
- }
- if (notBuilder == null || notRemoteView == null) {
- return;
- }
- if (drawableId != -1) {
- notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
- }
- notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Misc
- //////////////////////////////////////////////////////////////////////////*/
-
- public void closePopup() {
- if (DEBUG) {
- Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
- }
- if (isPopupClosing) {
- return;
- }
- isPopupClosing = true;
-
- if (playerImpl != null) {
- playerImpl.savePlaybackState();
- if (playerImpl.getRootView() != null) {
- windowManager.removeView(playerImpl.getRootView());
- }
- playerImpl.setRootView(null);
- playerImpl.stopActivityBinding();
- playerImpl.destroy();
- playerImpl = null;
- }
-
- mBinder = null;
- if (notificationManager != null) {
- notificationManager.cancel(NOTIFICATION_ID);
- }
-
- animateOverlayAndFinishService();
- }
-
- private void animateOverlayAndFinishService() {
- final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight()
- - closeOverlayButton.getY());
-
- closeOverlayButton.animate().setListener(null).cancel();
- closeOverlayButton.animate()
- .setInterpolator(new AnticipateInterpolator())
- .translationY(targetTranslationY)
- .setDuration(400)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(final Animator animation) {
- end();
- }
-
- @Override
- public void onAnimationEnd(final Animator animation) {
- end();
- }
-
- private void end() {
- windowManager.removeView(closeOverlayView);
-
- stopForeground(true);
- stopSelf();
- }
- }).start();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////*/
-
- /**
- * @see #checkPopupPositionBounds(float, float)
- * @return if the popup was out of bounds and have been moved back to it
- */
- @SuppressWarnings("UnusedReturnValue")
- private boolean checkPopupPositionBounds() {
- return checkPopupPositionBounds(screenWidth, screenHeight);
- }
-
- /**
- * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary
- * that goes from (0, 0) to (boundaryWidth, boundaryHeight).
- *
- * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed
- * and {@code true} is returned to represent this change.
- *
- *
- * @param boundaryWidth width of the boundary
- * @param boundaryHeight height of the boundary
- * @return if the popup was out of bounds and have been moved back to it
- */
- private boolean checkPopupPositionBounds(final float boundaryWidth,
- final float boundaryHeight) {
- if (DEBUG) {
- Log.d(TAG, "checkPopupPositionBounds() called with: "
- + "boundaryWidth = [" + boundaryWidth + "], "
- + "boundaryHeight = [" + boundaryHeight + "]");
- }
-
- if (popupLayoutParams.x < 0) {
- popupLayoutParams.x = 0;
- return true;
- } else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) {
- popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width);
- return true;
- }
-
- if (popupLayoutParams.y < 0) {
- popupLayoutParams.y = 0;
- return true;
- } else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) {
- popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height);
- return true;
- }
-
- return false;
- }
-
- private void savePositionAndSize() {
- SharedPreferences sharedPreferences = PreferenceManager
- .getDefaultSharedPreferences(PopupVideoPlayer.this);
- sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply();
- sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply();
- sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply();
- }
-
- private float getMinimumVideoHeight(final float width) {
- final float height = width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
-// if (DEBUG) {
-// Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], "
-// + "returned: " + height);
-// }
- return height;
- }
-
- 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);
- }
-
- popupWidth = getResources().getDimension(R.dimen.popup_default_width);
- popupHeight = getMinimumVideoHeight(popupWidth);
-
- minimumWidth = getResources().getDimension(R.dimen.popup_minimum_width);
- minimumHeight = getMinimumVideoHeight(minimumWidth);
-
- maximumWidth = screenWidth;
- maximumHeight = screenHeight;
- }
-
- private void updatePopupSize(final int width, final int height) {
- if (playerImpl == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "updatePopupSize() called with: "
- + "width = [" + width + "], height = [" + height + "]");
- }
-
- final int actualWidth = (int) (width > maximumWidth ? maximumWidth
- : width < minimumWidth ? minimumWidth : width);
-
- final int actualHeight;
- if (height == -1) {
- actualHeight = (int) getMinimumVideoHeight(width);
- } else {
- actualHeight = (int) (height > maximumHeight ? maximumHeight
- : height < minimumHeight ? minimumHeight : height);
- }
-
- popupLayoutParams.width = actualWidth;
- popupLayoutParams.height = actualHeight;
- popupWidth = actualWidth;
- popupHeight = actualHeight;
-
- if (DEBUG) {
- Log.d(TAG, "updatePopupSize() updated values: "
- + "width = [" + actualWidth + "], height = [" + actualHeight + "]");
- }
- windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
- }
-
- protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) {
- final String methodName = "setImageResource";
-
- if (remoteViews == null) {
- return;
- }
-
- switch (repeatMode) {
- case Player.REPEAT_MODE_OFF:
- remoteViews.setInt(R.id.notificationRepeat, methodName,
- R.drawable.exo_controls_repeat_off);
- break;
- case Player.REPEAT_MODE_ONE:
- remoteViews.setInt(R.id.notificationRepeat, methodName,
- R.drawable.exo_controls_repeat_one);
- break;
- case Player.REPEAT_MODE_ALL:
- remoteViews.setInt(R.id.notificationRepeat, methodName,
- R.drawable.exo_controls_repeat_all);
- break;
- }
- }
-
- private void updateWindowFlags(final int flags) {
- if (popupLayoutParams == null || windowManager == null || playerImpl == null) {
- return;
- }
-
- popupLayoutParams.flags = flags;
- windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
- }
- ///////////////////////////////////////////////////////////////////////////
-
- protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener {
- private TextView resizingIndicator;
- private ImageButton fullScreenButton;
- private ImageView videoPlayPause;
-
- private View extraOptionsView;
- private View closingOverlayView;
-
- VideoPlayerImpl(final Context context) {
- super("VideoPlayerImpl" + PopupVideoPlayer.TAG, context);
- }
-
- @Override
- public void handleIntent(final Intent intent) {
- super.handleIntent(intent);
-
- resetNotification();
- startForeground(NOTIFICATION_ID, notBuilder.build());
- }
-
- @Override
- public void initViews(final View view) {
- super.initViews(view);
- resizingIndicator = view.findViewById(R.id.resizing_indicator);
- fullScreenButton = view.findViewById(R.id.fullScreenButton);
- fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
- videoPlayPause = view.findViewById(R.id.videoPlayPause);
-
- extraOptionsView = view.findViewById(R.id.extraOptionsView);
- closingOverlayView = view.findViewById(R.id.closingOverlay);
- view.addOnLayoutChangeListener(this);
- }
-
- @Override
- public void initListeners() {
- super.initListeners();
- videoPlayPause.setOnClickListener(v -> onPlayPause());
- }
-
- @Override
- protected void setupSubtitleView(@NonNull final SubtitleView view, final float captionScale,
- @NonNull final CaptionStyleCompat captionStyle) {
- float captionRatio = (captionScale - 1f) / 5f + 1f;
- view.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio);
- view.setApplyEmbeddedStyles(captionStyle.equals(CaptionStyleCompat.DEFAULT));
- view.setStyle(captionStyle);
- }
-
- @Override
- public void onLayoutChange(final View view, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop,
- final int oldRight, final int oldBottom) {
- float widthDp = Math.abs(right - left) / getResources().getDisplayMetrics().density;
- final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP ? View.VISIBLE : View.GONE;
- extraOptionsView.setVisibility(visibility);
- }
-
- @Override
- public void destroy() {
- if (notRemoteView != null) {
- notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
- }
- super.destroy();
- }
-
- @Override
- public void onFullScreenButtonClicked() {
- super.onFullScreenButtonClicked();
-
- if (DEBUG) {
- Log.d(TAG, "onFullScreenButtonClicked() called");
- }
-
- setRecovery();
- final Intent intent = NavigationHelper.getPlayerIntent(
- context,
- MainVideoPlayer.class,
- this.getPlayQueue(),
- this.getRepeatMode(),
- this.getPlaybackSpeed(),
- this.getPlaybackPitch(),
- this.getPlaybackSkipSilence(),
- this.getPlaybackQuality(),
- false,
- !isPlaying(),
- isMuted()
- );
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- closePopup();
- }
-
- @Override
- public void onDismiss(final PopupMenu menu) {
- super.onDismiss(menu);
- if (isPlaying()) {
- hideControls(500, 0);
- }
- }
-
- @Override
- protected int nextResizeMode(final int resizeMode) {
- if (resizeMode == AspectRatioFrameLayout.RESIZE_MODE_FILL) {
- return AspectRatioFrameLayout.RESIZE_MODE_FIT;
- } else {
- return AspectRatioFrameLayout.RESIZE_MODE_FILL;
- }
- }
-
- @Override
- public void onStopTrackingTouch(final SeekBar seekBar) {
- super.onStopTrackingTouch(seekBar);
- if (wasPlaying()) {
- hideControls(100, 0);
- }
- }
-
- @Override
- public void onShuffleClicked() {
- super.onShuffleClicked();
- updatePlayback();
- }
-
- @Override
- public void onMuteUnmuteButtonClicked() {
- super.onMuteUnmuteButtonClicked();
- updatePlayback();
- }
-
- @Override
- public void onUpdateProgress(final int currentProgress, final int duration,
- final int bufferPercent) {
- updateProgress(currentProgress, duration, bufferPercent);
- super.onUpdateProgress(currentProgress, duration, bufferPercent);
- }
-
- @Override
- protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
- return new VideoPlaybackResolver.QualityResolver() {
- @Override
- public int getDefaultResolutionIndex(final List sortedVideos) {
- return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
- }
-
- @Override
- public int getOverrideResolutionIndex(final List sortedVideos,
- final String playbackQuality) {
- return ListHelper.getPopupResolutionIndex(context, sortedVideos,
- playbackQuality);
- }
- };
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Thumbnail Loading
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onLoadingComplete(final String imageUri, final View view,
- final Bitmap loadedImage) {
- super.onLoadingComplete(imageUri, view, loadedImage);
- if (playerImpl == null) {
- return;
- }
- // rebuild notification here since remote view does not release bitmaps,
- // causing memory leaks
- resetNotification();
- updateNotification(-1);
- }
-
- @Override
- public void onLoadingFailed(final String imageUri, final View view,
- final FailReason failReason) {
- super.onLoadingFailed(imageUri, view, failReason);
- resetNotification();
- updateNotification(-1);
- }
-
- @Override
- public void onLoadingCancelled(final String imageUri, final View view) {
- super.onLoadingCancelled(imageUri, view);
- resetNotification();
- updateNotification(-1);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Activity Event Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- /*package-private*/ void setActivityListener(final PlayerEventListener listener) {
- activityListener = listener;
- updateMetadata();
- updatePlayback();
- triggerProgressUpdate();
- }
-
- /*package-private*/ void removeActivityListener(final PlayerEventListener listener) {
- if (activityListener == listener) {
- activityListener = null;
- }
- }
-
- private void updateMetadata() {
- if (activityListener != null && getCurrentMetadata() != null) {
- activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
- }
- }
-
- private void updatePlayback() {
- if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
- activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
- playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
- }
- }
-
- private void updateProgress(final int currentProgress, final int duration,
- final int bufferPercent) {
- if (activityListener != null) {
- activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
- }
- }
-
- private void stopActivityBinding() {
- if (activityListener != null) {
- activityListener.onServiceStopped();
- activityListener = null;
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // ExoPlayer Video Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void onRepeatModeChanged(final int i) {
- super.onRepeatModeChanged(i);
- setRepeatModeRemote(notRemoteView, i);
- updatePlayback();
- resetNotification();
- updateNotification(-1);
- }
-
- @Override
- public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
- super.onPlaybackParametersChanged(playbackParameters);
- updatePlayback();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Playback Listener
- //////////////////////////////////////////////////////////////////////////*/
-
- protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
- super.onMetadataChanged(tag);
- resetNotification();
- updateNotification(-1);
- updateMetadata();
- }
-
- @Override
- public void onPlaybackShutdown() {
- super.onPlaybackShutdown();
- closePopup();
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Broadcast Receiver
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- protected void setupBroadcastReceiver(final IntentFilter intentFltr) {
- super.setupBroadcastReceiver(intentFltr);
- if (DEBUG) {
- Log.d(TAG, "setupBroadcastReceiver() called with: "
- + "intentFilter = [" + intentFltr + "]");
- }
- intentFltr.addAction(ACTION_CLOSE);
- intentFltr.addAction(ACTION_PLAY_PAUSE);
- intentFltr.addAction(ACTION_REPEAT);
-
- intentFltr.addAction(Intent.ACTION_SCREEN_ON);
- intentFltr.addAction(Intent.ACTION_SCREEN_OFF);
- }
-
- @Override
- public void onBroadcastReceived(final Intent intent) {
- super.onBroadcastReceived(intent);
- if (intent == null || intent.getAction() == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
- }
- switch (intent.getAction()) {
- case ACTION_CLOSE:
- closePopup();
- break;
- case ACTION_PLAY_PAUSE:
- onPlayPause();
- break;
- case ACTION_REPEAT:
- onRepeatClicked();
- break;
- case Intent.ACTION_SCREEN_ON:
- enableVideoRenderer(true);
- break;
- case Intent.ACTION_SCREEN_OFF:
- enableVideoRenderer(false);
- break;
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // States
- //////////////////////////////////////////////////////////////////////////*/
-
- @Override
- public void changeState(final int state) {
- super.changeState(state);
- updatePlayback();
- }
-
- @Override
- public void onBlocked() {
- super.onBlocked();
- resetNotification();
- updateNotification(R.drawable.exo_controls_play);
- }
-
- @Override
- public void onPlaying() {
- super.onPlaying();
-
- updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
-
- resetNotification();
- updateNotification(R.drawable.exo_controls_pause);
-
- videoPlayPause.setBackgroundResource(R.drawable.exo_controls_pause);
- hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
-
- startForeground(NOTIFICATION_ID, notBuilder.build());
- }
-
- @Override
- public void onBuffering() {
- super.onBuffering();
- resetNotification();
- updateNotification(R.drawable.exo_controls_play);
- }
-
- @Override
- public void onPaused() {
- super.onPaused();
-
- updateWindowFlags(IDLE_WINDOW_FLAGS);
-
- resetNotification();
- updateNotification(R.drawable.exo_controls_play);
- videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
-
- stopForeground(false);
- }
-
- @Override
- public void onPausedSeek() {
- super.onPausedSeek();
- resetNotification();
- updateNotification(R.drawable.exo_controls_play);
-
- videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
- }
-
- @Override
- public void onCompleted() {
- super.onCompleted();
-
- updateWindowFlags(IDLE_WINDOW_FLAGS);
-
- resetNotification();
- updateNotification(R.drawable.ic_replay_white_24dp);
- videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white_24dp);
-
- stopForeground(false);
- }
-
- @Override
- public void showControlsThenHide() {
- videoPlayPause.setVisibility(View.VISIBLE);
- super.showControlsThenHide();
- }
-
- public void showControls(final long duration) {
- videoPlayPause.setVisibility(View.VISIBLE);
- super.showControls(duration);
- }
-
- public void hideControls(final long duration, final long delay) {
- super.hideControlsAndButton(duration, delay, videoPlayPause);
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////*/
-
- /*package-private*/ void enableVideoRenderer(final boolean enable) {
- final int videoRendererIndex = getRendererIndex(C.TRACK_TYPE_VIDEO);
- if (videoRendererIndex != RENDERER_UNAVAILABLE) {
- trackSelector.setParameters(trackSelector.buildUponParameters()
- .setRendererDisabled(videoRendererIndex, !enable));
- }
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Getters
- //////////////////////////////////////////////////////////////////////////*/
-
- @SuppressWarnings("WeakerAccess")
- public TextView getResizingIndicator() {
- return resizingIndicator;
- }
-
- public View getClosingOverlayView() {
- return closingOverlayView;
- }
- }
-
- private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener
- implements View.OnTouchListener {
- private int initialPopupX;
- private int initialPopupY;
- private boolean isMoving;
- private boolean isResizing;
-
- //initial co-ordinates and distance between fingers
- private double initPointerDistance = -1;
- private float initFirstPointerX = -1;
- private float initFirstPointerY = -1;
- private float initSecPointerX = -1;
- private float initSecPointerY = -1;
-
-
- @Override
- public boolean onDoubleTap(final MotionEvent e) {
- if (DEBUG) {
- Log.d(TAG, "onDoubleTap() called with: e = [" + e + "], "
- + "rawXy = " + e.getRawX() + ", " + e.getRawY()
- + ", xy = " + e.getX() + ", " + e.getY());
- }
- if (playerImpl == null || !playerImpl.isPlaying()) {
- return false;
- }
-
- playerImpl.hideControls(0, 0);
-
- if (e.getX() > popupWidth / 2) {
- playerImpl.onFastForward();
- } else {
- playerImpl.onFastRewind();
- }
-
- return true;
- }
-
- @Override
- public boolean onSingleTapConfirmed(final MotionEvent e) {
- if (DEBUG) {
- Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
- }
- if (playerImpl == null || playerImpl.getPlayer() == null) {
- return false;
- }
- if (playerImpl.isControlsVisible()) {
- playerImpl.hideControls(100, 100);
- } else {
- playerImpl.showControlsThenHide();
-
- }
- return true;
- }
-
- @Override
- public boolean onDown(final MotionEvent e) {
- if (DEBUG) {
- Log.d(TAG, "onDown() called with: e = [" + e + "]");
- }
-
- // Fix popup position when the user touch it, it may have the wrong one
- // because the soft input is visible (the draggable area is currently resized).
- checkPopupPositionBounds(closeOverlayView.getWidth(), closeOverlayView.getHeight());
-
- initialPopupX = popupLayoutParams.x;
- initialPopupY = popupLayoutParams.y;
- popupWidth = popupLayoutParams.width;
- popupHeight = popupLayoutParams.height;
- return super.onDown(e);
- }
-
- @Override
- public void onLongPress(final MotionEvent e) {
- if (DEBUG) {
- Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
- }
- updateScreenSize();
- checkPopupPositionBounds();
- updatePopupSize((int) screenWidth, -1);
- }
-
- @Override
- public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
- final float distanceX, final float distanceY) {
- if (isResizing || playerImpl == null) {
- return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
- }
-
- if (!isMoving) {
- animateView(closeOverlayButton, true, 200);
- }
-
- isMoving = true;
-
- float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
- float posX = (int) (initialPopupX + diffX);
- float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
- float posY = (int) (initialPopupY + diffY);
-
- if (posX > (screenWidth - popupWidth)) {
- posX = (int) (screenWidth - popupWidth);
- } else if (posX < 0) {
- posX = 0;
- }
-
- if (posY > (screenHeight - popupHeight)) {
- posY = (int) (screenHeight - popupHeight);
- } else if (posY < 0) {
- posY = 0;
- }
-
- popupLayoutParams.x = (int) posX;
- popupLayoutParams.y = (int) posY;
-
- final View closingOverlayView = playerImpl.getClosingOverlayView();
- if (isInsideClosingRadius(movingEvent)) {
- if (closingOverlayView.getVisibility() == View.GONE) {
- animateView(closingOverlayView, true, 250);
- }
- } else {
- if (closingOverlayView.getVisibility() == View.VISIBLE) {
- animateView(closingOverlayView, false, 0);
- }
- }
-
-// if (DEBUG) {
-// Log.d(TAG, "PopupVideoPlayer.onScroll = "
-// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
-// + initialEvent.getRawY() + "], "
-// + "e1.getX,Y = [" + initialEvent.getX() + ", "
-// + initialEvent.getY() + "], "
-// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
-// + movingEvent.getRawY() + "], "
-// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
-// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
-// + "posX,Y = [" + posX + ", " + posY + "], "
-// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
-// }
- windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
- return true;
- }
-
- private void onScrollEnd(final MotionEvent event) {
- if (DEBUG) {
- Log.d(TAG, "onScrollEnd() called");
- }
- if (playerImpl == null) {
- return;
- }
- if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
- playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
- }
-
- if (isInsideClosingRadius(event)) {
- closePopup();
- } else {
- animateView(playerImpl.getClosingOverlayView(), false, 0);
-
- if (!isPopupClosing) {
- animateView(closeOverlayButton, false, 200);
- }
- }
- }
-
- @Override
- public boolean onFling(final MotionEvent e1, final MotionEvent e2,
- final float velocityX, final float velocityY) {
- if (DEBUG) {
- Log.d(TAG, "Fling velocity: dX=[" + velocityX + "], dY=[" + velocityY + "]");
- }
- if (playerImpl == null) {
- return false;
- }
-
- final float absVelocityX = Math.abs(velocityX);
- final float absVelocityY = Math.abs(velocityY);
- if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
- if (absVelocityX > tossFlingVelocity) {
- popupLayoutParams.x = (int) velocityX;
- }
- if (absVelocityY > tossFlingVelocity) {
- popupLayoutParams.y = (int) velocityY;
- }
- checkPopupPositionBounds();
- windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onTouch(final View v, final MotionEvent event) {
- popupGestureDetector.onTouchEvent(event);
- if (playerImpl == null) {
- return false;
- }
- if (event.getPointerCount() == 2 && !isMoving && !isResizing) {
- if (DEBUG) {
- Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
- }
- playerImpl.showAndAnimateControl(-1, true);
- playerImpl.getLoadingPanel().setVisibility(View.GONE);
-
- playerImpl.hideControls(0, 0);
- animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
- animateView(playerImpl.getResizingIndicator(), true, 200, 0);
-
- //record co-ordinates of fingers
- initFirstPointerX = event.getX(0);
- initFirstPointerY = event.getY(0);
- initSecPointerX = event.getX(1);
- initSecPointerY = event.getY(1);
- //record distance between fingers
- initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
- initFirstPointerY - initSecPointerY);
-
- isResizing = true;
- }
-
- if (event.getAction() == MotionEvent.ACTION_MOVE && !isMoving && isResizing) {
- if (DEBUG) {
- Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
- + "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
- }
- return handleMultiDrag(event);
- }
-
- if (event.getAction() == MotionEvent.ACTION_UP) {
- if (DEBUG) {
- Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
- + "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
- }
- if (isMoving) {
- isMoving = false;
- onScrollEnd(event);
- }
-
- if (isResizing) {
- isResizing = false;
-
- initPointerDistance = -1;
- initFirstPointerX = -1;
- initFirstPointerY = -1;
- initSecPointerX = -1;
- initSecPointerY = -1;
-
- animateView(playerImpl.getResizingIndicator(), false, 100, 0);
- playerImpl.changeState(playerImpl.getCurrentState());
- }
-
- if (!isPopupClosing) {
- savePositionAndSize();
- }
- }
-
- v.performClick();
- return true;
- }
-
- private boolean handleMultiDrag(final MotionEvent event) {
- if (initPointerDistance != -1 && event.getPointerCount() == 2) {
- // get the movements of the fingers
- double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
- event.getY(0) - initFirstPointerY);
- double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
- event.getY(1) - initSecPointerY);
-
- // minimum threshold beyond which pinch gesture will work
- int minimumMove = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop();
-
- if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
- // calculate current distance between the pointers
- double currentPointerDistance =
- Math.hypot(event.getX(0) - event.getX(1),
- event.getY(0) - event.getY(1));
-
- // change co-ordinates of popup so the center stays at the same position
- double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
- initPointerDistance = currentPointerDistance;
- popupLayoutParams.x += (popupWidth - newWidth) / 2;
-
- checkPopupPositionBounds();
- updateScreenSize();
-
- updatePopupSize((int) Math.min(screenWidth, newWidth), -1);
- return true;
- }
- }
- return false;
- }
-
- /*//////////////////////////////////////////////////////////////////////////
- // Utils
- //////////////////////////////////////////////////////////////////////////*/
-
- private int distanceFromCloseButton(final MotionEvent popupMotionEvent) {
- final int closeOverlayButtonX = closeOverlayButton.getLeft()
- + closeOverlayButton.getWidth() / 2;
- final int closeOverlayButtonY = closeOverlayButton.getTop()
- + closeOverlayButton.getHeight() / 2;
-
- float fingerX = popupLayoutParams.x + popupMotionEvent.getX();
- float fingerY = popupLayoutParams.y + popupMotionEvent.getY();
-
- return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2)
- + Math.pow(closeOverlayButtonY - fingerY, 2));
- }
-
- private float getClosingRadius() {
- final int buttonRadius = closeOverlayButton.getWidth() / 2;
- // 20% wider than the button itself
- return buttonRadius * 1.2f;
- }
-
- private boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) {
- return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
- }
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java
deleted file mode 100644
index efb4176a65c..00000000000
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayerActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.schabi.newpipe.player;
-
-import android.content.Intent;
-import android.view.MenuItem;
-
-import org.schabi.newpipe.R;
-
-import static org.schabi.newpipe.player.PopupVideoPlayer.ACTION_CLOSE;
-
-public final class PopupVideoPlayerActivity extends ServicePlayerActivity {
-
- private static final String TAG = "PopupVideoPlayerActivity";
-
- @Override
- public String getTag() {
- return TAG;
- }
-
- @Override
- public String getSupportActionTitle() {
- return getResources().getString(R.string.title_activity_popup_player);
- }
-
- @Override
- public Intent getBindIntent() {
- return new Intent(this, PopupVideoPlayer.class);
- }
-
- @Override
- public void startPlayerListener() {
- if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
- ((PopupVideoPlayer.VideoPlayerImpl) player).setActivityListener(this);
- }
- }
-
- @Override
- public void stopPlayerListener() {
- if (player != null && player instanceof PopupVideoPlayer.VideoPlayerImpl) {
- ((PopupVideoPlayer.VideoPlayerImpl) player).removeActivityListener(this);
- }
- }
-
- @Override
- public int getPlayerOptionMenuResource() {
- return R.menu.menu_play_queue_popup;
- }
-
- @Override
- public boolean onPlayerOptionSelected(final MenuItem item) {
- if (item.getItemId() == R.id.action_switch_background) {
- this.player.setRecovery();
- getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
- getApplicationContext().startService(
- getSwitchIntent(BackgroundPlayer.class)
- .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
- );
- return true;
- }
- return false;
- }
-
- @Override
- public Intent getPlayerShutdownIntent() {
- return new Intent(ACTION_CLOSE);
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
index 72becef8ff1..0ffd7f5949d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java
@@ -27,17 +27,21 @@
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
+import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
+import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
+import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -110,7 +114,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
public abstract boolean onPlayerOptionSelected(MenuItem item);
- public abstract Intent getPlayerShutdownIntent();
+ public abstract void setupMenu(Menu m);
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
@@ -152,6 +156,13 @@ public boolean onCreateOptionsMenu(final Menu m) {
return true;
}
+ // Allow to setup visibility of menuItems
+ @Override
+ public boolean onPrepareOptionsMenu(final Menu m) {
+ setupMenu(m);
+ return super.onPrepareOptionsMenu(m);
+ }
+
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
@@ -175,11 +186,9 @@ public boolean onOptionsItemSelected(final MenuItem item) {
return true;
case R.id.action_switch_main:
this.player.setRecovery();
- getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
getApplicationContext().startActivity(
- getSwitchIntent(MainVideoPlayer.class)
- .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
- );
+ getSwitchIntent(MainActivity.class, MainPlayer.PlayerType.VIDEO)
+ .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()));
return true;
}
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
@@ -191,13 +200,22 @@ protected void onDestroy() {
unbind();
}
- protected Intent getSwitchIntent(final Class clazz) {
+ protected Intent getSwitchIntent(final Class clazz, final MainPlayer.PlayerType playerType) {
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
this.player.getPlayQueue(), this.player.getRepeatMode(),
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
- this.player.getPlaybackSkipSilence(), null, false, false, this.player.isMuted())
+ this.player.getPlaybackSkipSilence(),
+ null,
+ true,
+ !this.player.isPlaying(),
+ this.player.isMuted())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying());
+ .putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM)
+ .putExtra(Constants.KEY_URL, this.player.getVideoUrl())
+ .putExtra(Constants.KEY_TITLE, this.player.getVideoTitle())
+ .putExtra(Constants.KEY_SERVICE_ID,
+ this.player.getCurrentMetadata().getMetadata().getServiceId())
+ .putExtra(VideoPlayer.PLAYER_TYPE, playerType);
}
////////////////////////////////////////////////////////////////////////////
@@ -247,6 +265,8 @@ public void onServiceConnected(final ComponentName name, final IBinder service)
if (service instanceof PlayerServiceBinder) {
player = ((PlayerServiceBinder) service).getPlayerInstance();
+ } else if (service instanceof MainPlayer.LocalBinder) {
+ player = ((MainPlayer.LocalBinder) service).getPlayer();
}
if (player == null || player.getPlayQueue() == null
@@ -500,7 +520,7 @@ private void openPlaybackParameterDialog() {
return;
}
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
- player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
+ player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
}
@Override
@@ -560,7 +580,7 @@ private void openPlaylistAppendDialog(final List playlist) {
////////////////////////////////////////////////////////////////////////////
private void shareUrl(final String subject, final String url) {
- Intent intent = new Intent(Intent.ACTION_SEND);
+ final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, url);
@@ -571,6 +591,10 @@ private void shareUrl(final String subject, final String url) {
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////
+ @Override
+ public void onQueueUpdate(final PlayQueue queue) {
+ }
+
@Override
public void onPlaybackUpdate(final int state, final int repeatMode, final boolean shuffled,
final PlaybackParameters parameters) {
@@ -610,7 +634,7 @@ public void onProgressUpdate(final int currentProgress, final int duration,
}
@Override
- public void onMetadataUpdate(final StreamInfo info) {
+ public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (info != null) {
metadataTitle.setText(info.getName());
metadataArtist.setText(info.getUploaderName());
@@ -710,7 +734,7 @@ private void onMaybePlaybackAdapterChanged() {
private void onMaybeMuteChanged() {
if (menu != null && player != null) {
- MenuItem item = menu.findItem(R.id.action_mute);
+ final MenuItem item = menu.findItem(R.id.action_mute);
//Change the mute-button item in ActionBar
//1) Text change:
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
index 576d42a00ee..2416403897b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
@@ -34,16 +34,16 @@
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
+
import android.view.Menu;
import android.view.MenuItem;
-import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
@@ -69,6 +69,7 @@
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
+import org.schabi.newpipe.views.ExpandableSurfaceView;
import java.util.ArrayList;
import java.util.List;
@@ -117,8 +118,7 @@ public abstract class VideoPlayer extends BasePlayer
private View rootView;
- private AspectRatioFrameLayout aspectRatioFrameLayout;
- private SurfaceView surfaceView;
+ private ExpandableSurfaceView surfaceView;
private View surfaceForeground;
private View loadingPanel;
@@ -135,7 +135,7 @@ public abstract class VideoPlayer extends BasePlayer
private TextView playbackLiveSync;
private TextView playbackSpeedTextView;
- private View topControlsRoot;
+ private LinearLayout topControlsRoot;
private TextView qualityTextView;
private SubtitleView subtitleView;
@@ -167,7 +167,7 @@ public VideoPlayer(final String debugTag, final Context context) {
// workaround to match normalized captions like english to English or deutsch to Deutsch
private static boolean containsCaseInsensitive(final List list, final String toFind) {
- for (String i : list) {
+ for (final String i : list) {
if (i.equalsIgnoreCase(toFind)) {
return true;
}
@@ -182,7 +182,6 @@ public void setup(final View view) {
public void initViews(final View view) {
this.rootView = view;
- this.aspectRatioFrameLayout = view.findViewById(R.id.aspectRatioLayout);
this.surfaceView = view.findViewById(R.id.surfaceView);
this.surfaceForeground = view.findViewById(R.id.surfaceForeground);
this.loadingPanel = view.findViewById(R.id.loading_panel);
@@ -207,12 +206,10 @@ public void initViews(final View view) {
this.resizeView = view.findViewById(R.id.resizeTextView);
resizeView.setText(PlayerHelper
- .resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode()));
+ .resizeTypeOf(context, getSurfaceView().getResizeMode()));
this.captionTextView = view.findViewById(R.id.captionTextView);
- //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);
}
@@ -282,7 +279,7 @@ public void buildQualityMenu() {
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
for (int i = 0; i < availableStreams.size(); i++) {
- VideoStream videoStream = availableStreams.get(i);
+ final VideoStream videoStream = availableStreams.get(i);
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat
.getNameById(videoStream.getFormatId()) + " " + videoStream.resolution);
}
@@ -314,7 +311,7 @@ private void buildCaptionMenu(final List availableLanguages) {
}
captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId);
- String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
+ final String userPreferredLanguage = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.caption_user_set_key), null);
/*
* only search for autogenerated cc as fallback
@@ -326,7 +323,7 @@ private void buildCaptionMenu(final List availableLanguages) {
&& !userPreferredLanguage.contains("(");
// Add option for turning off caption
- MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
+ final MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
0, Menu.NONE, R.string.caption_none);
captionOffItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
@@ -342,7 +339,7 @@ private void buildCaptionMenu(final List availableLanguages) {
// Add all available captions
for (int i = 0; i < availableLanguages.size(); i++) {
final String captionLanguage = availableLanguages.get(i);
- MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
+ final MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId,
i + 1, Menu.NONE, captionLanguage);
captionItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
@@ -520,7 +517,6 @@ public void onCompleted() {
super.onCompleted();
showControls(500);
- animateView(endScreen, true, 800);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
loadingPanel.setVisibility(View.GONE);
@@ -555,7 +551,7 @@ public void onVideoSizeChanged(final int width, final int height,
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
}
- aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
+ getSurfaceView().setAspectRatio(((float) width) / height);
}
@Override
@@ -583,7 +579,7 @@ private void onTextTrackUpdate() {
.getTrackGroups(textRenderer);
// Extract all loaded languages
- List availableLanguages = new ArrayList<>(textTracks.length);
+ final List availableLanguages = new ArrayList<>(textTracks.length);
for (int i = 0; i < textTracks.length; i++) {
final TrackGroup textTrack = textTracks.get(i);
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
@@ -620,12 +616,6 @@ public void onPrepared(final boolean playWhenReady) {
playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed()));
super.onPrepared(playWhenReady);
-
- if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) {
- controlsVisibilityHandler.removeCallbacksAndMessages(null);
- controlsVisibilityHandler
- .postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION);
- }
}
@Override
@@ -675,7 +665,7 @@ public void onLoadingComplete(final String imageUri, final View view,
}
}
- protected void onFullScreenButtonClicked() {
+ protected void toggleFullscreen() {
changeState(STATE_BLOCKED);
}
@@ -739,8 +729,8 @@ public boolean onMenuItemClick(final MenuItem menuItem) {
qualityTextView.setText(menuItem.getTitle());
return true;
} else if (playbackSpeedPopupMenuGroupId == menuItem.getGroupId()) {
- int speedIndex = menuItem.getItemId();
- float speed = PLAYBACK_SPEEDS[speedIndex];
+ final int speedIndex = menuItem.getItemId();
+ final float speed = PLAYBACK_SPEEDS[speedIndex];
setPlaybackSpeed(speed);
playbackSpeedTextView.setText(formatSpeed(speed));
@@ -799,16 +789,16 @@ private void onCaptionClicked() {
showControls(DEFAULT_CONTROLS_DURATION);
}
- private void onResizeClicked() {
- if (getAspectRatioFrameLayout() != null) {
- final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
+ void onResizeClicked() {
+ if (getSurfaceView() != null) {
+ final int currentResizeMode = getSurfaceView().getResizeMode();
final int newResizeMode = nextResizeMode(currentResizeMode);
setResizeMode(newResizeMode);
}
}
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
- getAspectRatioFrameLayout().setResizeMode(resizeMode);
+ getSurfaceView().setResizeMode(resizeMode);
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
}
@@ -916,9 +906,9 @@ public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd)
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)
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1.0f, 0.0f),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1.0f),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1.0f)
).setDuration(DEFAULT_CONTROLS_DURATION);
controlViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -931,10 +921,10 @@ public void onAnimationEnd(final Animator animation) {
return;
}
- float scaleFrom = goneOnEnd ? 1f : 1f;
- float scaleTo = goneOnEnd ? 1.8f : 1.4f;
- float alphaFrom = goneOnEnd ? 1f : 0f;
- float alphaTo = goneOnEnd ? 0f : 1f;
+ final float scaleFrom = goneOnEnd ? 1f : 1f;
+ final float scaleTo = goneOnEnd ? 1.8f : 1.4f;
+ final float alphaFrom = goneOnEnd ? 1f : 0f;
+ final float alphaTo = goneOnEnd ? 0f : 1f;
controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
@@ -1020,6 +1010,9 @@ private Runnable hideControlsAndButtonHandler(final long duration, final View vi
animateView(controlsRoot, false, duration);
};
}
+
+ public abstract void hideSystemUIIfNeeded();
+
/*//////////////////////////////////////////////////////////////////////////
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/
@@ -1033,11 +1026,7 @@ public void setPlaybackQuality(final String quality) {
this.resolver.setPlaybackQuality(quality);
}
- public AspectRatioFrameLayout getAspectRatioFrameLayout() {
- return aspectRatioFrameLayout;
- }
-
- public SurfaceView getSurfaceView() {
+ public ExpandableSurfaceView getSurfaceView() {
return surfaceView;
}
@@ -1096,7 +1085,7 @@ public TextView getPlaybackEndTime() {
return playbackEndTime;
}
- public View getTopControlsRoot() {
+ public LinearLayout getTopControlsRoot() {
return topControlsRoot;
}
@@ -1108,6 +1097,10 @@ public PopupMenu getQualityPopupMenu() {
return qualityPopupMenu;
}
+ public TextView getPlaybackSpeedTextView() {
+ return playbackSpeedTextView;
+ }
+
public PopupMenu getPlaybackSpeedPopupMenu() {
return playbackSpeedPopupMenu;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
new file mode 100644
index 00000000000..0e06c1aaf4e
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
@@ -0,0 +1,2177 @@
+/*
+ * Copyright 2017 Mauricio Colli
+ * Part of NewPipe
+ *
+ * License: GPL-3.0+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.schabi.newpipe.player;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AnticipateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.text.CaptionStyleCompat;
+import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
+import com.google.android.exoplayer2.ui.SubtitleView;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.nostra13.universalimageloader.core.assist.FailReason;
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.extractor.stream.VideoStream;
+import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
+import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
+import org.schabi.newpipe.player.event.PlayerEventListener;
+import org.schabi.newpipe.player.event.PlayerGestureListener;
+import org.schabi.newpipe.player.event.PlayerServiceEventListener;
+import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
+import org.schabi.newpipe.player.helper.PlayerHelper;
+import org.schabi.newpipe.player.playqueue.PlayQueue;
+import org.schabi.newpipe.player.playqueue.PlayQueueItem;
+import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
+import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
+import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
+import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
+import org.schabi.newpipe.player.resolver.MediaSourceTag;
+import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
+import org.schabi.newpipe.util.DeviceUtils;
+import org.schabi.newpipe.util.AnimationUtils;
+import org.schabi.newpipe.util.Constants;
+import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.ListHelper;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.ShareUtils;
+
+import java.util.List;
+
+import static android.content.Context.WINDOW_SERVICE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_OPEN_CONTROLS;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS;
+import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT;
+import static org.schabi.newpipe.player.MainPlayer.NOTIFICATION_ID;
+import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
+import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
+import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA;
+import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
+import static org.schabi.newpipe.util.AnimationUtils.animateView;
+import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
+import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
+/**
+ * Unified UI for all players.
+ *
+ * @author mauriciocolli
+ */
+
+public class VideoPlayerImpl extends VideoPlayer
+ implements View.OnLayoutChangeListener,
+ PlaybackParameterDialog.Callback,
+ View.OnLongClickListener {
+ private static final String TAG = ".VideoPlayerImpl";
+
+ static final String POPUP_SAVED_WIDTH = "popup_saved_width";
+ static final String POPUP_SAVED_X = "popup_saved_x";
+ static final String POPUP_SAVED_Y = "popup_saved_y";
+ private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300;
+ private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+
+ private static final float MAX_GESTURE_LENGTH = 0.75f;
+ private static final int NOTIFICATION_UPDATES_BEFORE_RESET = 60;
+
+ private TextView titleTextView;
+ private TextView channelTextView;
+ private RelativeLayout volumeRelativeLayout;
+ private ProgressBar volumeProgressBar;
+ private ImageView volumeImageView;
+ private RelativeLayout brightnessRelativeLayout;
+ private ProgressBar brightnessProgressBar;
+ private ImageView brightnessImageView;
+ private TextView resizingIndicator;
+ private ImageButton queueButton;
+ private ImageButton repeatButton;
+ private ImageButton shuffleButton;
+ private ImageButton playWithKodi;
+ private ImageButton openInBrowser;
+ private ImageButton fullscreenButton;
+ private ImageButton playerCloseButton;
+ private ImageButton screenRotationButton;
+ private ImageButton muteButton;
+
+ private ImageButton playPauseButton;
+ private ImageButton playPreviousButton;
+ private ImageButton playNextButton;
+
+ private RelativeLayout queueLayout;
+ private ImageButton itemsListCloseButton;
+ private RecyclerView itemsList;
+ private ItemTouchHelper itemTouchHelper;
+
+ private boolean queueVisible;
+ private MainPlayer.PlayerType playerType = MainPlayer.PlayerType.VIDEO;
+
+ private ImageButton moreOptionsButton;
+ private ImageButton shareButton;
+
+ private View primaryControls;
+ private View secondaryControls;
+
+ private int maxGestureLength;
+
+ private boolean audioOnly = false;
+ private boolean isFullscreen = false;
+ private boolean isVerticalVideo = false;
+ private boolean fragmentIsVisible = false;
+ boolean shouldUpdateOnProgress;
+ int timesNotificationUpdated;
+
+ private final MainPlayer service;
+ private PlayerServiceEventListener fragmentListener;
+ private PlayerEventListener activityListener;
+ private GestureDetector gestureDetector;
+ private final SharedPreferences defaultPreferences;
+ private ContentObserver settingsContentObserver;
+ @NonNull
+ private final AudioPlaybackResolver resolver;
+
+ private int cachedDuration;
+ private String cachedDurationString;
+
+ // Popup
+ private WindowManager.LayoutParams popupLayoutParams;
+ public WindowManager windowManager;
+
+ private View closingOverlayView;
+ private View closeOverlayView;
+ private FloatingActionButton closeOverlayButton;
+
+ public boolean isPopupClosing = false;
+
+ private float screenWidth;
+ private float screenHeight;
+ private float popupWidth;
+ private float popupHeight;
+ private float minimumWidth;
+ private float minimumHeight;
+ private float maximumWidth;
+ private float maximumHeight;
+ // Popup end
+
+
+ @Override
+ public void handleIntent(final Intent intent) {
+ if (intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) == null) {
+ return;
+ }
+
+ final MainPlayer.PlayerType oldPlayerType = playerType;
+ choosePlayerTypeFromIntent(intent);
+ audioOnly = audioPlayerSelected();
+
+ // We need to setup audioOnly before super(), see "sourceOf"
+ super.handleIntent(intent);
+
+ if (oldPlayerType != playerType && playQueue != null) {
+ // If playerType changes from one to another we should reload the player
+ // (to disable/enable video stream or to set quality)
+ setRecovery();
+ reload();
+ }
+
+ setupElementsVisibility();
+ setupElementsSize();
+
+ if (audioPlayerSelected()) {
+ service.removeViewFromParent();
+ } else if (popupPlayerSelected()) {
+ getRootView().setVisibility(View.VISIBLE);
+ initPopup();
+ initPopupCloseOverlay();
+ playPauseButton.requestFocus();
+ } else {
+ getRootView().setVisibility(View.VISIBLE);
+ initVideoPlayer();
+ // Android TV: without it focus will frame the whole player
+ playPauseButton.requestFocus();
+ }
+
+ onPlay();
+ }
+
+ VideoPlayerImpl(final MainPlayer service) {
+ super("MainPlayer" + TAG, service);
+ this.service = service;
+ this.shouldUpdateOnProgress = true;
+ this.windowManager = (WindowManager) service.getSystemService(WINDOW_SERVICE);
+ this.defaultPreferences = PreferenceManager.getDefaultSharedPreferences(service);
+ this.resolver = new AudioPlaybackResolver(context, dataSource);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public void initViews(final View view) {
+ super.initViews(view);
+ this.titleTextView = view.findViewById(R.id.titleTextView);
+ this.channelTextView = view.findViewById(R.id.channelTextView);
+ this.volumeRelativeLayout = view.findViewById(R.id.volumeRelativeLayout);
+ this.volumeProgressBar = view.findViewById(R.id.volumeProgressBar);
+ this.volumeImageView = view.findViewById(R.id.volumeImageView);
+ this.brightnessRelativeLayout = view.findViewById(R.id.brightnessRelativeLayout);
+ this.brightnessProgressBar = view.findViewById(R.id.brightnessProgressBar);
+ this.brightnessImageView = view.findViewById(R.id.brightnessImageView);
+ this.resizingIndicator = view.findViewById(R.id.resizing_indicator);
+ this.queueButton = view.findViewById(R.id.queueButton);
+ this.repeatButton = view.findViewById(R.id.repeatButton);
+ this.shuffleButton = view.findViewById(R.id.shuffleButton);
+ this.playWithKodi = view.findViewById(R.id.playWithKodi);
+ this.openInBrowser = view.findViewById(R.id.openInBrowser);
+ this.fullscreenButton = view.findViewById(R.id.fullScreenButton);
+ this.screenRotationButton = view.findViewById(R.id.screenRotationButton);
+ this.playerCloseButton = view.findViewById(R.id.playerCloseButton);
+ this.muteButton = view.findViewById(R.id.switchMute);
+
+ this.playPauseButton = view.findViewById(R.id.playPauseButton);
+ this.playPreviousButton = view.findViewById(R.id.playPreviousButton);
+ this.playNextButton = view.findViewById(R.id.playNextButton);
+
+ this.moreOptionsButton = view.findViewById(R.id.moreOptionsButton);
+ this.primaryControls = view.findViewById(R.id.primaryControls);
+ this.secondaryControls = view.findViewById(R.id.secondaryControls);
+ this.shareButton = view.findViewById(R.id.share);
+
+ this.queueLayout = view.findViewById(R.id.playQueuePanel);
+ this.itemsListCloseButton = view.findViewById(R.id.playQueueClose);
+ this.itemsList = view.findViewById(R.id.playQueue);
+
+ closingOverlayView = view.findViewById(R.id.closingOverlay);
+
+ titleTextView.setSelected(true);
+ channelTextView.setSelected(true);
+ }
+
+ @Override
+ protected void setupSubtitleView(final @NonNull SubtitleView view,
+ final float captionScale,
+ @NonNull final CaptionStyleCompat captionStyle) {
+ if (popupPlayerSelected()) {
+ final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f;
+ view.setFractionalTextSize(SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio);
+ } else {
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels);
+ final float captionRatioInverse = 20f + 4f * (1.0f - captionScale);
+ view.setFixedTextSize(TypedValue.COMPLEX_UNIT_PX,
+ (float) minimumLength / captionRatioInverse);
+ }
+ view.setApplyEmbeddedStyles(captionStyle.equals(CaptionStyleCompat.DEFAULT));
+ view.setStyle(captionStyle);
+ }
+
+ /**
+ * This method ensures that popup and main players have different look.
+ * We use one layout for both players and need to decide what to show and what to hide.
+ * Additional measuring should be done inside {@link #setupElementsSize}.
+ * {@link #setControlsSize} is used to adapt the UI to fullscreen mode, multiWindow, navBar, etc
+ */
+ private void setupElementsVisibility() {
+ if (popupPlayerSelected()) {
+ fullscreenButton.setVisibility(View.VISIBLE);
+ screenRotationButton.setVisibility(View.GONE);
+ getResizeView().setVisibility(View.GONE);
+ getRootView().findViewById(R.id.metadataView).setVisibility(View.GONE);
+ queueButton.setVisibility(View.GONE);
+ moreOptionsButton.setVisibility(View.GONE);
+ getTopControlsRoot().setOrientation(LinearLayout.HORIZONTAL);
+ primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.WRAP_CONTENT;
+ secondaryControls.setAlpha(1.0f);
+ secondaryControls.setVisibility(View.VISIBLE);
+ secondaryControls.setTranslationY(0);
+ shareButton.setVisibility(View.GONE);
+ playWithKodi.setVisibility(View.GONE);
+ openInBrowser.setVisibility(View.GONE);
+ muteButton.setVisibility(View.GONE);
+ playerCloseButton.setVisibility(View.GONE);
+ getTopControlsRoot().bringToFront();
+ getTopControlsRoot().setClickable(false);
+ getTopControlsRoot().setFocusable(false);
+ getBottomControlsRoot().bringToFront();
+ onQueueClosed();
+ } else {
+ fullscreenButton.setVisibility(View.GONE);
+ setupScreenRotationButton();
+ getResizeView().setVisibility(View.VISIBLE);
+ getRootView().findViewById(R.id.metadataView).setVisibility(View.VISIBLE);
+ moreOptionsButton.setVisibility(View.VISIBLE);
+ getTopControlsRoot().setOrientation(LinearLayout.VERTICAL);
+ primaryControls.getLayoutParams().width = LinearLayout.LayoutParams.MATCH_PARENT;
+ secondaryControls.setVisibility(View.INVISIBLE);
+ moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(service,
+ R.drawable.ic_expand_more_white_24dp));
+ shareButton.setVisibility(View.VISIBLE);
+ showHideKodiButton();
+ openInBrowser.setVisibility(View.VISIBLE);
+ muteButton.setVisibility(View.VISIBLE);
+ playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE);
+ // Top controls have a large minHeight which is allows to drag the player
+ // down in fullscreen mode (just larger area to make easy to locate by finger)
+ getTopControlsRoot().setClickable(true);
+ getTopControlsRoot().setFocusable(true);
+ }
+ if (!isFullscreen()) {
+ titleTextView.setVisibility(View.GONE);
+ channelTextView.setVisibility(View.GONE);
+ } else {
+ titleTextView.setVisibility(View.VISIBLE);
+ channelTextView.setVisibility(View.VISIBLE);
+ }
+ setMuteButton(muteButton, isMuted());
+
+ animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0);
+ }
+
+ /**
+ * Changes padding, size of elements based on player selected right now.
+ * Popup player has small padding in comparison with the main player
+ */
+ private void setupElementsSize() {
+ if (popupPlayerSelected()) {
+ final int controlsPadding = service.getResources()
+ .getDimensionPixelSize(R.dimen.player_popup_controls_padding);
+ final int buttonsPadding = service.getResources()
+ .getDimensionPixelSize(R.dimen.player_popup_buttons_padding);
+ getTopControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
+ getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
+ getQualityTextView().setPadding(
+ buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ getPlaybackSpeedTextView().setPadding(
+ buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ getCaptionTextView().setPadding(
+ buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ getPlaybackSpeedTextView().setMinimumWidth(0);
+ } else if (videoPlayerSelected()) {
+ final int buttonsMinWidth = service.getResources()
+ .getDimensionPixelSize(R.dimen.player_main_buttons_min_width);
+ final int playerTopPadding = service.getResources()
+ .getDimensionPixelSize(R.dimen.player_main_top_padding);
+ final int controlsPadding = service.getResources()
+ .getDimensionPixelSize(R.dimen.player_main_controls_padding);
+ final int buttonsPadding = service.getResources()
+ .getDimensionPixelSize(R.dimen.player_main_buttons_padding);
+ getTopControlsRoot().setPaddingRelative(
+ controlsPadding, playerTopPadding, controlsPadding, 0);
+ getBottomControlsRoot().setPaddingRelative(controlsPadding, 0, controlsPadding, 0);
+ getQualityTextView().setPadding(
+ buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ getPlaybackSpeedTextView().setPadding(
+ buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ getPlaybackSpeedTextView().setMinimumWidth(buttonsMinWidth);
+ getCaptionTextView().setPadding(
+ buttonsPadding, buttonsPadding, buttonsPadding, buttonsPadding);
+ }
+ }
+
+ @Override
+ public void initListeners() {
+ super.initListeners();
+
+ final PlayerGestureListener listener = new PlayerGestureListener(this, service);
+ gestureDetector = new GestureDetector(context, listener);
+ getRootView().setOnTouchListener(listener);
+
+ queueButton.setOnClickListener(this);
+ repeatButton.setOnClickListener(this);
+ shuffleButton.setOnClickListener(this);
+
+ playPauseButton.setOnClickListener(this);
+ playPreviousButton.setOnClickListener(this);
+ playNextButton.setOnClickListener(this);
+
+ moreOptionsButton.setOnClickListener(this);
+ moreOptionsButton.setOnLongClickListener(this);
+ shareButton.setOnClickListener(this);
+ fullscreenButton.setOnClickListener(this);
+ screenRotationButton.setOnClickListener(this);
+ playWithKodi.setOnClickListener(this);
+ openInBrowser.setOnClickListener(this);
+ playerCloseButton.setOnClickListener(this);
+ muteButton.setOnClickListener(this);
+
+ settingsContentObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(final boolean selfChange) {
+ setupScreenRotationButton();
+ }
+ };
+ service.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false,
+ settingsContentObserver);
+ getRootView().addOnLayoutChangeListener(this);
+ }
+
+ public boolean onKeyDown(final int keyCode) {
+ switch (keyCode) {
+ default:
+ break;
+ case KeyEvent.KEYCODE_BACK:
+ if (DeviceUtils.isTv(service) && isControlsVisible()) {
+ hideControls(0, 0);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (getRootView().hasFocus() && !getControlsRoot().hasFocus()) {
+ // do not interfere with focus in playlist etc.
+ return false;
+ }
+
+ if (getCurrentState() == BasePlayer.STATE_BLOCKED) {
+ return true;
+ }
+
+ if (!isControlsVisible()) {
+ if (!queueVisible) {
+ playPauseButton.requestFocus();
+ }
+ showControlsThenHide();
+ showSystemUIPartially();
+ return true;
+ } else {
+ hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME);
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ public AppCompatActivity getParentActivity() {
+ // ! instanceof ViewGroup means that view was added via windowManager for Popup
+ if (getRootView() == null
+ || getRootView().getParent() == null
+ || !(getRootView().getParent() instanceof ViewGroup)) {
+ return null;
+ }
+
+ final ViewGroup parent = (ViewGroup) getRootView().getParent();
+ return (AppCompatActivity) parent.getContext();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // View
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) {
+ switch (repeatMode) {
+ case Player.REPEAT_MODE_OFF:
+ imageButton.setImageResource(R.drawable.exo_controls_repeat_off);
+ break;
+ case Player.REPEAT_MODE_ONE:
+ imageButton.setImageResource(R.drawable.exo_controls_repeat_one);
+ break;
+ case Player.REPEAT_MODE_ALL:
+ imageButton.setImageResource(R.drawable.exo_controls_repeat_all);
+ break;
+ }
+ }
+
+ private void setShuffleButton(final ImageButton button, final boolean shuffled) {
+ final int shuffleAlpha = shuffled ? 255 : 77;
+ button.setImageAlpha(shuffleAlpha);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Playback Parameters Listener
+ ////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
+ final boolean playbackSkipSilence) {
+ setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
+ }
+
+ @Override
+ public void onVideoSizeChanged(final int width, final int height,
+ final int unappliedRotationDegrees,
+ final float pixelWidthHeightRatio) {
+ super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
+ isVerticalVideo = width < height;
+ prepareOrientation();
+ setupScreenRotationButton();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // ExoPlayer Video Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onRepeatModeChanged(final int i) {
+ super.onRepeatModeChanged(i);
+ updatePlaybackButtons();
+ updatePlayback();
+ service.resetNotification();
+ service.updateNotification(-1);
+ }
+
+ @Override
+ public void onShuffleClicked() {
+ super.onShuffleClicked();
+ updatePlaybackButtons();
+ updatePlayback();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Playback Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onPlayerError(final ExoPlaybackException error) {
+ super.onPlayerError(error);
+
+ if (fragmentListener != null) {
+ fragmentListener.onPlayerError(error);
+ }
+ }
+
+ protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
+ super.onMetadataChanged(tag);
+
+ showHideKodiButton();
+
+ titleTextView.setText(tag.getMetadata().getName());
+ channelTextView.setText(tag.getMetadata().getUploaderName());
+
+ service.resetNotification();
+ service.updateNotification(-1);
+ updateMetadata();
+ }
+
+ @Override
+ public void onPlaybackShutdown() {
+ if (DEBUG) {
+ Log.d(TAG, "onPlaybackShutdown() called");
+ }
+ service.onDestroy();
+ }
+
+ @Override
+ public void onMuteUnmuteButtonClicked() {
+ super.onMuteUnmuteButtonClicked();
+ updatePlayback();
+ setMuteButton(muteButton, isMuted());
+ }
+
+ @Override
+ public void onUpdateProgress(final int currentProgress,
+ final int duration, final int bufferPercent) {
+ super.onUpdateProgress(currentProgress, duration, bufferPercent);
+
+ updateProgress(currentProgress, duration, bufferPercent);
+
+ if (!shouldUpdateOnProgress || getCurrentState() == BasePlayer.STATE_COMPLETED
+ || getCurrentState() == BasePlayer.STATE_PAUSED || getPlayQueue() == null) {
+ return;
+ }
+
+ if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) {
+ service.resetNotification();
+ }
+
+ if (service.getBigNotRemoteView() != null) {
+ if (cachedDuration != duration) {
+ cachedDuration = duration;
+ cachedDurationString = getTimeString(duration);
+ }
+ service.getBigNotRemoteView()
+ .setProgressBar(R.id.notificationProgressBar,
+ duration, currentProgress, false);
+ service.getBigNotRemoteView()
+ .setTextViewText(R.id.notificationTime,
+ getTimeString(currentProgress) + " / " + cachedDurationString);
+ }
+ if (service.getNotRemoteView() != null) {
+ service.getNotRemoteView()
+ .setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
+ }
+ service.updateNotification(-1);
+ }
+
+ @Override
+ @Nullable
+ public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
+ // For LiveStream or video/popup players we can use super() method
+ // but not for audio player
+ if (!audioOnly) {
+ return super.sourceOf(item, info);
+ } else {
+ return resolver.resolve(info);
+ }
+ }
+
+ @Override
+ public void onPlayPrevious() {
+ super.onPlayPrevious();
+ triggerProgressUpdate();
+ }
+
+ @Override
+ public void onPlayNext() {
+ super.onPlayNext();
+ triggerProgressUpdate();
+ }
+
+ @Override
+ protected void initPlayback(@NonNull final PlayQueue queue, final int repeatMode,
+ final float playbackSpeed, final float playbackPitch,
+ final boolean playbackSkipSilence,
+ final boolean playOnReady, final boolean isMuted) {
+ super.initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
+ playbackSkipSilence, playOnReady, isMuted);
+ updateQueue();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player Overrides
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void toggleFullscreen() {
+ if (DEBUG) {
+ Log.d(TAG, "toggleFullscreen() called");
+ }
+ if (simpleExoPlayer == null || getCurrentMetadata() == null) {
+ return;
+ }
+
+ if (popupPlayerSelected()) {
+ setRecovery();
+ service.removeViewFromParent();
+ final Intent intent = NavigationHelper.getPlayerIntent(
+ service,
+ MainActivity.class,
+ this.getPlayQueue(),
+ this.getRepeatMode(),
+ this.getPlaybackSpeed(),
+ this.getPlaybackPitch(),
+ this.getPlaybackSkipSilence(),
+ null,
+ true,
+ !isPlaying(),
+ isMuted()
+ );
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Constants.KEY_SERVICE_ID,
+ getCurrentMetadata().getMetadata().getServiceId());
+ intent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
+ intent.putExtra(Constants.KEY_URL, getVideoUrl());
+ intent.putExtra(Constants.KEY_TITLE, getVideoTitle());
+ intent.putExtra(VideoDetailFragment.AUTO_PLAY, true);
+ service.onDestroy();
+ context.startActivity(intent);
+ return;
+ } else {
+ if (fragmentListener == null) {
+ return;
+ }
+
+ isFullscreen = !isFullscreen;
+ setControlsSize();
+ fragmentListener.onFullscreenStateChanged(isFullscreen());
+ }
+
+ if (!isFullscreen()) {
+ titleTextView.setVisibility(View.GONE);
+ channelTextView.setVisibility(View.GONE);
+ playerCloseButton.setVisibility(videoPlayerSelected() ? View.VISIBLE : View.GONE);
+ } else {
+ titleTextView.setVisibility(View.VISIBLE);
+ channelTextView.setVisibility(View.VISIBLE);
+ playerCloseButton.setVisibility(View.GONE);
+ }
+ setupScreenRotationButton();
+ }
+
+ @Override
+ public void onClick(final View v) {
+ super.onClick(v);
+ if (v.getId() == playPauseButton.getId()) {
+ onPlayPause();
+ } else if (v.getId() == playPreviousButton.getId()) {
+ onPlayPrevious();
+ } else if (v.getId() == playNextButton.getId()) {
+ onPlayNext();
+ } else if (v.getId() == queueButton.getId()) {
+ onQueueClicked();
+ return;
+ } else if (v.getId() == repeatButton.getId()) {
+ onRepeatClicked();
+ return;
+ } else if (v.getId() == shuffleButton.getId()) {
+ onShuffleClicked();
+ return;
+ } else if (v.getId() == moreOptionsButton.getId()) {
+ onMoreOptionsClicked();
+ } else if (v.getId() == shareButton.getId()) {
+ onShareClicked();
+ } else if (v.getId() == playWithKodi.getId()) {
+ onPlayWithKodiClicked();
+ } else if (v.getId() == openInBrowser.getId()) {
+ onOpenInBrowserClicked();
+ } else if (v.getId() == fullscreenButton.getId()) {
+ toggleFullscreen();
+ } else if (v.getId() == screenRotationButton.getId()) {
+ if (!isVerticalVideo) {
+ fragmentListener.onScreenRotationButtonClicked();
+ } else {
+ toggleFullscreen();
+ }
+ } else if (v.getId() == muteButton.getId()) {
+ onMuteUnmuteButtonClicked();
+ } else if (v.getId() == playerCloseButton.getId()) {
+ service.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER));
+ }
+
+ if (getCurrentState() != STATE_COMPLETED) {
+ getControlsVisibilityHandler().removeCallbacksAndMessages(null);
+ animateView(getControlsRoot(), true, DEFAULT_CONTROLS_DURATION, 0, () -> {
+ if (getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible()) {
+ if (v.getId() == playPauseButton.getId()) {
+ hideControls(0, 0);
+ } else {
+ hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean onLongClick(final View v) {
+ if (v.getId() == moreOptionsButton.getId() && isFullscreen()) {
+ fragmentListener.onMoreOptionsLongClicked();
+ hideControls(0, 0);
+ hideSystemUIIfNeeded();
+ }
+ return true;
+ }
+
+ private void onQueueClicked() {
+ queueVisible = true;
+
+ hideSystemUIIfNeeded();
+ buildQueue();
+ updatePlaybackButtons();
+
+ getControlsRoot().setVisibility(View.INVISIBLE);
+ queueLayout.requestFocus();
+ animateView(queueLayout, SLIDE_AND_ALPHA, true,
+ DEFAULT_CONTROLS_DURATION);
+
+ itemsList.scrollToPosition(playQueue.getIndex());
+ }
+
+ public void onQueueClosed() {
+ if (!queueVisible) {
+ return;
+ }
+
+ animateView(queueLayout, SLIDE_AND_ALPHA, false,
+ DEFAULT_CONTROLS_DURATION, 0, () -> {
+ // Even when queueLayout is GONE it receives touch events
+ // and ruins normal behavior of the app. This line fixes it
+ queueLayout.setTranslationY(-queueLayout.getHeight() * 5);
+ });
+ queueVisible = false;
+ playPauseButton.requestFocus();
+ }
+
+ private void onMoreOptionsClicked() {
+ if (DEBUG) {
+ Log.d(TAG, "onMoreOptionsClicked() called");
+ }
+
+ final boolean isMoreControlsVisible = secondaryControls.getVisibility() == View.VISIBLE;
+
+ animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION,
+ isMoreControlsVisible ? 0 : 180);
+ animateView(secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible,
+ DEFAULT_CONTROLS_DURATION, 0,
+ () -> {
+ // Fix for a ripple effect on background drawable.
+ // When view returns from GONE state it takes more milliseconds than returning
+ // from INVISIBLE state. And the delay makes ripple background end to fast
+ if (isMoreControlsVisible) {
+ secondaryControls.setVisibility(View.INVISIBLE);
+ }
+ });
+ showControls(DEFAULT_CONTROLS_DURATION);
+ }
+
+ private void onShareClicked() {
+ // share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
+ // Timestamp doesn't make sense in a live stream so drop it
+ final String ts = isLive() ? "" : ("&t=" + (getPlaybackSeekBar().getProgress() / 1000));
+ ShareUtils.shareUrl(service,
+ getVideoTitle(),
+ getVideoUrl() + ts);
+ }
+
+ private void onPlayWithKodiClicked() {
+ if (getCurrentMetadata() == null) {
+ return;
+ }
+ onPause();
+ try {
+ NavigationHelper.playWithKore(getParentActivity(), Uri.parse(getVideoUrl()));
+ } catch (final Exception e) {
+ if (DEBUG) {
+ Log.i(TAG, "Failed to start kore", e);
+ }
+ KoreUtil.showInstallKoreDialog(getParentActivity());
+ }
+ }
+
+ private void onOpenInBrowserClicked() {
+ if (getCurrentMetadata() == null) {
+ return;
+ }
+
+ ShareUtils.openUrlInBrowser(getParentActivity(),
+ getCurrentMetadata().getMetadata().getOriginalUrl());
+ }
+
+ private void showHideKodiButton() {
+ final boolean kodiEnabled = defaultPreferences.getBoolean(
+ service.getString(R.string.show_play_with_kodi_key), false);
+ // show kodi button if it supports the current service and it is enabled in settings
+ final boolean showKodiButton = playQueue != null && playQueue.getItem() != null
+ && KoreUtil.isServiceSupportedByKore(playQueue.getItem().getServiceId())
+ && PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
+ playWithKodi.setVisibility(videoPlayerSelected() && kodiEnabled && showKodiButton
+ ? View.VISIBLE : View.GONE);
+ }
+
+ private void setupScreenRotationButton() {
+ final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(service);
+ final boolean tabletInLandscape = DeviceUtils.isTablet(service) && service.isLandscape();
+ final boolean showButton = videoPlayerSelected()
+ && (orientationLocked || isVerticalVideo || tabletInLandscape);
+ screenRotationButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
+ screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(service, isFullscreen()
+ ? R.drawable.ic_fullscreen_exit_white_24dp
+ : R.drawable.ic_fullscreen_white_24dp));
+ }
+
+ private void prepareOrientation() {
+ final boolean orientationLocked = PlayerHelper.globalScreenOrientationLocked(service);
+ if (orientationLocked
+ && isFullscreen()
+ && service.isLandscape() == isVerticalVideo
+ && fragmentListener != null) {
+ fragmentListener.onScreenRotationButtonClicked();
+ }
+ }
+
+ @Override
+ public void onPlaybackSpeedClicked() {
+ if (videoPlayerSelected()) {
+ PlaybackParameterDialog
+ .newInstance(
+ getPlaybackSpeed(), getPlaybackPitch(), getPlaybackSkipSilence(), this)
+ .show(getParentActivity().getSupportFragmentManager(), null);
+ } else {
+ super.onPlaybackSpeedClicked();
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(final SeekBar seekBar) {
+ super.onStopTrackingTouch(seekBar);
+ if (wasPlaying()) {
+ showControlsThenHide();
+ }
+ }
+
+ @Override
+ public void onDismiss(final PopupMenu menu) {
+ super.onDismiss(menu);
+ if (isPlaying()) {
+ hideControls(DEFAULT_CONTROLS_DURATION, 0);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ public void onLayoutChange(final View view, final int l, final int t, final int r, final int b,
+ final int ol, final int ot, final int or, final int ob) {
+ if (l != ol || t != ot || r != or || b != ob) {
+ // Use smaller value to be consistent between screen orientations
+ // (and to make usage easier)
+ final int width = r - l;
+ final int height = b - t;
+ final int min = Math.min(width, height);
+ maxGestureLength = (int) (min * MAX_GESTURE_LENGTH);
+
+ if (DEBUG) {
+ Log.d(TAG, "maxGestureLength = " + maxGestureLength);
+ }
+
+ volumeProgressBar.setMax(maxGestureLength);
+ brightnessProgressBar.setMax(maxGestureLength);
+
+ setInitialGestureValues();
+ queueLayout.getLayoutParams().height = height - queueLayout.getTop();
+
+ if (popupPlayerSelected()) {
+ final float widthDp = Math.abs(r - l) / service.getResources()
+ .getDisplayMetrics().density;
+ final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP
+ ? View.VISIBLE
+ : View.GONE;
+ secondaryControls.setVisibility(visibility);
+ }
+ }
+ }
+
+ @Override
+ protected int nextResizeMode(final int currentResizeMode) {
+ final int newResizeMode;
+ switch (currentResizeMode) {
+ case AspectRatioFrameLayout.RESIZE_MODE_FIT:
+ newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL;
+ break;
+ case AspectRatioFrameLayout.RESIZE_MODE_FILL:
+ newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
+ break;
+ default:
+ newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
+ break;
+ }
+
+ storeResizeMode(newResizeMode);
+ return newResizeMode;
+ }
+
+ private void storeResizeMode(final @AspectRatioFrameLayout.ResizeMode int resizeMode) {
+ defaultPreferences.edit()
+ .putInt(service.getString(R.string.last_resize_mode), resizeMode)
+ .apply();
+ }
+
+ private void restoreResizeMode() {
+ setResizeMode(defaultPreferences.getInt(
+ service.getString(R.string.last_resize_mode),
+ AspectRatioFrameLayout.RESIZE_MODE_FIT));
+ }
+
+ @Override
+ protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
+ return new VideoPlaybackResolver.QualityResolver() {
+ @Override
+ public int getDefaultResolutionIndex(final List sortedVideos) {
+ return videoPlayerSelected()
+ ? ListHelper.getDefaultResolutionIndex(context, sortedVideos)
+ : ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
+ }
+
+ @Override
+ public int getOverrideResolutionIndex(final List sortedVideos,
+ final String playbackQuality) {
+ return videoPlayerSelected()
+ ? getResolutionIndex(context, sortedVideos, playbackQuality)
+ : getPopupResolutionIndex(context, sortedVideos, playbackQuality);
+ }
+ };
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // States
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void animatePlayButtons(final boolean show, final int duration) {
+ animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+ if (playQueue.getIndex() > 0 || !show) {
+ animateView(playPreviousButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+ }
+ if (playQueue.getIndex() + 1 < playQueue.getStreams().size() || !show) {
+ animateView(playNextButton, AnimationUtils.Type.SCALE_AND_ALPHA, show, duration);
+ }
+
+ }
+
+ @Override
+ public void changeState(final int state) {
+ super.changeState(state);
+ updatePlayback();
+ }
+
+ @Override
+ public void onBlocked() {
+ super.onBlocked();
+ playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ animatePlayButtons(false, 100);
+ getRootView().setKeepScreenOn(false);
+
+ service.resetNotification();
+ service.updateNotification(R.drawable.exo_controls_play);
+ }
+
+ @Override
+ public void onBuffering() {
+ super.onBuffering();
+ getRootView().setKeepScreenOn(true);
+
+ service.resetNotification();
+ service.updateNotification(R.drawable.exo_controls_play);
+ }
+
+ @Override
+ public void onPlaying() {
+ super.onPlaying();
+ animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
+ playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
+ animatePlayButtons(true, 200);
+ if (!queueVisible) {
+ playPauseButton.requestFocus();
+ }
+ });
+
+ updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
+ checkLandscape();
+ getRootView().setKeepScreenOn(true);
+
+ service.resetNotification();
+ service.updateNotification(R.drawable.exo_controls_pause);
+
+ service.startForeground(NOTIFICATION_ID, service.getNotBuilder().build());
+ }
+
+ @Override
+ public void onPaused() {
+ super.onPaused();
+ animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
+ playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
+ animatePlayButtons(true, 200);
+ if (!queueVisible) {
+ playPauseButton.requestFocus();
+ }
+ });
+
+ updateWindowFlags(IDLE_WINDOW_FLAGS);
+
+ service.resetNotification();
+ service.updateNotification(R.drawable.exo_controls_play);
+
+ // Remove running notification when user don't want music (or video in popup)
+ // to be played in background
+ if (!minimizeOnPopupEnabled() && !backgroundPlaybackEnabled() && videoPlayerSelected()) {
+ service.stopForeground(true);
+ }
+
+ getRootView().setKeepScreenOn(false);
+ }
+
+ @Override
+ public void onPausedSeek() {
+ super.onPausedSeek();
+ animatePlayButtons(false, 100);
+ getRootView().setKeepScreenOn(true);
+
+ service.resetNotification();
+ service.updateNotification(R.drawable.exo_controls_play);
+ }
+
+
+ @Override
+ public void onCompleted() {
+ animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
+ playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
+ animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
+ });
+ getRootView().setKeepScreenOn(false);
+
+ updateWindowFlags(IDLE_WINDOW_FLAGS);
+
+ service.resetNotification();
+ service.updateNotification(R.drawable.ic_replay_white_24dp);
+
+ super.onCompleted();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+
+ service.getContentResolver().unregisterContentObserver(settingsContentObserver);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Broadcast Receiver
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ protected void setupBroadcastReceiver(final IntentFilter intentFilter) {
+ super.setupBroadcastReceiver(intentFilter);
+ if (DEBUG) {
+ Log.d(TAG, "setupBroadcastReceiver() called with: "
+ + "intentFilter = [" + intentFilter + "]");
+ }
+
+ intentFilter.addAction(ACTION_CLOSE);
+ intentFilter.addAction(ACTION_PLAY_PAUSE);
+ intentFilter.addAction(ACTION_OPEN_CONTROLS);
+ intentFilter.addAction(ACTION_REPEAT);
+ intentFilter.addAction(ACTION_PLAY_PREVIOUS);
+ intentFilter.addAction(ACTION_PLAY_NEXT);
+ intentFilter.addAction(ACTION_FAST_REWIND);
+ intentFilter.addAction(ACTION_FAST_FORWARD);
+
+ intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED);
+ intentFilter.addAction(VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED);
+
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+ intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
+ }
+
+ @Override
+ public void onBroadcastReceived(final Intent intent) {
+ super.onBroadcastReceived(intent);
+ if (intent == null || intent.getAction() == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
+ }
+
+ switch (intent.getAction()) {
+ case ACTION_CLOSE:
+ service.onDestroy();
+ break;
+ case ACTION_PLAY_NEXT:
+ onPlayNext();
+ break;
+ case ACTION_PLAY_PREVIOUS:
+ onPlayPrevious();
+ break;
+ case ACTION_FAST_FORWARD:
+ onFastForward();
+ break;
+ case ACTION_FAST_REWIND:
+ onFastRewind();
+ break;
+ case ACTION_PLAY_PAUSE:
+ onPlayPause();
+ if (!fragmentIsVisible) {
+ // Ensure that we have audio-only stream playing when a user
+ // started to play from notification's play button from outside of the app
+ onFragmentStopped();
+ }
+ break;
+ case ACTION_REPEAT:
+ onRepeatClicked();
+ break;
+ case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED:
+ fragmentIsVisible = true;
+ useVideoSource(true);
+ break;
+ case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED:
+ fragmentIsVisible = false;
+ onFragmentStopped();
+ break;
+ case Intent.ACTION_CONFIGURATION_CHANGED:
+ assureCorrectAppLanguage(service);
+ if (DEBUG) {
+ Log.d(TAG, "onConfigurationChanged() called");
+ }
+ if (popupPlayerSelected()) {
+ updateScreenSize();
+ updatePopupSize(getPopupLayoutParams().width, -1);
+ checkPopupPositionBounds();
+ }
+
+ // The only situation I need to re-calculate elements sizes is
+ // when a user rotates a device from landscape to landscape
+ // because in that case the controls should be aligned to another side of a screen.
+ // The problem is when user leaves the app and returns back
+ // (while the app in landscape) Android reports via DisplayMetrics that orientation
+ // is portrait and it gives wrong sizes calculations.
+ // Let's skip re-calculation in every case but landscape
+ final boolean reportedOrientationIsLandscape = service.isLandscape();
+ final boolean actualOrientationIsLandscape = context.getResources()
+ .getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ if (reportedOrientationIsLandscape && actualOrientationIsLandscape) {
+ setControlsSize();
+ }
+ // Close it because when changing orientation from portrait
+ // (in fullscreen mode) the size of queue layout can be larger than the screen size
+ onQueueClosed();
+ break;
+ case Intent.ACTION_SCREEN_ON:
+ shouldUpdateOnProgress = true;
+ // Interrupt playback only when screen turns on
+ // and user is watching video in popup player.
+ // Same actions for video player will be handled in ACTION_VIDEO_FRAGMENT_RESUMED
+ if (backgroundPlaybackEnabled()
+ && popupPlayerSelected()
+ && (isPlaying() || isLoading())) {
+ useVideoSource(true);
+ }
+ break;
+ case Intent.ACTION_SCREEN_OFF:
+ shouldUpdateOnProgress = false;
+ // Interrupt playback only when screen turns off with popup player working
+ if (backgroundPlaybackEnabled()
+ && popupPlayerSelected()
+ && (isPlaying() || isLoading())) {
+ useVideoSource(false);
+ }
+ break;
+ }
+ service.resetNotification();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Thumbnail Loading
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onLoadingComplete(final String imageUri,
+ final View view,
+ final Bitmap loadedImage) {
+ super.onLoadingComplete(imageUri, view, loadedImage);
+ // rebuild notification here since remote view does not release bitmaps,
+ // causing memory leaks
+ service.resetNotification();
+ service.updateNotification(-1);
+ }
+
+ @Override
+ public void onLoadingFailed(final String imageUri,
+ final View view,
+ final FailReason failReason) {
+ super.onLoadingFailed(imageUri, view, failReason);
+ service.resetNotification();
+ service.updateNotification(-1);
+ }
+
+ @Override
+ public void onLoadingCancelled(final String imageUri, final View view) {
+ super.onLoadingCancelled(imageUri, view);
+ service.resetNotification();
+ service.updateNotification(-1);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void setInitialGestureValues() {
+ if (getAudioReactor() != null) {
+ final float currentVolumeNormalized = (float) getAudioReactor()
+ .getVolume() / getAudioReactor().getMaxVolume();
+ volumeProgressBar.setProgress(
+ (int) (volumeProgressBar.getMax() * currentVolumeNormalized));
+ }
+ }
+
+ private void choosePlayerTypeFromIntent(final Intent intent) {
+ // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra
+ if (intent.getIntExtra(PLAYER_TYPE, PLAYER_TYPE_VIDEO) == PLAYER_TYPE_AUDIO) {
+ playerType = MainPlayer.PlayerType.AUDIO;
+ } else if (intent.getIntExtra(PLAYER_TYPE, PLAYER_TYPE_VIDEO) == PLAYER_TYPE_POPUP) {
+ playerType = MainPlayer.PlayerType.POPUP;
+ } else {
+ playerType = MainPlayer.PlayerType.VIDEO;
+ }
+ }
+
+ public boolean backgroundPlaybackEnabled() {
+ return PlayerHelper.getMinimizeOnExitAction(service) == MINIMIZE_ON_EXIT_MODE_BACKGROUND;
+ }
+
+ public boolean minimizeOnPopupEnabled() {
+ return PlayerHelper.getMinimizeOnExitAction(service)
+ == PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
+ }
+
+ public boolean audioPlayerSelected() {
+ return playerType == MainPlayer.PlayerType.AUDIO;
+ }
+
+ public boolean videoPlayerSelected() {
+ return playerType == MainPlayer.PlayerType.VIDEO;
+ }
+
+ public boolean popupPlayerSelected() {
+ return playerType == MainPlayer.PlayerType.POPUP;
+ }
+
+ public boolean isPlayerStopped() {
+ return getPlayer() == null || getPlayer().getPlaybackState() == SimpleExoPlayer.STATE_IDLE;
+ }
+
+ private int distanceFromCloseButton(final MotionEvent popupMotionEvent) {
+ final int closeOverlayButtonX = closeOverlayButton.getLeft()
+ + closeOverlayButton.getWidth() / 2;
+ final int closeOverlayButtonY = closeOverlayButton.getTop()
+ + closeOverlayButton.getHeight() / 2;
+
+ final float fingerX = popupLayoutParams.x + popupMotionEvent.getX();
+ final float fingerY = popupLayoutParams.y + popupMotionEvent.getY();
+
+ return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2)
+ + Math.pow(closeOverlayButtonY - fingerY, 2));
+ }
+
+ private float getClosingRadius() {
+ final int buttonRadius = closeOverlayButton.getWidth() / 2;
+ // 20% wider than the button itself
+ return buttonRadius * 1.2f;
+ }
+
+ public boolean isInsideClosingRadius(final MotionEvent popupMotionEvent) {
+ return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
+ }
+
+ public boolean isFullscreen() {
+ return isFullscreen;
+ }
+
+ public void showControlsThenHide() {
+ if (DEBUG) {
+ Log.d(TAG, "showControlsThenHide() called");
+ }
+ showOrHideButtons();
+ showSystemUIPartially();
+ super.showControlsThenHide();
+ }
+
+ @Override
+ public void showControls(final long duration) {
+ if (DEBUG) {
+ Log.d(TAG, "showControls() called with: duration = [" + duration + "]");
+ }
+ showOrHideButtons();
+ showSystemUIPartially();
+ super.showControls(duration);
+ }
+
+ @Override
+ public void hideControls(final long duration, final long delay) {
+ if (DEBUG) {
+ Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
+ }
+
+ showOrHideButtons();
+
+ getControlsVisibilityHandler().removeCallbacksAndMessages(null);
+ getControlsVisibilityHandler().postDelayed(() ->
+ animateView(getControlsRoot(), false, duration, 0,
+ this::hideSystemUIIfNeeded), delay
+ );
+ }
+
+ @Override
+ public void safeHideControls(final long duration, final long delay) {
+ if (getControlsRoot().isInTouchMode()) {
+ hideControls(duration, delay);
+ }
+ }
+
+ private void showOrHideButtons() {
+ if (playQueue == null) {
+ return;
+ }
+
+ playPreviousButton.setVisibility(playQueue.getIndex() == 0
+ ? View.INVISIBLE
+ : View.VISIBLE);
+ playNextButton.setVisibility(playQueue.getIndex() + 1 == playQueue.getStreams().size()
+ ? View.INVISIBLE
+ : View.VISIBLE);
+ queueButton.setVisibility(playQueue.getStreams().size() <= 1 || popupPlayerSelected()
+ ? View.GONE
+ : View.VISIBLE);
+ }
+
+ private void showSystemUIPartially() {
+ if (isFullscreen() && getParentActivity() != null) {
+ final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ getParentActivity().getWindow().getDecorView().setSystemUiVisibility(visibility);
+ }
+ }
+
+ @Override
+ public void hideSystemUIIfNeeded() {
+ if (fragmentListener != null) {
+ fragmentListener.hideSystemUiIfNeeded();
+ }
+ }
+
+ /**
+ * Measures width and height of controls visible on screen.
+ * It ensures that controls will be side-by-side with NavigationBar and notches
+ * but not under them. Tablets have only bottom NavigationBar
+ */
+ public void setControlsSize() {
+ final Point size = new Point();
+ final Display display = getRootView().getDisplay();
+ if (display == null || !videoPlayerSelected()) {
+ return;
+ }
+ // This method will give a correct size of a usable area of a window.
+ // It doesn't include NavigationBar, notches, etc.
+ display.getSize(size);
+
+ final int width = isFullscreen
+ ? (service.isLandscape()
+ ? size.x : size.y) : ViewGroup.LayoutParams.MATCH_PARENT;
+ final int gravity = isFullscreen
+ ? (display.getRotation() == Surface.ROTATION_90
+ ? Gravity.START : Gravity.END)
+ : Gravity.TOP;
+
+ getTopControlsRoot().getLayoutParams().width = width;
+ final RelativeLayout.LayoutParams topParams =
+ ((RelativeLayout.LayoutParams) getTopControlsRoot().getLayoutParams());
+ topParams.removeRule(RelativeLayout.ALIGN_PARENT_START);
+ topParams.removeRule(RelativeLayout.ALIGN_PARENT_END);
+ topParams.addRule(gravity == Gravity.END
+ ? RelativeLayout.ALIGN_PARENT_END
+ : RelativeLayout.ALIGN_PARENT_START);
+ getTopControlsRoot().requestLayout();
+
+ getBottomControlsRoot().getLayoutParams().width = width;
+ final RelativeLayout.LayoutParams bottomParams =
+ ((RelativeLayout.LayoutParams) getBottomControlsRoot().getLayoutParams());
+ bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_START);
+ bottomParams.removeRule(RelativeLayout.ALIGN_PARENT_END);
+ bottomParams.addRule(gravity == Gravity.END
+ ? RelativeLayout.ALIGN_PARENT_END
+ : RelativeLayout.ALIGN_PARENT_START);
+ getBottomControlsRoot().requestLayout();
+
+ final ViewGroup controlsRoot = getRootView().findViewById(R.id.playbackWindowRoot);
+ // In tablet navigationBar located at the bottom of the screen.
+ // And the situations when we need to set custom height is
+ // in fullscreen mode in tablet in non-multiWindow mode or with vertical video.
+ // Other than that MATCH_PARENT is good
+ final boolean navBarAtTheBottom = DeviceUtils.isTablet(service) || !service.isLandscape();
+ controlsRoot.getLayoutParams().height = isFullscreen && !isInMultiWindow()
+ && navBarAtTheBottom ? size.y : ViewGroup.LayoutParams.MATCH_PARENT;
+ controlsRoot.requestLayout();
+
+ final int topPadding = isFullscreen && !isInMultiWindow() ? getStatusBarHeight() : 0;
+ getRootView().findViewById(R.id.playbackWindowRoot).setPadding(0, topPadding, 0, 0);
+ getRootView().findViewById(R.id.playbackWindowRoot).requestLayout();
+ }
+
+ /**
+ * @return statusBar height that was found inside system resources
+ * or default value if no value was provided inside resources
+ */
+ private int getStatusBarHeight() {
+ int statusBarHeight = 0;
+ final int resourceId = service.getResources().getIdentifier(
+ "status_bar_height_landscape", "dimen", "android");
+ if (resourceId > 0) {
+ statusBarHeight = service.getResources().getDimensionPixelSize(resourceId);
+ }
+ if (statusBarHeight == 0) {
+ // Some devices provide wrong value for status bar height in landscape mode,
+ // this is workaround
+ final DisplayMetrics metrics = getRootView().getResources().getDisplayMetrics();
+ statusBarHeight = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, 24, metrics);
+ }
+ return statusBarHeight;
+ }
+
+ protected void setMuteButton(final ImageButton button, final boolean isMuted) {
+ button.setImageDrawable(AppCompatResources.getDrawable(service, isMuted
+ ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
+ }
+
+ /**
+ * @return true if main player is attached to activity and activity inside multiWindow mode
+ */
+ private boolean isInMultiWindow() {
+ final AppCompatActivity parent = getParentActivity();
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ && parent != null
+ && parent.isInMultiWindowMode();
+ }
+
+ private void updatePlaybackButtons() {
+ if (repeatButton == null
+ || shuffleButton == null
+ || simpleExoPlayer == null
+ || playQueue == null) {
+ return;
+ }
+
+ setRepeatModeButton(repeatButton, getRepeatMode());
+ setShuffleButton(shuffleButton, playQueue.isShuffled());
+ }
+
+ public void checkLandscape() {
+ final AppCompatActivity parent = getParentActivity();
+ final boolean videoInLandscapeButNotInFullscreen = service.isLandscape()
+ && !isFullscreen()
+ && videoPlayerSelected()
+ && !audioOnly;
+
+ final boolean playingState = getCurrentState() != STATE_COMPLETED
+ && getCurrentState() != STATE_PAUSED;
+ if (parent != null
+ && videoInLandscapeButNotInFullscreen
+ && playingState
+ && !DeviceUtils.isTablet(service)) {
+ toggleFullscreen();
+ }
+
+ setControlsSize();
+ }
+
+ private void buildQueue() {
+ itemsList.setAdapter(playQueueAdapter);
+ itemsList.setClickable(true);
+ itemsList.setLongClickable(true);
+
+ itemsList.clearOnScrollListeners();
+ itemsList.addOnScrollListener(getQueueScrollListener());
+
+ itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
+ itemTouchHelper.attachToRecyclerView(itemsList);
+
+ playQueueAdapter.setSelectedListener(getOnSelectedListener());
+
+ itemsListCloseButton.setOnClickListener(view -> onQueueClosed());
+ }
+
+ public void useVideoSource(final boolean video) {
+ if (playQueue == null || audioOnly == !video || audioPlayerSelected()) {
+ return;
+ }
+
+ audioOnly = !video;
+ // When a user returns from background controls could be hidden
+ // but systemUI will be shown 100%. Hide it
+ if (!audioOnly && !isControlsVisible()) {
+ hideSystemUIIfNeeded();
+ }
+ setRecovery();
+ reload();
+ }
+
+ private OnScrollBelowItemsListener getQueueScrollListener() {
+ return new OnScrollBelowItemsListener() {
+ @Override
+ public void onScrolledDown(final RecyclerView recyclerView) {
+ if (playQueue != null && !playQueue.isComplete()) {
+ playQueue.fetch();
+ } else if (itemsList != null) {
+ itemsList.clearOnScrollListeners();
+ }
+ }
+ };
+ }
+
+ private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
+ return new PlayQueueItemTouchCallback() {
+ @Override
+ public void onMove(final int sourceIndex, final int targetIndex) {
+ if (playQueue != null) {
+ playQueue.move(sourceIndex, targetIndex);
+ }
+ }
+
+ @Override
+ public void onSwiped(final int index) {
+ if (index != -1) {
+ playQueue.remove(index);
+ }
+ }
+ };
+ }
+
+ private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() {
+ return new PlayQueueItemBuilder.OnSelectedListener() {
+ @Override
+ public void selected(final PlayQueueItem item, final View view) {
+ onSelected(item);
+ }
+
+ @Override
+ public void held(final PlayQueueItem item, final View view) {
+ final int index = playQueue.indexOf(item);
+ if (index != -1) {
+ playQueue.remove(index);
+ }
+ }
+
+ @Override
+ public void onStartDrag(final PlayQueueItemHolder viewHolder) {
+ if (itemTouchHelper != null) {
+ itemTouchHelper.startDrag(viewHolder);
+ }
+ }
+ };
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @SuppressLint("RtlHardcoded")
+ private void initPopup() {
+ if (DEBUG) {
+ Log.d(TAG, "initPopup() called");
+ }
+
+ // Popup is already added to windowManager
+ if (popupHasParent()) {
+ return;
+ }
+
+ updateScreenSize();
+
+ final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(service);
+ final float defaultSize = service.getResources().getDimension(R.dimen.popup_default_width);
+ final SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(service);
+ popupWidth = popupRememberSizeAndPos
+ ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize)
+ : defaultSize;
+ popupHeight = getMinimumVideoHeight(popupWidth);
+
+ popupLayoutParams = new WindowManager.LayoutParams(
+ (int) popupWidth, (int) popupHeight,
+ popupLayoutParamType(),
+ IDLE_WINDOW_FLAGS,
+ PixelFormat.TRANSLUCENT);
+ popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+ popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ getSurfaceView().setHeights((int) popupHeight, (int) popupHeight);
+
+ final int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
+ final int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
+ popupLayoutParams.x = popupRememberSizeAndPos
+ ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
+ popupLayoutParams.y = popupRememberSizeAndPos
+ ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
+
+ checkPopupPositionBounds();
+
+ getLoadingPanel().setMinimumWidth(popupLayoutParams.width);
+ getLoadingPanel().setMinimumHeight(popupLayoutParams.height);
+
+ service.removeViewFromParent();
+ windowManager.addView(getRootView(), popupLayoutParams);
+
+ // Popup doesn't have aspectRatio selector, using FIT automatically
+ setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private void initPopupCloseOverlay() {
+ if (DEBUG) {
+ Log.d(TAG, "initPopupCloseOverlay() called");
+ }
+
+ // closeOverlayView is already added to windowManager
+ if (closeOverlayView != null) {
+ return;
+ }
+
+ closeOverlayView = View.inflate(service, R.layout.player_popup_close_overlay, null);
+ closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton);
+
+ final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+
+ final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+ popupLayoutParamType(),
+ flags,
+ PixelFormat.TRANSLUCENT);
+ closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+ closeOverlayLayoutParams.softInputMode =
+ WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+
+ closeOverlayButton.setVisibility(View.GONE);
+ windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
+ }
+
+ private void initVideoPlayer() {
+ restoreResizeMode();
+ getRootView().setLayoutParams(new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Popup utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ /**
+ * @return if the popup was out of bounds and have been moved back to it
+ * @see #checkPopupPositionBounds(float, float)
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public boolean checkPopupPositionBounds() {
+ return checkPopupPositionBounds(screenWidth, screenHeight);
+ }
+
+ /**
+ * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary
+ * that goes from (0, 0) to (boundaryWidth, boundaryHeight).
+ *
+ * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed
+ * and {@code true} is returned to represent this change.
+ *
+ *
+ * @param boundaryWidth width of the boundary
+ * @param boundaryHeight height of the boundary
+ * @return if the popup was out of bounds and have been moved back to it
+ */
+ public boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "checkPopupPositionBounds() called with: "
+ + "boundaryWidth = [" + boundaryWidth + "], "
+ + "boundaryHeight = [" + boundaryHeight + "]");
+ }
+
+ if (popupLayoutParams.x < 0) {
+ popupLayoutParams.x = 0;
+ return true;
+ } else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) {
+ popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width);
+ return true;
+ }
+
+ if (popupLayoutParams.y < 0) {
+ popupLayoutParams.y = 0;
+ return true;
+ } else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) {
+ popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void savePositionAndSize() {
+ final SharedPreferences sharedPreferences = PreferenceManager
+ .getDefaultSharedPreferences(service);
+ sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply();
+ sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply();
+ sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply();
+ }
+
+ private float getMinimumVideoHeight(final float width) {
+ final float height = width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
+ /*if (DEBUG) {
+ Log.d(TAG, "getMinimumVideoHeight() called with: width = ["
+ + width + "], returned: " + height);
+ }*/
+ return height;
+ }
+
+ public void updateScreenSize() {
+ final 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);
+ }
+
+ popupWidth = service.getResources().getDimension(R.dimen.popup_default_width);
+ popupHeight = getMinimumVideoHeight(popupWidth);
+
+ minimumWidth = service.getResources().getDimension(R.dimen.popup_minimum_width);
+ minimumHeight = getMinimumVideoHeight(minimumWidth);
+
+ maximumWidth = screenWidth;
+ maximumHeight = screenHeight;
+ }
+
+ public void updatePopupSize(final int width, final int height) {
+ if (DEBUG) {
+ Log.d(TAG, "updatePopupSize() called with: width = ["
+ + width + "], height = [" + height + "]");
+ }
+
+ if (popupLayoutParams == null
+ || windowManager == null
+ || getParentActivity() != null
+ || getRootView().getParent() == null) {
+ return;
+ }
+
+ final int actualWidth = (int) (width > maximumWidth
+ ? maximumWidth : width < minimumWidth ? minimumWidth : width);
+ final int actualHeight;
+ if (height == -1) {
+ actualHeight = (int) getMinimumVideoHeight(width);
+ } else {
+ actualHeight = (int) (height > maximumHeight
+ ? maximumHeight : height < minimumHeight
+ ? minimumHeight : height);
+ }
+
+ popupLayoutParams.width = actualWidth;
+ popupLayoutParams.height = actualHeight;
+ popupWidth = actualWidth;
+ popupHeight = actualHeight;
+ getSurfaceView().setHeights((int) popupHeight, (int) popupHeight);
+
+ if (DEBUG) {
+ Log.d(TAG, "updatePopupSize() updated values:"
+ + " width = [" + actualWidth + "], height = [" + actualHeight + "]");
+ }
+ windowManager.updateViewLayout(getRootView(), popupLayoutParams);
+ }
+
+ private void updateWindowFlags(final int flags) {
+ if (popupLayoutParams == null
+ || windowManager == null
+ || getParentActivity() != null
+ || getRootView().getParent() == null) {
+ return;
+ }
+
+ popupLayoutParams.flags = flags;
+ windowManager.updateViewLayout(getRootView(), popupLayoutParams);
+ }
+
+ private int popupLayoutParamType() {
+ return Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O
+ ? WindowManager.LayoutParams.TYPE_PHONE
+ : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Misc
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public void closePopup() {
+ if (DEBUG) {
+ Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
+ }
+ if (isPopupClosing) {
+ return;
+ }
+ isPopupClosing = true;
+
+ savePlaybackState();
+ windowManager.removeView(getRootView());
+
+ animateOverlayAndFinishService();
+ }
+
+ public void removePopupFromView() {
+ final boolean isCloseOverlayHasParent = closeOverlayView != null
+ && closeOverlayView.getParent() != null;
+ if (popupHasParent()) {
+ windowManager.removeView(getRootView());
+ }
+ if (isCloseOverlayHasParent) {
+ windowManager.removeView(closeOverlayView);
+ }
+ }
+
+ private void animateOverlayAndFinishService() {
+ final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight()
+ - closeOverlayButton.getY());
+
+ closeOverlayButton.animate().setListener(null).cancel();
+ closeOverlayButton.animate()
+ .setInterpolator(new AnticipateInterpolator())
+ .translationY(targetTranslationY)
+ .setDuration(400)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(final Animator animation) {
+ end();
+ }
+
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ end();
+ }
+
+ private void end() {
+ windowManager.removeView(closeOverlayView);
+ closeOverlayView = null;
+
+ service.onDestroy();
+ }
+ }).start();
+ }
+
+ private boolean popupHasParent() {
+ final View root = getRootView();
+ return root != null
+ && root.getLayoutParams() instanceof WindowManager.LayoutParams
+ && root.getParent() != null;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Manipulations with listener
+ ///////////////////////////////////////////////////////////////////////////
+
+ public void setFragmentListener(final PlayerServiceEventListener listener) {
+ fragmentListener = listener;
+ fragmentIsVisible = true;
+ updateMetadata();
+ updatePlayback();
+ triggerProgressUpdate();
+ }
+
+ public void removeFragmentListener(final PlayerServiceEventListener listener) {
+ if (fragmentListener == listener) {
+ fragmentListener = null;
+ }
+ }
+
+ void setActivityListener(final PlayerEventListener listener) {
+ activityListener = listener;
+ updateMetadata();
+ updatePlayback();
+ triggerProgressUpdate();
+ }
+
+ void removeActivityListener(final PlayerEventListener listener) {
+ if (activityListener == listener) {
+ activityListener = null;
+ }
+ }
+
+ private void updateQueue() {
+ if (fragmentListener != null && playQueue != null) {
+ fragmentListener.onQueueUpdate(playQueue);
+ }
+ if (activityListener != null && playQueue != null) {
+ activityListener.onQueueUpdate(playQueue);
+ }
+ }
+
+ private void updateMetadata() {
+ if (fragmentListener != null && getCurrentMetadata() != null) {
+ fragmentListener.onMetadataUpdate(getCurrentMetadata().getMetadata(), playQueue);
+ }
+ if (activityListener != null && getCurrentMetadata() != null) {
+ activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata(), playQueue);
+ }
+ }
+
+ private void updatePlayback() {
+ if (fragmentListener != null && simpleExoPlayer != null && playQueue != null) {
+ fragmentListener.onPlaybackUpdate(currentState, getRepeatMode(),
+ playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
+ }
+ if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
+ activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
+ playQueue.isShuffled(), getPlaybackParameters());
+ }
+ }
+
+ private void updateProgress(final int currentProgress, final int duration,
+ final int bufferPercent) {
+ if (fragmentListener != null) {
+ fragmentListener.onProgressUpdate(currentProgress, duration, bufferPercent);
+ }
+ if (activityListener != null) {
+ activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
+ }
+ }
+
+ void stopActivityBinding() {
+ if (fragmentListener != null) {
+ fragmentListener.onServiceStopped();
+ fragmentListener = null;
+ }
+ if (activityListener != null) {
+ activityListener.onServiceStopped();
+ activityListener = null;
+ }
+ }
+
+ /**
+ * This will be called when a user goes to another app/activity, turns off a screen.
+ * We don't want to interrupt playback and don't want to see notification so
+ * next lines of code will enable audio-only playback only if needed
+ * */
+ private void onFragmentStopped() {
+ if (videoPlayerSelected() && (isPlaying() || isLoading())) {
+ if (backgroundPlaybackEnabled()) {
+ useVideoSource(false);
+ } else if (minimizeOnPopupEnabled()) {
+ setRecovery();
+ NavigationHelper.playOnPopupPlayer(getParentActivity(), playQueue, true);
+ } else {
+ onPause();
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Getters
+ ///////////////////////////////////////////////////////////////////////////
+
+ public RelativeLayout getVolumeRelativeLayout() {
+ return volumeRelativeLayout;
+ }
+
+ public ProgressBar getVolumeProgressBar() {
+ return volumeProgressBar;
+ }
+
+ public ImageView getVolumeImageView() {
+ return volumeImageView;
+ }
+
+ public RelativeLayout getBrightnessRelativeLayout() {
+ return brightnessRelativeLayout;
+ }
+
+ public ProgressBar getBrightnessProgressBar() {
+ return brightnessProgressBar;
+ }
+
+ public ImageView getBrightnessImageView() {
+ return brightnessImageView;
+ }
+
+ public ImageButton getPlayPauseButton() {
+ return playPauseButton;
+ }
+
+ public int getMaxGestureLength() {
+ return maxGestureLength;
+ }
+
+ public TextView getResizingIndicator() {
+ return resizingIndicator;
+ }
+
+ public GestureDetector getGestureDetector() {
+ return gestureDetector;
+ }
+
+ public WindowManager.LayoutParams getPopupLayoutParams() {
+ return popupLayoutParams;
+ }
+
+ public float getScreenWidth() {
+ return screenWidth;
+ }
+
+ public float getScreenHeight() {
+ return screenHeight;
+ }
+
+ public float getPopupWidth() {
+ return popupWidth;
+ }
+
+ public float getPopupHeight() {
+ return popupHeight;
+ }
+
+ public void setPopupWidth(final float width) {
+ popupWidth = width;
+ }
+
+ public void setPopupHeight(final float height) {
+ popupHeight = height;
+ }
+
+ public View getCloseOverlayButton() {
+ return closeOverlayButton;
+ }
+
+ public View getClosingOverlayView() {
+ return closingOverlayView;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
new file mode 100644
index 00000000000..1d0b3ae264a
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java
@@ -0,0 +1,64 @@
+package org.schabi.newpipe.player.event;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import org.schabi.newpipe.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CustomBottomSheetBehavior extends BottomSheetBehavior {
+
+ public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ boolean visible;
+ Rect globalRect = new Rect();
+ private boolean skippingInterception = false;
+ private final List skipInterceptionOfElements = Arrays.asList(
+ R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
+ R.id.playQueuePanel, R.id.viewpager);
+
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent,
+ @NonNull final FrameLayout child,
+ final MotionEvent event) {
+ // Drop following when action ends
+ if (event.getAction() == MotionEvent.ACTION_CANCEL
+ || event.getAction() == MotionEvent.ACTION_UP) {
+ skippingInterception = false;
+ }
+
+ // Found that user still swiping, continue following
+ if (skippingInterception) {
+ return false;
+ }
+
+ // Don't need to do anything if bottomSheet isn't expanded
+ if (getState() == BottomSheetBehavior.STATE_EXPANDED) {
+ // Without overriding scrolling will not work when user touches these elements
+ for (final Integer element : skipInterceptionOfElements) {
+ final ViewGroup viewGroup = child.findViewById(element);
+ if (viewGroup != null) {
+ visible = viewGroup.getGlobalVisibleRect(globalRect);
+ if (visible
+ && globalRect.contains((int) event.getRawX(), (int) event.getRawY())) {
+ skippingInterception = true;
+ return false;
+ }
+ }
+ }
+ }
+
+ return super.onInterceptTouchEvent(parent, child, event);
+ }
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/OnKeyDownListener.java b/app/src/main/java/org/schabi/newpipe/player/event/OnKeyDownListener.java
new file mode 100644
index 00000000000..fc1f9d80ddf
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/event/OnKeyDownListener.java
@@ -0,0 +1,5 @@
+package org.schabi.newpipe.player.event;
+
+public interface OnKeyDownListener {
+ boolean onKeyDown(int keyCode);
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java
index 0809fa0f5b1..b5520e8bee7 100644
--- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java
@@ -4,14 +4,13 @@
import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.player.playqueue.PlayQueue;
public interface PlayerEventListener {
+ void onQueueUpdate(PlayQueue queue);
void onPlaybackUpdate(int state, int repeatMode, boolean shuffled,
PlaybackParameters parameters);
-
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
-
- void onMetadataUpdate(StreamInfo info);
-
+ void onMetadataUpdate(StreamInfo info, PlayQueue queue);
void onServiceStopped();
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
new file mode 100644
index 00000000000..5ee8485cf40
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java
@@ -0,0 +1,622 @@
+package org.schabi.newpipe.player.event;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.WindowManager;
+import androidx.appcompat.content.res.AppCompatResources;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.player.BasePlayer;
+import org.schabi.newpipe.player.MainPlayer;
+import org.schabi.newpipe.player.VideoPlayerImpl;
+import org.schabi.newpipe.player.helper.PlayerHelper;
+
+import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
+import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
+import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
+import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
+import static org.schabi.newpipe.util.AnimationUtils.animateView;
+
+public class PlayerGestureListener
+ extends GestureDetector.SimpleOnGestureListener
+ implements View.OnTouchListener {
+ private static final String TAG = ".PlayerGestureListener";
+ private static final boolean DEBUG = BasePlayer.DEBUG;
+
+ private final VideoPlayerImpl playerImpl;
+ private final MainPlayer service;
+
+ private int initialPopupX;
+ private int initialPopupY;
+
+ private boolean isMovingInMain;
+ private boolean isMovingInPopup;
+
+ private boolean isResizing;
+
+ private final int tossFlingVelocity;
+
+ private final boolean isVolumeGestureEnabled;
+ private final boolean isBrightnessGestureEnabled;
+ private final int maxVolume;
+ private static final int MOVEMENT_THRESHOLD = 40;
+
+ // [popup] initial coordinates and distance between fingers
+ private double initPointerDistance = -1;
+ private float initFirstPointerX = -1;
+ private float initFirstPointerY = -1;
+ private float initSecPointerX = -1;
+ private float initSecPointerY = -1;
+
+
+ public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
+ this.playerImpl = playerImpl;
+ this.service = service;
+ this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service);
+
+ isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
+ isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
+ maxVolume = playerImpl.getAudioReactor().getMaxVolume();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Helpers
+ //////////////////////////////////////////////////////////////////////////*/
+
+ /*
+ * Main and popup players' gesture listeners is too different.
+ * So it will be better to have different implementations of them
+ * */
+ @Override
+ public boolean onDoubleTap(final MotionEvent e) {
+ if (DEBUG) {
+ Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = "
+ + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
+ }
+
+ if (playerImpl.popupPlayerSelected()) {
+ return onDoubleTapInPopup(e);
+ } else {
+ return onDoubleTapInMain(e);
+ }
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(final MotionEvent e) {
+ if (DEBUG) {
+ Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
+ }
+
+ if (playerImpl.popupPlayerSelected()) {
+ return onSingleTapConfirmedInPopup(e);
+ } else {
+ return onSingleTapConfirmedInMain(e);
+ }
+ }
+
+ @Override
+ public boolean onDown(final MotionEvent e) {
+ if (DEBUG) {
+ Log.d(TAG, "onDown() called with: e = [" + e + "]");
+ }
+
+ if (playerImpl.popupPlayerSelected()) {
+ return onDownInPopup(e);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void onLongPress(final MotionEvent e) {
+ if (DEBUG) {
+ Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
+ }
+
+ if (playerImpl.popupPlayerSelected()) {
+ onLongPressInPopup(e);
+ }
+ }
+
+ @Override
+ public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
+ final float distanceX, final float distanceY) {
+ if (playerImpl.popupPlayerSelected()) {
+ return onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY);
+ } else {
+ return onScrollInMain(initialEvent, movingEvent, distanceX, distanceY);
+ }
+ }
+
+ @Override
+ public boolean onFling(final MotionEvent e1, final MotionEvent e2,
+ final float velocityX, final float velocityY) {
+ if (DEBUG) {
+ Log.d(TAG, "onFling() called with velocity: dX=["
+ + velocityX + "], dY=[" + velocityY + "]");
+ }
+
+ if (playerImpl.popupPlayerSelected()) {
+ return onFlingInPopup(e1, e2, velocityX, velocityY);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean onTouch(final View v, final MotionEvent event) {
+ /*if (DEBUG && false) {
+ Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
+ }*/
+
+ if (playerImpl.popupPlayerSelected()) {
+ return onTouchInPopup(v, event);
+ } else {
+ return onTouchInMain(v, event);
+ }
+ }
+
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Main player listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private boolean onDoubleTapInMain(final MotionEvent e) {
+ if (e.getX() > playerImpl.getRootView().getWidth() * 2.0 / 3.0) {
+ playerImpl.onFastForward();
+ } else if (e.getX() < playerImpl.getRootView().getWidth() / 3.0) {
+ playerImpl.onFastRewind();
+ } else {
+ playerImpl.getPlayPauseButton().performClick();
+ }
+
+ return true;
+ }
+
+
+ private boolean onSingleTapConfirmedInMain(final MotionEvent e) {
+ if (DEBUG) {
+ Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
+ }
+
+ if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) {
+ return true;
+ }
+
+ if (playerImpl.isControlsVisible()) {
+ playerImpl.hideControls(150, 0);
+ } else {
+ if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
+ playerImpl.showControls(0);
+ } else {
+ playerImpl.showControlsThenHide();
+ }
+ }
+ return true;
+ }
+
+ private boolean onScrollInMain(final MotionEvent initialEvent, final MotionEvent movingEvent,
+ final float distanceX, final float distanceY) {
+ if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) {
+ return false;
+ }
+
+ final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight(service);
+ final boolean isTouchingNavigationBar = initialEvent.getY()
+ > playerImpl.getRootView().getHeight() - getNavigationBarHeight(service);
+ if (isTouchingStatusBar || isTouchingNavigationBar) {
+ return false;
+ }
+
+ /*if (DEBUG && false) Log.d(TAG, "onScrollInMain = " +
+ ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
+ ", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
+ ", distanceXy = [" + distanceX + ", " + distanceY + "]");*/
+
+ final boolean insideThreshold =
+ Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
+ if (!isMovingInMain && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
+ || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
+ return false;
+ }
+
+ isMovingInMain = true;
+
+ final boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
+ final boolean acceptVolumeArea = acceptAnyArea
+ || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2.0;
+
+ if (isVolumeGestureEnabled && acceptVolumeArea) {
+ playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
+ final float currentProgressPercent = (float) playerImpl
+ .getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
+ final int currentVolume = (int) (maxVolume * currentProgressPercent);
+ playerImpl.getAudioReactor().setVolume(currentVolume);
+
+ if (DEBUG) {
+ Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
+ }
+
+ playerImpl.getVolumeImageView().setImageDrawable(
+ AppCompatResources.getDrawable(service, currentProgressPercent <= 0
+ ? R.drawable.ic_volume_off_white_24dp
+ : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
+ : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
+ : R.drawable.ic_volume_up_white_24dp)
+ );
+
+ if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
+ animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ }
+ if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
+ playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
+ }
+ } else {
+ final Activity parent = playerImpl.getParentActivity();
+ if (parent == null) {
+ return true;
+ }
+
+ final Window window = parent.getWindow();
+
+ playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
+ final float currentProgressPercent = (float) playerImpl.getBrightnessProgressBar()
+ .getProgress() / playerImpl.getMaxGestureLength();
+ final WindowManager.LayoutParams layoutParams = window.getAttributes();
+ layoutParams.screenBrightness = currentProgressPercent;
+ window.setAttributes(layoutParams);
+
+ if (DEBUG) {
+ Log.d(TAG, "onScroll().brightnessControl, "
+ + "currentBrightness = " + currentProgressPercent);
+ }
+
+ playerImpl.getBrightnessImageView().setImageDrawable(
+ AppCompatResources.getDrawable(service,
+ currentProgressPercent < 0.25
+ ? R.drawable.ic_brightness_low_white_24dp
+ : currentProgressPercent < 0.75
+ ? R.drawable.ic_brightness_medium_white_24dp
+ : R.drawable.ic_brightness_high_white_24dp)
+ );
+
+ if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
+ animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
+ }
+ if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
+ playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
+ }
+ }
+ return true;
+ }
+
+ private void onScrollEndInMain() {
+ if (DEBUG) {
+ Log.d(TAG, "onScrollEnd() called");
+ }
+
+ if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
+ animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
+ }
+ if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
+ animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
+ }
+
+ if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
+ playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
+ }
+ }
+
+ private boolean onTouchInMain(final View v, final MotionEvent event) {
+ playerImpl.getGestureDetector().onTouchEvent(event);
+ if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) {
+ isMovingInMain = false;
+ onScrollEndInMain();
+ }
+ // This hack allows to stop receiving touch events on appbar
+ // while touching video player's view
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ v.getParent().requestDisallowInterceptTouchEvent(playerImpl.isFullscreen());
+ return true;
+ case MotionEvent.ACTION_UP:
+ v.getParent().requestDisallowInterceptTouchEvent(false);
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Popup player listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private boolean onDoubleTapInPopup(final MotionEvent e) {
+ if (playerImpl == null || !playerImpl.isPlaying()) {
+ return false;
+ }
+
+ playerImpl.hideControls(0, 0);
+
+ if (e.getX() > playerImpl.getPopupWidth() / 2) {
+ playerImpl.onFastForward();
+ } else {
+ playerImpl.onFastRewind();
+ }
+
+ return true;
+ }
+
+ private boolean onSingleTapConfirmedInPopup(final MotionEvent e) {
+ if (playerImpl == null || playerImpl.getPlayer() == null) {
+ return false;
+ }
+ if (playerImpl.isControlsVisible()) {
+ playerImpl.hideControls(100, 100);
+ } else {
+ playerImpl.getPlayPauseButton().requestFocus();
+ playerImpl.showControlsThenHide();
+ }
+ return true;
+ }
+
+ private boolean onDownInPopup(final MotionEvent e) {
+ // Fix popup position when the user touch it, it may have the wrong one
+ // because the soft input is visible (the draggable area is currently resized).
+ playerImpl.updateScreenSize();
+ playerImpl.checkPopupPositionBounds();
+
+ initialPopupX = playerImpl.getPopupLayoutParams().x;
+ initialPopupY = playerImpl.getPopupLayoutParams().y;
+ playerImpl.setPopupWidth(playerImpl.getPopupLayoutParams().width);
+ playerImpl.setPopupHeight(playerImpl.getPopupLayoutParams().height);
+ return super.onDown(e);
+ }
+
+ private void onLongPressInPopup(final MotionEvent e) {
+ playerImpl.updateScreenSize();
+ playerImpl.checkPopupPositionBounds();
+ playerImpl.updatePopupSize((int) playerImpl.getScreenWidth(), -1);
+ }
+
+ private boolean onScrollInPopup(final MotionEvent initialEvent,
+ final MotionEvent movingEvent,
+ final float distanceX,
+ final float distanceY) {
+ if (isResizing || playerImpl == null) {
+ return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
+ }
+
+ if (!isMovingInPopup) {
+ animateView(playerImpl.getCloseOverlayButton(), true, 200);
+ }
+
+ isMovingInPopup = true;
+
+ final float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
+ float posX = (int) (initialPopupX + diffX);
+ final float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
+ float posY = (int) (initialPopupY + diffY);
+
+ if (posX > (playerImpl.getScreenWidth() - playerImpl.getPopupWidth())) {
+ posX = (int) (playerImpl.getScreenWidth() - playerImpl.getPopupWidth());
+ } else if (posX < 0) {
+ posX = 0;
+ }
+
+ if (posY > (playerImpl.getScreenHeight() - playerImpl.getPopupHeight())) {
+ posY = (int) (playerImpl.getScreenHeight() - playerImpl.getPopupHeight());
+ } else if (posY < 0) {
+ posY = 0;
+ }
+
+ playerImpl.getPopupLayoutParams().x = (int) posX;
+ playerImpl.getPopupLayoutParams().y = (int) posY;
+
+ final View closingOverlayView = playerImpl.getClosingOverlayView();
+ if (playerImpl.isInsideClosingRadius(movingEvent)) {
+ if (closingOverlayView.getVisibility() == View.GONE) {
+ animateView(closingOverlayView, true, 250);
+ }
+ } else {
+ if (closingOverlayView.getVisibility() == View.VISIBLE) {
+ animateView(closingOverlayView, false, 0);
+ }
+ }
+
+// if (DEBUG) {
+// Log.d(TAG, "onScrollInPopup = "
+// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
+// + initialEvent.getRawY() + "], "
+// + "e1.getX,Y = [" + initialEvent.getX() + ", "
+// + initialEvent.getY() + "], "
+// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
+// + movingEvent.getRawY() + "], "
+// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
+// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
+// + "posX,Y = [" + posX + ", " + posY + "], "
+// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
+// }
+ playerImpl.windowManager
+ .updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
+ return true;
+ }
+
+ private void onScrollEndInPopup(final MotionEvent event) {
+ if (playerImpl == null) {
+ return;
+ }
+ if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
+ playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
+ }
+
+ if (playerImpl.isInsideClosingRadius(event)) {
+ playerImpl.closePopup();
+ } else {
+ animateView(playerImpl.getClosingOverlayView(), false, 0);
+
+ if (!playerImpl.isPopupClosing) {
+ animateView(playerImpl.getCloseOverlayButton(), false, 200);
+ }
+ }
+ }
+
+ private boolean onFlingInPopup(final MotionEvent e1,
+ final MotionEvent e2,
+ final float velocityX,
+ final float velocityY) {
+ if (playerImpl == null) {
+ return false;
+ }
+
+ final float absVelocityX = Math.abs(velocityX);
+ final float absVelocityY = Math.abs(velocityY);
+ if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
+ if (absVelocityX > tossFlingVelocity) {
+ playerImpl.getPopupLayoutParams().x = (int) velocityX;
+ }
+ if (absVelocityY > tossFlingVelocity) {
+ playerImpl.getPopupLayoutParams().y = (int) velocityY;
+ }
+ playerImpl.checkPopupPositionBounds();
+ playerImpl.windowManager
+ .updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onTouchInPopup(final View v, final MotionEvent event) {
+ if (playerImpl == null) {
+ return false;
+ }
+ playerImpl.getGestureDetector().onTouchEvent(event);
+
+ if (event.getPointerCount() == 2 && !isMovingInPopup && !isResizing) {
+ if (DEBUG) {
+ Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
+ }
+ playerImpl.showAndAnimateControl(-1, true);
+ playerImpl.getLoadingPanel().setVisibility(View.GONE);
+
+ playerImpl.hideControls(0, 0);
+ animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
+ animateView(playerImpl.getResizingIndicator(), true, 200, 0);
+ //record coordinates of fingers
+ initFirstPointerX = event.getX(0);
+ initFirstPointerY = event.getY(0);
+ initSecPointerX = event.getX(1);
+ initSecPointerY = event.getY(1);
+ //record distance between fingers
+ initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
+ initFirstPointerY - initSecPointerY);
+
+ isResizing = true;
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
+ if (DEBUG) {
+ Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
+ + "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
+ }
+ return handleMultiDrag(event);
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (DEBUG) {
+ Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
+ + "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
+ }
+ if (isMovingInPopup) {
+ isMovingInPopup = false;
+ onScrollEndInPopup(event);
+ }
+
+ if (isResizing) {
+ isResizing = false;
+
+ initPointerDistance = -1;
+ initFirstPointerX = -1;
+ initFirstPointerY = -1;
+ initSecPointerX = -1;
+ initSecPointerY = -1;
+
+ animateView(playerImpl.getResizingIndicator(), false, 100, 0);
+ playerImpl.changeState(playerImpl.getCurrentState());
+ }
+
+ if (!playerImpl.isPopupClosing) {
+ playerImpl.savePositionAndSize();
+ }
+ }
+
+ v.performClick();
+ return true;
+ }
+
+ private boolean handleMultiDrag(final MotionEvent event) {
+ if (initPointerDistance != -1 && event.getPointerCount() == 2) {
+ // get the movements of the fingers
+ final double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
+ event.getY(0) - initFirstPointerY);
+ final double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
+ event.getY(1) - initSecPointerY);
+
+ // minimum threshold beyond which pinch gesture will work
+ final int minimumMove = ViewConfiguration.get(service).getScaledTouchSlop();
+
+ if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
+ // calculate current distance between the pointers
+ final double currentPointerDistance =
+ Math.hypot(event.getX(0) - event.getX(1),
+ event.getY(0) - event.getY(1));
+
+ final double popupWidth = playerImpl.getPopupWidth();
+ // change co-ordinates of popup so the center stays at the same position
+ final double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
+ initPointerDistance = currentPointerDistance;
+ playerImpl.getPopupLayoutParams().x += (popupWidth - newWidth) / 2;
+
+ playerImpl.checkPopupPositionBounds();
+ playerImpl.updateScreenSize();
+
+ playerImpl.updatePopupSize(
+ (int) Math.min(playerImpl.getScreenWidth(), newWidth),
+ -1);
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /*
+ * Utils
+ * */
+
+ private int getNavigationBarHeight(final Context context) {
+ final int resId = context.getResources()
+ .getIdentifier("navigation_bar_height", "dimen", "android");
+ if (resId > 0) {
+ return context.getResources().getDimensionPixelSize(resId);
+ }
+ return 0;
+ }
+
+ private int getStatusBarHeight(final Context context) {
+ final int resId = context.getResources()
+ .getIdentifier("status_bar_height", "dimen", "android");
+ if (resId > 0) {
+ return context.getResources().getDimensionPixelSize(resId);
+ }
+ return 0;
+ }
+}
+
+
diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java
new file mode 100644
index 00000000000..f8d03087e0d
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java
@@ -0,0 +1,15 @@
+package org.schabi.newpipe.player.event;
+
+import com.google.android.exoplayer2.ExoPlaybackException;
+
+public interface PlayerServiceEventListener extends PlayerEventListener {
+ void onFullscreenStateChanged(boolean fullscreen);
+
+ void onScreenRotationButtonClicked();
+
+ void onMoreOptionsLongClicked();
+
+ void onPlayerError(ExoPlaybackException error);
+
+ void hideSystemUiIfNeeded();
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
index 369e3236e73..f434b062120 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java
@@ -114,7 +114,7 @@ public void onAudioFocusChange(final int focusChange) {
private void onAudioFocusGain() {
Log.d(TAG, "onAudioFocusGain() called");
player.setVolume(DUCK_AUDIO_TO);
- animateAudio(DUCK_AUDIO_TO, 1f);
+ animateAudio(DUCK_AUDIO_TO, 1.0f);
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true);
@@ -133,7 +133,7 @@ private void onAudioFocusLossCanDuck() {
}
private void animateAudio(final float from, final float to) {
- ValueAnimator valueAnimator = new ValueAnimator();
+ final ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setFloatValues(from, to);
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
valueAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
index 2ef22f2eb36..9703a35884b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java
@@ -80,13 +80,13 @@ public void tryDeleteCacheFiles() {
}
try {
- for (File file : cacheDir.listFiles()) {
+ for (final File file : cacheDir.listFiles()) {
final String filePath = file.getAbsolutePath();
final boolean deleteSuccessful = file.delete();
Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful);
}
- } catch (Exception ignored) {
+ } catch (final Exception ignored) {
Log.e(TAG, "Failed to delete file.", ignored);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
index 92ae009f699..e164e056343 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
@@ -29,7 +29,7 @@ private LoadController(final int initialPlaybackBufferMs,
final int optimalPlaybackBufferMs) {
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
- DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
+ final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
initialPlaybackBufferMs, initialPlaybackBufferMs);
internalLoadControl = builder.createDefaultLoadControl();
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
index e101e2185a5..849593e8911 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java
@@ -62,7 +62,7 @@ public void setLockScreenArt(final NotificationCompat.Builder builder,
.build()
);
- MediaStyle mediaStyle = new MediaStyle()
+ final MediaStyle mediaStyle = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken());
builder.setStyle(mediaStyle);
@@ -76,7 +76,7 @@ public void clearLockScreenArt(final NotificationCompat.Builder builder) {
.build()
);
- MediaStyle mediaStyle = new MediaStyle()
+ final MediaStyle mediaStyle = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken());
builder.setStyle(mediaStyle);
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
index 0d511d565de..2b6560534c9 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java
@@ -92,8 +92,10 @@ public class PlaybackParameterDialog extends DialogFragment {
public static PlaybackParameterDialog newInstance(final double playbackTempo,
final double playbackPitch,
- final boolean playbackSkipSilence) {
- PlaybackParameterDialog dialog = new PlaybackParameterDialog();
+ final boolean playbackSkipSilence,
+ final Callback callback) {
+ final PlaybackParameterDialog dialog = new PlaybackParameterDialog();
+ dialog.callback = callback;
dialog.initialTempo = playbackTempo;
dialog.initialPitch = playbackPitch;
@@ -111,9 +113,9 @@ public static PlaybackParameterDialog newInstance(final double playbackTempo,
@Override
public void onAttach(final Context context) {
super.onAttach(context);
- if (context != null && context instanceof Callback) {
+ if (context instanceof Callback) {
callback = (Callback) context;
- } else {
+ } else if (callback == null) {
dismiss();
}
}
@@ -185,8 +187,8 @@ private void setupControlViews(@NonNull final View rootView) {
private void setupTempoControl(@NonNull final View rootView) {
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
- TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
- TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
+ final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
+ final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
@@ -210,8 +212,8 @@ private void setupTempoControl(@NonNull final View rootView) {
private void setupPitchControl(@NonNull final View rootView) {
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
- TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
- TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
+ final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
+ final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
@@ -267,12 +269,12 @@ private void setupSkipSilenceControl(@NonNull final View rootView) {
}
private void setupStepSizeSelector(@NonNull final View rootView) {
- TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
- TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
- TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
- TextView stepSizeTwentyFivePercentText = rootView
+ final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
+ final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
+ final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
+ final TextView stepSizeTwentyFivePercentText = rootView
.findViewById(R.id.stepSizeTwentyFivePercent);
- TextView stepSizeOneHundredPercentText = rootView
+ final TextView stepSizeOneHundredPercentText = rootView
.findViewById(R.id.stepSizeOneHundredPercent);
if (stepSizeOnePercentText != null) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
index e63e56bf99f..09072340b1e 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
@@ -4,6 +4,7 @@
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
+import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.IntDef;
@@ -45,6 +46,9 @@
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
+import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
+import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE;
import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP;
@@ -56,6 +60,15 @@ public final class PlayerHelper {
private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x");
private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%");
+ @Retention(SOURCE)
+ @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI,
+ AUTOPLAY_TYPE_NEVER})
+ public @interface AutoplayType {
+ int AUTOPLAY_TYPE_ALWAYS = 0;
+ int AUTOPLAY_TYPE_WIFI = 1;
+ int AUTOPLAY_TYPE_NEVER = 2;
+ }
+
private PlayerHelper() { }
////////////////////////////////////////////////////////////////////////////
@@ -63,10 +76,10 @@ private PlayerHelper() { }
////////////////////////////////////////////////////////////////////////////
public static String getTimeString(final int milliSeconds) {
- int seconds = (milliSeconds % 60000) / 1000;
- int minutes = (milliSeconds % 3600000) / 60000;
- int hours = (milliSeconds % 86400000) / 3600000;
- int days = (milliSeconds % (86400000 * 7)) / 86400000;
+ final int seconds = (milliSeconds % 60000) / 1000;
+ final int minutes = (milliSeconds % 3600000) / 60000;
+ final int hours = (milliSeconds % 86400000) / 3600000;
+ final int days = (milliSeconds % (86400000 * 7)) / 86400000;
STRING_BUILDER.setLength(0);
return days > 0
@@ -203,6 +216,11 @@ public static boolean isAutoQueueEnabled(@NonNull final Context context) {
return isAutoQueueEnabled(context, false);
}
+ public static boolean isClearingQueueConfirmationRequired(@NonNull final Context context) {
+ return getPreferences(context)
+ .getBoolean(context.getString(R.string.clear_queue_confirmation_key), false);
+ }
+
@MinimizeMode
public static int getMinimizeOnExitAction(@NonNull final Context context) {
final String defaultAction = context.getString(R.string.minimize_on_exit_none_key);
@@ -219,6 +237,18 @@ public static int getMinimizeOnExitAction(@NonNull final Context context) {
}
}
+ @AutoplayType
+ public static int getAutoplayType(@NonNull final Context context) {
+ final String type = getAutoplayType(context, context.getString(R.string.autoplay_wifi_key));
+ if (type.equals(context.getString(R.string.autoplay_always_key))) {
+ return AUTOPLAY_TYPE_ALWAYS;
+ } else if (type.equals(context.getString(R.string.autoplay_never_key))) {
+ return AUTOPLAY_TYPE_NEVER;
+ } else {
+ return AUTOPLAY_TYPE_WIFI;
+ }
+ }
+
@NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
@@ -308,7 +338,7 @@ public static float getCaptionScale(@NonNull final Context context) {
final CaptioningManager captioningManager
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager == null || !captioningManager.isEnabled()) {
- return 1f;
+ return 1.0f;
}
return captioningManager.getFontScale();
@@ -324,6 +354,13 @@ public static void setScreenBrightness(@NonNull final Context context,
setScreenBrightness(context, setScreenBrightness, System.currentTimeMillis());
}
+ public static boolean globalScreenOrientationLocked(final Context context) {
+ // 1: Screen orientation changes using accelerometer
+ // 0: Screen orientation is locked
+ return android.provider.Settings.System.getInt(
+ context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 0;
+ }
+
////////////////////////////////////////////////////////////////////////////
// Private helpers
////////////////////////////////////////////////////////////////////////////
@@ -368,7 +405,7 @@ private static boolean isAutoQueueEnabled(@NonNull final Context context, final
private static void setScreenBrightness(@NonNull final Context context,
final float screenBrightness, final long timestamp) {
- SharedPreferences.Editor editor = getPreferences(context).edit();
+ final SharedPreferences.Editor editor = getPreferences(context).edit();
editor.putFloat(context.getString(R.string.screen_brightness_key), screenBrightness);
editor.putLong(context.getString(R.string.screen_brightness_timestamp_key), timestamp);
editor.apply();
@@ -376,8 +413,8 @@ private static void setScreenBrightness(@NonNull final Context context,
private static float getScreenBrightness(@NonNull final Context context,
final float screenBrightness) {
- SharedPreferences sp = getPreferences(context);
- long timestamp = sp
+ final SharedPreferences sp = getPreferences(context);
+ final long timestamp = sp
.getLong(context.getString(R.string.screen_brightness_timestamp_key), 0);
// Hypothesis: 4h covers a viewing block, e.g. evening.
// External lightning conditions will change in the next
@@ -396,9 +433,15 @@ private static String getMinimizeOnExitAction(@NonNull final Context context,
.getString(context.getString(R.string.minimize_on_exit_key), key);
}
+ private static String getAutoplayType(@NonNull final Context context,
+ final String key) {
+ return getPreferences(context).getString(context.getString(R.string.autoplay_key),
+ key);
+ }
+
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
final StreamInfoItem streamInfoItem) {
- SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
+ final SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
singlePlayQueue.getItem().setAutoQueued(true);
return singlePlayQueue;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java
index 1f1152b627f..764c375afae 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java
@@ -87,13 +87,13 @@ private void publishFloatingQueueWindow() {
}
// Yes this is almost a copypasta, got a problem with that? =\
- int windowCount = callback.getQueueSize();
- int currentWindowIndex = callback.getCurrentPlayingIndex();
- int queueSize = Math.min(maxQueueSize, windowCount);
- int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
+ final int windowCount = callback.getQueueSize();
+ final int currentWindowIndex = callback.getCurrentPlayingIndex();
+ final int queueSize = Math.min(maxQueueSize, windowCount);
+ final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
windowCount - queueSize);
- List queue = new ArrayList<>();
+ final List queue = new ArrayList<>();
for (int i = startIndex; i < startIndex + queueSize; i++) {
queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i));
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java
index 0154716e078..5b20077c396 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java
@@ -57,13 +57,14 @@ public MediaDescriptionCompat getQueueMetadata(final int index) {
}
final PlayQueueItem item = player.getPlayQueue().getItem(index);
- MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder()
+ final MediaDescriptionCompat.Builder descriptionBuilder
+ = new MediaDescriptionCompat.Builder()
.setMediaId(String.valueOf(index))
.setTitle(item.getTitle())
.setSubtitle(item.getUploader());
// set additional metadata for A2DP/AVRCP
- Bundle additionalMetadata = new Bundle();
+ final Bundle additionalMetadata = new Bundle();
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle());
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader());
additionalMetadata
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java
index e554059d9a5..d70707fdbf1 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java
@@ -60,14 +60,14 @@ protected Pair selectTextTrack(
TextTrackScore selectedTrackScore = null;
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
- TrackGroup trackGroup = groups.get(groupIndex);
- @Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
+ final TrackGroup trackGroup = groups.get(groupIndex);
+ @Capabilities final int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
- Format format = trackGroup.getFormat(trackIndex);
- TextTrackScore trackScore = new TextTrackScore(format, params,
+ final Format format = trackGroup.getFormat(trackIndex);
+ final TextTrackScore trackScore = new TextTrackScore(format, params,
trackFormatSupport[trackIndex], selectedAudioLanguage);
if (formatHasLanguage(format, preferredTextLanguage)) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java
index 3c15cd3422d..ec364c4df7f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java
@@ -129,7 +129,7 @@ public void dispose() {
}
private static List extractListItems(final List infos) {
- List result = new ArrayList<>();
+ final List result = new ArrayList<>();
for (final InfoItem stream : infos) {
if (stream instanceof StreamInfoItem) {
result.add(new PlayQueueItem((StreamInfoItem) stream));
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
index 7391294ba7a..dc580346202 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
@@ -51,16 +51,24 @@ public abstract class PlayQueue implements Serializable {
@NonNull
private final AtomicInteger queueIndex;
+ private final ArrayList history;
private transient BehaviorSubject eventBroadcast;
private transient Flowable broadcastReceiver;
private transient Subscription reportingReactor;
+ private transient boolean disposed;
+
PlayQueue(final int index, final List startWith) {
streams = new ArrayList<>();
streams.addAll(startWith);
+ history = new ArrayList<>();
+ if (streams.size() > index) {
+ history.add(streams.get(index));
+ }
queueIndex = new AtomicInteger(index);
+ disposed = false;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -99,6 +107,7 @@ public void dispose() {
eventBroadcast = null;
broadcastReceiver = null;
reportingReactor = null;
+ disposed = true;
}
/**
@@ -149,6 +158,9 @@ public synchronized void setIndex(final int index) {
if (index >= streams.size()) {
newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
}
+ if (oldIndex != newIndex) {
+ history.add(streams.get(newIndex));
+ }
queueIndex.set(newIndex);
broadcast(new SelectEvent(oldIndex, newIndex));
@@ -269,7 +281,7 @@ public synchronized void append(@NonNull final PlayQueueItem... items) {
* @param items {@link PlayQueueItem}s to append
*/
public synchronized void append(@NonNull final List items) {
- List itemList = new ArrayList<>(items);
+ final List itemList = new ArrayList<>(items);
if (isShuffled()) {
backup.addAll(itemList);
@@ -314,6 +326,9 @@ public synchronized void remove(final int index) {
public synchronized void error() {
final int oldIndex = getIndex();
queueIndex.incrementAndGet();
+ if (streams.size() > queueIndex.get()) {
+ history.add(streams.get(queueIndex.get()));
+ }
broadcast(new ErrorEvent(oldIndex, getIndex()));
}
@@ -334,7 +349,11 @@ private synchronized void removeInternal(final int removeIndex) {
if (backup != null) {
backup.remove(getItem(removeIndex));
}
- streams.remove(removeIndex);
+
+ history.remove(streams.remove(removeIndex));
+ if (streams.size() > queueIndex.get()) {
+ history.add(streams.get(queueIndex.get()));
+ }
}
/**
@@ -367,7 +386,7 @@ public synchronized void move(final int source, final int target) {
queueIndex.incrementAndGet();
}
- PlayQueueItem playQueueItem = streams.remove(source);
+ final PlayQueueItem playQueueItem = streams.remove(source);
playQueueItem.setAutoQueued(false);
streams.add(target, playQueueItem);
broadcast(new MoveEvent(source, target));
@@ -427,6 +446,9 @@ public synchronized void shuffle() {
streams.add(0, streams.remove(newIndex));
}
queueIndex.set(0);
+ if (streams.size() > 0) {
+ history.add(streams.get(0));
+ }
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
}
@@ -458,10 +480,60 @@ public synchronized void unshuffle() {
} else {
queueIndex.set(0);
}
+ if (streams.size() > queueIndex.get()) {
+ history.add(streams.get(queueIndex.get()));
+ }
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
}
+ /**
+ * Selects previous played item.
+ *
+ * This method removes currently playing item from history and
+ * starts playing the last item from history if it exists
+ *
+ * @return true if history is not empty and the item can be played
+ * */
+ public synchronized boolean previous() {
+ if (history.size() <= 1) {
+ return false;
+ }
+
+ history.remove(history.size() - 1);
+
+ final PlayQueueItem last = history.remove(history.size() - 1);
+ setIndex(indexOf(last));
+
+ return true;
+ }
+
+ /*
+ * Compares two PlayQueues. Useful when a user switches players but queue is the same so
+ * we don't have to do anything with new queue.
+ * This method also gives a chance to track history of items in a queue in
+ * VideoDetailFragment without duplicating items from two identical queues
+ * */
+ @Override
+ public boolean equals(@Nullable final Object obj) {
+ if (!(obj instanceof PlayQueue)
+ || getStreams().size() != ((PlayQueue) obj).getStreams().size()) {
+ return false;
+ }
+
+ final PlayQueue other = (PlayQueue) obj;
+ for (int i = 0; i < getStreams().size(); i++) {
+ if (!getItem(i).getUrl().equals(other.getItem(i).getUrl())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean isDisposed() {
+ return disposed;
+ }
/*//////////////////////////////////////////////////////////////////////////
// Rx Broadcast
//////////////////////////////////////////////////////////////////////////*/
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java
index 79cf0601c39..527e804703c 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java
@@ -26,7 +26,7 @@ public SinglePlayQueue(final List items, final int index) {
}
private static List playQueueItemsOf(final List items) {
- List playQueueItems = new ArrayList<>(items.size());
+ final List playQueueItems = new ArrayList<>(items.size());
for (final StreamInfoItem item : items) {
playQueueItems.add(new PlayQueueItem(item));
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
index 2eb766769bc..814fda4ba98 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
@@ -52,7 +52,7 @@ public MediaSource resolve(@NonNull final StreamInfo info) {
return liveSource;
}
- List mediaSources = new ArrayList<>();
+ final List mediaSources = new ArrayList<>();
// Create video stream source
final List videos = ListHelper.getSortedStreamVideosList(context,
@@ -106,7 +106,7 @@ public MediaSource resolve(@NonNull final StreamInfo info) {
SELECTION_FLAG_AUTOSELECT,
PlayerHelper.captionLanguageOf(context, subtitle));
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
- .createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
+ .createMediaSource(Uri.parse(subtitle.getUrl()), textFormat, TIME_UNSET);
mediaSources.add(textSource);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
index 52761e46769..7ff3af91a32 100644
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
@@ -112,9 +112,9 @@ public static void reportError(final Context context, final List el,
private static void startErrorActivity(final Class returnActivity, final Context context,
final ErrorInfo errorInfo, final List el) {
- ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
+ final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
ac.setReturnActivity(returnActivity);
- Intent intent = new Intent(context, ErrorActivity.class);
+ final Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
intent.putExtra(ERROR_LIST, elToSl(el));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -154,9 +154,9 @@ public static void reportError(final Handler handler, final Context context,
public static void reportError(final Context context, final CrashReportData report,
final ErrorInfo errorInfo) {
- String[] el = new String[]{report.getString(ReportField.STACK_TRACE)};
+ final String[] el = new String[]{report.getString(ReportField.STACK_TRACE)};
- Intent intent = new Intent(context, ErrorActivity.class);
+ final Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
intent.putExtra(ERROR_LIST, el);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -172,7 +172,7 @@ private static String getStackTrace(final Throwable throwable) {
// errorList to StringList
private static String[] elToSl(final List stackTraces) {
- String[] out = new String[stackTraces.size()];
+ final String[] out = new String[stackTraces.size()];
for (int i = 0; i < stackTraces.size(); i++) {
out[i] = getStackTrace(stackTraces.get(i));
}
@@ -186,12 +186,12 @@ protected void onCreate(final Bundle savedInstanceState) {
ThemeHelper.setTheme(this);
setContentView(R.layout.activity_error);
- Intent intent = getIntent();
+ final Intent intent = getIntent();
- Toolbar toolbar = findViewById(R.id.toolbar);
+ final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
- ActionBar actionBar = getSupportActionBar();
+ final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.error_report_title);
@@ -203,11 +203,11 @@ protected void onCreate(final Bundle savedInstanceState) {
final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton);
userCommentBox = findViewById(R.id.errorCommentBox);
- TextView errorView = findViewById(R.id.errorView);
- TextView infoView = findViewById(R.id.errorInfosView);
- TextView errorMessageView = findViewById(R.id.errorMessageView);
+ final TextView errorView = findViewById(R.id.errorView);
+ final TextView infoView = findViewById(R.id.errorInfosView);
+ final TextView errorMessageView = findViewById(R.id.errorMessageView);
- ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
+ final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
returnActivity = ac.getReturnActivity();
errorInfo = intent.getParcelableExtra(ERROR_INFO);
errorList = intent.getStringArrayExtra(ERROR_LIST);
@@ -242,27 +242,27 @@ protected void onCreate(final Bundle savedInstanceState) {
errorView.setText(formErrorText(errorList));
// print stack trace once again for debugging:
- for (String e : errorList) {
+ for (final String e : errorList) {
Log.e(TAG, e);
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
- MenuInflater inflater = getMenuInflater();
+ final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.error_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
- int id = item.getItemId();
+ final int id = item.getItemId();
switch (id) {
case android.R.id.home:
goToReturnActivity();
break;
case R.id.menu_item_share_error:
- Intent intent = new Intent();
+ final Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, buildJson());
intent.setType("text/plain");
@@ -304,9 +304,9 @@ private void openPrivacyPolicyDialog(final Context context, final String action)
}
private String formErrorText(final String[] el) {
- StringBuilder text = new StringBuilder();
+ final StringBuilder text = new StringBuilder();
if (el != null) {
- for (String e : el) {
+ for (final String e : el) {
text.append("-------------------------------------\n").append(e);
}
}
@@ -334,19 +334,19 @@ static Class extends Activity> getReturnActivity(final Class> returnActivity
}
private void goToReturnActivity() {
- Class extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
+ final Class extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
if (checkedReturnActivity == null) {
super.onBackPressed();
} else {
- Intent intent = new Intent(this, checkedReturnActivity);
+ final Intent intent = new Intent(this, checkedReturnActivity);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
}
}
private void buildInfo(final ErrorInfo info) {
- TextView infoLabelView = findViewById(R.id.errorInfoLabelsView);
- TextView infoView = findViewById(R.id.errorInfosView);
+ final TextView infoLabelView = findViewById(R.id.errorInfoLabelsView);
+ final TextView infoView = findViewById(R.id.errorInfosView);
String text = "";
infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n"));
@@ -383,7 +383,7 @@ private String buildJson() {
.value("user_comment", userCommentBox.getText().toString())
.end()
.done();
- } catch (Throwable e) {
+ } catch (final Throwable e) {
Log.e(TAG, "Error while erroring: Could not build json");
e.printStackTrace();
}
@@ -441,7 +441,7 @@ private String buildMarkdown() {
}
htmlErrorReport.append("
\n");
return htmlErrorReport.toString();
- } catch (Throwable e) {
+ } catch (final Throwable e) {
Log.e(TAG, "Error while erroring: Could not build markdown");
e.printStackTrace();
return "";
@@ -478,7 +478,7 @@ private String getOsString() {
private void addGuruMeditation() {
//just an easter egg
- TextView sorryView = findViewById(R.id.errorSorryView);
+ final TextView sorryView = findViewById(R.id.errorSorryView);
String text = sorryView.getText().toString();
text += "\n" + getString(R.string.guru_meditation);
sorryView.setText(text);
@@ -491,7 +491,7 @@ public void onBackPressed() {
}
public String getCurrentTimeStamp() {
- SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(new Date());
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index a9531693cef..ab875ed5d32 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -42,7 +42,7 @@ public boolean onPreferenceChange(final Preference preference, final Object newV
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String themeKey = getString(R.string.theme_key);
+ final String themeKey = getString(R.string.theme_key);
startThemeKey = defaultPreferences
.getString(themeKey, getString(R.string.default_theme_value));
findPreference(themeKey).setOnPreferenceChangeListener(themePreferenceChange);
@@ -64,7 +64,7 @@ public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) {
try {
startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
- } catch (ActivityNotFoundException e) {
+ } catch (final ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.general_error, Toast.LENGTH_SHORT).show();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
index 125931ee113..d8b68629791 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
@@ -39,7 +39,7 @@ public void onResume() {
private void updateTitle() {
if (getActivity() instanceof AppCompatActivity) {
- ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(getPreferenceScreen().getTitle());
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index b0bb30aa70c..6ea2cc8a6c7 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -32,7 +32,6 @@
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -91,7 +90,7 @@ public boolean onPreferenceTreeClick(final Preference preference) {
}
if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) {
- Context context = getContext();
+ final Context context = getContext();
if (context != null) {
DownloaderImpl.getInstance().updateYoutubeRestrictedModeCookies(context);
} else {
@@ -105,7 +104,7 @@ public boolean onPreferenceTreeClick(final Preference preference) {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
- String homeDir = getActivity().getApplicationInfo().dataDir;
+ final String homeDir = getActivity().getApplicationInfo().dataDir;
databasesDir = new File(homeDir + "/databases");
newpipeDb = new File(homeDir + "/databases/newpipe.db");
newpipeDbJournal = new File(homeDir + "/databases/newpipe.db-journal");
@@ -117,9 +116,9 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
addPreferencesFromResource(R.xml.content_settings);
- Preference importDataPreference = findPreference(getString(R.string.import_data));
+ final Preference importDataPreference = findPreference(getString(R.string.import_data));
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
- Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
+ final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
@@ -128,9 +127,9 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
return true;
});
- Preference exportDataPreference = findPreference(getString(R.string.export_data));
+ final Preference exportDataPreference = findPreference(getString(R.string.export_data));
exportDataPreference.setOnPreferenceClickListener((Preference p) -> {
- Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
+ final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
@@ -175,12 +174,12 @@ public void onActivityResult(final int requestCode, final int resultCode,
if ((requestCode == REQUEST_IMPORT_PATH || requestCode == REQUEST_EXPORT_PATH)
&& resultCode == Activity.RESULT_OK && data.getData() != null) {
- String path = Utils.getFileForUri(data.getData()).getAbsolutePath();
+ final String path = Utils.getFileForUri(data.getData()).getAbsolutePath();
if (requestCode == REQUEST_EXPORT_PATH) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip");
} else {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.override_current_data)
.setPositiveButton(getString(R.string.finish),
(DialogInterface d, int id) -> importDatabase(path))
@@ -196,7 +195,7 @@ private void exportDatabase(final String path) {
//checkpoint before export
NewPipeDatabase.checkpoint();
- ZipOutputStream outZip = new ZipOutputStream(
+ final ZipOutputStream outZip = new ZipOutputStream(
new BufferedOutputStream(
new FileOutputStream(path)));
ZipHelper.addFileToZip(outZip, newpipeDb.getPath(), "newpipe.db");
@@ -208,7 +207,7 @@ private void exportDatabase(final String path) {
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT)
.show();
- } catch (Exception e) {
+ } catch (final Exception e) {
onError(e);
}
}
@@ -217,12 +216,11 @@ private void saveSharedPreferencesToFile(final File dst) {
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream(dst));
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
+ final SharedPreferences pref
+ = PreferenceManager.getDefaultSharedPreferences(getContext());
output.writeObject(pref.getAll());
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
+ } catch (final IOException e) {
e.printStackTrace();
} finally {
try {
@@ -230,7 +228,7 @@ private void saveSharedPreferencesToFile(final File dst) {
output.flush();
output.close();
}
- } catch (IOException ex) {
+ } catch (final IOException ex) {
ex.printStackTrace();
}
}
@@ -241,14 +239,14 @@ private void importDatabase(final String filePath) {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(filePath);
- } catch (IOException ioe) {
+ } catch (final IOException ioe) {
Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
.show();
return;
} finally {
try {
zipFile.close();
- } catch (Exception ignored) {
+ } catch (final Exception ignored) {
}
}
@@ -272,7 +270,7 @@ private void importDatabase(final String filePath) {
//If settings file exist, ask if it should be imported.
if (ZipHelper.extractFileFromZip(filePath, newpipeSettings.getPath(),
"newpipe.settings")) {
- AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
+ final AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle(R.string.import_settings);
alert.setNegativeButton(android.R.string.no, (dialog, which) -> {
@@ -291,7 +289,7 @@ private void importDatabase(final String filePath) {
// restart app to properly load db
System.exit(0);
}
- } catch (Exception e) {
+ } catch (final Exception e) {
onError(e);
}
}
@@ -300,13 +298,13 @@ private void loadSharedPreferences(final File src) {
ObjectInputStream input = null;
try {
input = new ObjectInputStream(new FileInputStream(src));
- SharedPreferences.Editor prefEdit = PreferenceManager
+ final SharedPreferences.Editor prefEdit = PreferenceManager
.getDefaultSharedPreferences(getContext()).edit();
prefEdit.clear();
- Map entries = (Map) input.readObject();
- for (Map.Entry entry : entries.entrySet()) {
- Object v = entry.getValue();
- String key = entry.getKey();
+ final Map entries = (Map) input.readObject();
+ for (final Map.Entry entry : entries.entrySet()) {
+ final Object v = entry.getValue();
+ final String key = entry.getKey();
if (v instanceof Boolean) {
prefEdit.putBoolean(key, (Boolean) v);
@@ -321,18 +319,14 @@ private void loadSharedPreferences(final File src) {
}
}
prefEdit.commit();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
+ } catch (final IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (input != null) {
input.close();
}
- } catch (IOException ex) {
+ } catch (final IOException ex) {
ex.printStackTrace();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
index aaa572eab9b..a4b29fc4941 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
@@ -120,7 +120,7 @@ private void showPathInSummary(final String prefKey, @StringRes final int defaul
try {
rawUri = URLDecoder.decode(rawUri, StandardCharsets.UTF_8.name());
- } catch (UnsupportedEncodingException e) {
+ } catch (final UnsupportedEncodingException e) {
// nothing to do
}
@@ -132,7 +132,7 @@ private boolean isFileUri(final String path) {
}
private boolean hasInvalidPath(final String prefKey) {
- String value = defaultPreferences.getString(prefKey, null);
+ final String value = defaultPreferences.getString(prefKey, null);
return value == null || value.isEmpty();
}
@@ -152,20 +152,20 @@ private void forgetSAFTree(final Context context, final String oldPath) {
}
try {
- Uri uri = Uri.parse(oldPath);
+ final Uri uri = Uri.parse(oldPath);
context.getContentResolver()
.releasePersistableUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS);
context.revokeUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS);
Log.i(TAG, "Revoke old path permissions success on " + oldPath);
- } catch (Exception err) {
+ } catch (final Exception err) {
Log.e(TAG, "Error revoking old path permissions on " + oldPath, err);
}
}
private void showMessageDialog(@StringRes final int title, @StringRes final int message) {
- AlertDialog.Builder msg = new AlertDialog.Builder(ctx);
+ final AlertDialog.Builder msg = new AlertDialog.Builder(ctx);
msg.setTitle(title);
msg.setMessage(message);
msg.setPositiveButton(getString(R.string.finish), null);
@@ -179,8 +179,8 @@ public boolean onPreferenceTreeClick(final Preference preference) {
+ "preference = [" + preference + "]");
}
- String key = preference.getKey();
- int request;
+ final String key = preference.getKey();
+ final int request;
if (key.equals(storageUseSafPreference)) {
Toast.makeText(getContext(), R.string.download_choose_new_path,
@@ -194,7 +194,7 @@ public boolean onPreferenceTreeClick(final Preference preference) {
return super.onPreferenceTreeClick(preference);
}
- Intent i;
+ final Intent i;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& NewPipeSettings.useStorageAccessFramework(ctx)) {
i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
@@ -229,7 +229,7 @@ public void onActivityResult(final int requestCode, final int resultCode, final
return;
}
- String key;
+ final String key;
if (requestCode == REQUEST_DOWNLOAD_VIDEO_PATH) {
key = downloadPathVideoPreference;
} else if (requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) {
@@ -262,19 +262,20 @@ public void onActivityResult(final int requestCode, final int resultCode, final
context.grantUriPermission(context.getPackageName(), uri,
StoredDirectoryHelper.PERMISSION_FLAGS);
- StoredDirectoryHelper mainStorage = new StoredDirectoryHelper(context, uri, null);
+ final StoredDirectoryHelper mainStorage
+ = new StoredDirectoryHelper(context, uri, null);
Log.i(TAG, "Acquiring tree success from " + uri.toString());
if (!mainStorage.canWrite()) {
throw new IOException("No write permissions on " + uri.toString());
}
- } catch (IOException err) {
+ } catch (final IOException err) {
Log.e(TAG, "Error acquiring tree from " + uri.toString(), err);
showMessageDialog(R.string.general_error, R.string.no_available_dir);
return;
}
} else {
- File target = Utils.getFileForUri(uri);
+ final File target = Utils.getFileForUri(uri);
if (!target.canWrite()) {
showMessageDialog(R.string.download_to_sdcard_error_title,
R.string.download_to_sdcard_error_message);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
index 47a16f6f3fe..8ce5fe4c2b9 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
@@ -60,14 +60,14 @@ private static void getAudioDownloadFolder(final Context context) {
private static void getDir(final Context context, final int keyID,
final String defaultDirectoryName) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String key = context.getString(keyID);
- String downloadPath = prefs.getString(key, null);
+ final String downloadPath = prefs.getString(key, null);
if ((downloadPath != null) && (!downloadPath.isEmpty())) {
return;
}
- SharedPreferences.Editor spEditor = prefs.edit();
+ final SharedPreferences.Editor spEditor = prefs.edit();
spEditor.putString(key, getNewPipeChildFolderPathForDir(getDir(defaultDirectoryName)));
spEditor.apply();
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
index 03e2465333e..dfa975eefaa 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java
@@ -96,16 +96,16 @@ public void onViewCreated(@NonNull final View rootView,
}
private void initViews(@NonNull final View rootView) {
- TextView instanceHelpTV = rootView.findViewById(R.id.instanceHelpTV);
+ final TextView instanceHelpTV = rootView.findViewById(R.id.instanceHelpTV);
instanceHelpTV.setText(getString(R.string.peertube_instance_url_help,
getString(R.string.peertube_instance_list_url)));
initButton(rootView);
- RecyclerView listInstances = rootView.findViewById(R.id.instances);
+ final RecyclerView listInstances = rootView.findViewById(R.id.instances);
listInstances.setLayoutManager(new LinearLayoutManager(requireContext()));
- ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
+ final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(listInstances);
instanceListAdapter = new InstanceListAdapter(requireContext(), itemTouchHelper);
@@ -178,7 +178,7 @@ private void selectInstance(final PeertubeInstance instance) {
private void updateTitle() {
if (getActivity() instanceof AppCompatActivity) {
- ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.peertube_instance_url_title);
}
@@ -186,14 +186,14 @@ private void updateTitle() {
}
private void saveChanges() {
- JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances");
- for (PeertubeInstance instance : instanceList) {
+ final JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances");
+ for (final PeertubeInstance instance : instanceList) {
jsonWriter.object();
jsonWriter.value("name", instance.getName());
jsonWriter.value("url", instance.getUrl());
jsonWriter.end();
}
- String jsonToSave = jsonWriter.end().end().done();
+ final String jsonToSave = jsonWriter.end().end().done();
sharedPreferences.edit().putString(savedInstanceListKey, jsonToSave).apply();
}
@@ -222,12 +222,12 @@ private void showAddItemDialog(final Context c) {
final EditText urlET = new EditText(c);
urlET.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
urlET.setHint(R.string.peertube_instance_add_help);
- AlertDialog dialog = new AlertDialog.Builder(c)
+ final AlertDialog dialog = new AlertDialog.Builder(c)
.setTitle(R.string.peertube_instance_add_title)
.setIcon(R.drawable.place_holder_peertube)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.finish, (dialog1, which) -> {
- String url = urlET.getText().toString();
+ final String url = urlET.getText().toString();
addInstance(url);
})
.create();
@@ -236,13 +236,13 @@ private void showAddItemDialog(final Context c) {
}
private void addInstance(final String url) {
- String cleanUrl = cleanUrl(url);
+ final String cleanUrl = cleanUrl(url);
if (cleanUrl == null) {
return;
}
progressBar.setVisibility(View.VISIBLE);
- Disposable disposable = Single.fromCallable(() -> {
- PeertubeInstance instance = new PeertubeInstance(cleanUrl);
+ final Disposable disposable = Single.fromCallable(() -> {
+ final PeertubeInstance instance = new PeertubeInstance(cleanUrl);
instance.fetchInstanceMetaData();
return instance;
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
@@ -273,7 +273,7 @@ private String cleanUrl(final String url) {
return null;
}
// only allow if not already exists
- for (PeertubeInstance instance : instanceList) {
+ for (final PeertubeInstance instance : instanceList) {
if (instance.getUrl().equals(cleanUrl)) {
Toast.makeText(getActivity(), R.string.peertube_instance_add_exists,
Toast.LENGTH_SHORT).show();
@@ -331,7 +331,7 @@ public boolean isItemViewSwipeEnabled() {
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) {
- int position = viewHolder.getAdapterPosition();
+ final int position = viewHolder.getAdapterPosition();
// do not allow swiping the selected instance
if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) {
instanceListAdapter.notifyItemChanged(position);
@@ -372,7 +372,7 @@ public void swapItems(final int fromPosition, final int toPosition) {
@Override
public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
final int viewType) {
- View view = inflater.inflate(R.layout.item_instance, parent, false);
+ final View view = inflater.inflate(R.layout.item_instance, parent, false);
return new InstanceListAdapter.TabViewHolder(view);
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
index df529fee0b0..1bc65da7038 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
@@ -94,10 +94,10 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.select_channel_fragment, container, false);
+ final View v = inflater.inflate(R.layout.select_channel_fragment, container, false);
recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- SelectChannelAdapter channelAdapter = new SelectChannelAdapter();
+ final SelectChannelAdapter channelAdapter = new SelectChannelAdapter();
recyclerView.setAdapter(channelAdapter);
progressBar = v.findViewById(R.id.progressBar);
@@ -107,7 +107,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup
emptyView.setVisibility(View.GONE);
- SubscriptionManager subscriptionManager = new SubscriptionManager(getContext());
+ final SubscriptionManager subscriptionManager = new SubscriptionManager(getContext());
subscriptionManager.subscriptions().toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -130,7 +130,7 @@ public void onCancel(final DialogInterface dialogInterface) {
private void clickedItem(final int position) {
if (onSelectedListener != null) {
- SubscriptionEntity entry = subscriptions.get(position);
+ final SubscriptionEntity entry = subscriptions.get(position);
onSelectedListener
.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName());
}
@@ -199,14 +199,14 @@ private class SelectChannelAdapter
@Override
public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent,
final int viewType) {
- View item = LayoutInflater.from(parent.getContext())
+ final View item = LayoutInflater.from(parent.getContext())
.inflate(R.layout.select_channel_item, parent, false);
return new SelectChannelItemHolder(item);
}
@Override
public void onBindViewHolder(final SelectChannelItemHolder holder, final int position) {
- SubscriptionEntity entry = subscriptions.get(position);
+ final SubscriptionEntity entry = subscriptions.get(position);
holder.titleView.setText(entry.getName());
holder.view.setOnClickListener(new View.OnClickListener() {
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
index 13d34dec871..9d0fece4fe6 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
@@ -76,12 +76,12 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
- View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
+ final View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
try {
selectKioskAdapter = new SelectKioskAdapter();
- } catch (Exception e) {
+ } catch (final Exception e) {
onError(e);
}
recyclerView.setAdapter(selectKioskAdapter);
@@ -135,9 +135,9 @@ private class SelectKioskAdapter
private final List kioskList = new Vector<>();
SelectKioskAdapter() throws Exception {
- for (StreamingService service : NewPipe.getServices()) {
- for (String kioskId : service.getKioskList().getAvailableKiosks()) {
- String name = String.format(getString(R.string.service_kiosk_string),
+ for (final StreamingService service : NewPipe.getServices()) {
+ for (final String kioskId : service.getKioskList().getAvailableKiosks()) {
+ final String name = String.format(getString(R.string.service_kiosk_string),
service.getServiceInfo().getName(),
KioskTranslator.getTranslatedKioskName(kioskId, getContext()));
kioskList.add(new Entry(ServiceHelper.getIcon(service.getServiceId()),
@@ -151,7 +151,7 @@ public int getItemCount() {
}
public SelectKioskItemHolder onCreateViewHolder(final ViewGroup parent, final int type) {
- View item = LayoutInflater.from(parent.getContext())
+ final View item = LayoutInflater.from(parent.getContext())
.inflate(R.layout.select_kiosk_item, parent, false);
return new SelectKioskItemHolder(item);
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index 1d5c94421a7..c858c7f7719 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -74,7 +74,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup
inflater.inflate(R.layout.select_playlist_fragment, container, false);
recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
+ final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
recyclerView.setAdapter(playlistAdapter);
progressBar = v.findViewById(R.id.progressBar);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
index 18cbece6fb4..d2d4c240439 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
@@ -13,7 +13,7 @@
import androidx.preference.PreferenceFragmentCompat;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.util.AndroidTvUtils;
+import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
@@ -53,7 +53,7 @@ protected void onCreate(final Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
setContentView(R.layout.settings_layout);
- Toolbar toolbar = findViewById(R.id.toolbar);
+ final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (savedInstanceBundle == null) {
@@ -62,14 +62,14 @@ protected void onCreate(final Bundle savedInstanceBundle) {
.commit();
}
- if (AndroidTvUtils.isTv(this)) {
+ if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this);
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
- ActionBar actionBar = getSupportActionBar();
+ final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
@@ -80,7 +80,7 @@ public boolean onCreateOptionsMenu(final Menu menu) {
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
- int id = item.getItemId();
+ final int id = item.getItemId();
if (id == android.R.id.home) {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finish();
@@ -95,7 +95,7 @@ public boolean onOptionsItemSelected(final MenuItem item) {
@Override
public boolean onPreferenceStartFragment(final PreferenceFragmentCompat caller,
final Preference preference) {
- Fragment fragment = Fragment
+ final Fragment fragment = Fragment
.instantiate(this, preference.getFragment(), preference.getExtras());
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
diff --git a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java
index 2b103e79427..476cf97ab11 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java
@@ -19,7 +19,7 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String updateToggleKey = getString(R.string.update_app_key);
+ final String updateToggleKey = getString(R.string.update_app_key);
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java
index bef9a7b56ab..ce6d6dad5b8 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/VideoAudioSettingsFragment.java
@@ -35,7 +35,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
// show a snackbar to let the user give permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& s.equals(getString(R.string.minimize_on_exit_key))) {
- String newSetting = sharedPreferences.getString(s, null);
+ final String newSetting = sharedPreferences.getString(s, null);
if (newSetting != null
&& newSetting.equals(getString(R.string.minimize_on_exit_popup_key))
&& !Settings.canDrawOverlays(getContext())) {
@@ -68,7 +68,7 @@ private void updateSeekOptions() {
final boolean inexactSeek = getPreferenceManager().getSharedPreferences()
.getBoolean(res.getString(R.string.use_inexact_seek_key), false);
- for (String durationsValue : durationsValues) {
+ for (final String durationsValue : durationsValues) {
currentDurationValue =
Integer.parseInt(durationsValue) / (int) DateUtils.SECOND_IN_MILLIS;
if (inexactSeek && currentDurationValue % 10 == 5) {
@@ -81,7 +81,7 @@ private void updateSeekOptions() {
res.getQuantityString(R.plurals.seconds,
currentDurationValue),
currentDurationValue));
- } catch (Resources.NotFoundException ignored) {
+ } catch (final Resources.NotFoundException ignored) {
// if this happens, the translation is missing,
// and the english string will be displayed instead
}
@@ -96,7 +96,7 @@ private void updateSeekOptions() {
final int newDuration = selectedDuration / (int) DateUtils.SECOND_IN_MILLIS + 5;
durations.setValue(Integer.toString(newDuration * (int) DateUtils.SECOND_IN_MILLIS));
- Toast toast = Toast
+ final Toast toast = Toast
.makeText(getContext(),
getString(R.string.new_seek_duration_toast, newDuration),
Toast.LENGTH_LONG);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
index 1b26cd529e9..44fe987ee69 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
@@ -173,7 +173,7 @@ private void initButton(final View rootView) {
return;
}
- Dialog.OnClickListener actionListener = (dialog, which) -> {
+ final Dialog.OnClickListener actionListener = (dialog, which) -> {
final ChooseTabListItem selected = availableTabs[which];
addTab(selected.tabId);
};
@@ -201,19 +201,19 @@ private void addTab(final int tabId) {
switch (type) {
case KIOSK:
- SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
+ final SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
selectKioskFragment.setOnSelectedListener((serviceId, kioskId, kioskName) ->
addTab(new Tab.KioskTab(serviceId, kioskId)));
selectKioskFragment.show(requireFragmentManager(), "select_kiosk");
return;
case CHANNEL:
- SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
+ final SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
selectChannelFragment.setOnSelectedListener((serviceId, url, name) ->
addTab(new Tab.ChannelTab(serviceId, url, name)));
selectChannelFragment.show(requireFragmentManager(), "select_channel");
return;
case PLAYLIST:
- SelectPlaylistFragment selectPlaylistFragment = new SelectPlaylistFragment();
+ final SelectPlaylistFragment selectPlaylistFragment = new SelectPlaylistFragment();
selectPlaylistFragment.setOnSelectedListener(
new SelectPlaylistFragment.OnSelectedListener() {
@Override
@@ -238,7 +238,7 @@ public void onRemotePlaylistSelected(
private ChooseTabListItem[] getAvailableTabs(final Context context) {
final ArrayList returnList = new ArrayList<>();
- for (Tab.Type type : Tab.Type.values()) {
+ for (final Tab.Type type : Tab.Type.values()) {
final Tab tab = type.getTab();
switch (type) {
case BLANK:
@@ -329,7 +329,7 @@ public boolean isItemViewSwipeEnabled() {
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) {
- int position = viewHolder.getAdapterPosition();
+ final int position = viewHolder.getAdapterPosition();
tabList.remove(position);
selectedTabsAdapter.notifyItemRemoved(position);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
index b0511cd1156..8e440c93df0 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
@@ -65,7 +65,7 @@ public static Tab from(final int tabId) {
@Nullable
public static Type typeFrom(final int tabId) {
- for (Type available : Type.values()) {
+ for (final Type available : Type.values()) {
if (available.getTabId() == tabId) {
return available;
}
@@ -481,7 +481,7 @@ private String getDefaultKioskId(final Context context) {
try {
final StreamingService service = NewPipe.getService(kioskServiceId);
kioskId = service.getKioskList().getDefaultKioskId();
- } catch (ExtractionException e) {
+ } catch (final ExtractionException e) {
ErrorActivity.reportError(context, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none",
"Loading default kiosk from selected service", 0));
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java
index d18aad9d37b..057ca50f0e2 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java
@@ -59,7 +59,7 @@ public static List getTabsFromJson(@Nullable final String tabsJson)
final JsonArray tabsArray = outerJsonObject.getArray(JSON_TABS_ARRAY_KEY);
- for (Object o : tabsArray) {
+ for (final Object o : tabsArray) {
if (!(o instanceof JsonObject)) {
continue;
}
@@ -70,7 +70,7 @@ public static List getTabsFromJson(@Nullable final String tabsJson)
returnTabs.add(tab);
}
}
- } catch (JsonParserException e) {
+ } catch (final JsonParserException e) {
throw new InvalidJsonException(e);
}
@@ -93,7 +93,7 @@ public static String getJsonToSave(@Nullable final List tabList) {
jsonWriter.array(JSON_TABS_ARRAY_KEY);
if (tabList != null) {
- for (Tab tab : tabList) {
+ for (final Tab tab : tabList) {
tab.writeJsonOn(jsonWriter);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
index c76df704796..316e3a8357b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java
@@ -30,7 +30,7 @@ public List getTabs() {
final String savedJson = sharedPreferences.getString(savedTabsKey, null);
try {
return TabsJsonHelper.getTabsFromJson(savedJson);
- } catch (TabsJsonHelper.InvalidJsonException e) {
+ } catch (final TabsJsonHelper.InvalidJsonException e) {
Toast.makeText(context, R.string.saved_tabs_invalid_json, Toast.LENGTH_SHORT).show();
return getDefaultTabs();
}
diff --git a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java
index dcd751e8182..b9d1026f08b 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java
@@ -70,7 +70,7 @@ public int readInt() throws IOException {
}
public long readUnsignedInt() throws IOException {
- long value = readInt();
+ final long value = readInt();
return value & 0xffffffffL;
}
@@ -82,8 +82,9 @@ public short readShort() throws IOException {
public long readLong() throws IOException {
primitiveRead(LONG_SIZE);
- long high = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3];
- long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7];
+ final long high
+ = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3];
+ final long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7];
return high << 32 | low;
}
@@ -114,7 +115,7 @@ public int read(final byte[] buffer, final int off, final int c) throws IOExcept
total += Math.max(stream.read(buffer, offset, count), 0);
} else {
while (count > 0 && !fillBuffer()) {
- int read = Math.min(readCount, count);
+ final int read = Math.min(readCount, count);
System.arraycopy(readBuffer, readOffset, buffer, offset, read);
readOffset += read;
@@ -169,7 +170,7 @@ public int read() throws IOException {
if (viewSize < 1) {
return -1;
}
- int res = DataReader.this.read();
+ final int res = DataReader.this.read();
if (res > 0) {
viewSize--;
}
@@ -188,7 +189,7 @@ public int read(final byte[] buffer, final int offset, final int count)
return -1;
}
- int res = DataReader.this.read(buffer, offset, Math.min(viewSize, count));
+ final int res = DataReader.this.read(buffer, offset, Math.min(viewSize, count));
viewSize -= res;
return res;
@@ -199,7 +200,7 @@ public long skip(final long amount) throws IOException {
if (viewSize < 1) {
return 0;
}
- int res = (int) DataReader.this.skipBytes(Math.min(amount, viewSize));
+ final int res = (int) DataReader.this.skipBytes(Math.min(amount, viewSize));
viewSize -= res;
return res;
@@ -230,8 +231,8 @@ public boolean markSupported() {
private final short[] primitive = new short[LONG_SIZE];
private void primitiveRead(final int amount) throws IOException {
- byte[] buffer = new byte[amount];
- int read = read(buffer, 0, amount);
+ final byte[] buffer = new byte[amount];
+ final int read = read(buffer, 0, amount);
if (read != amount) {
throw new EOFException("Truncated stream, missing "
diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java
index ff3aabd78c0..60390946b75 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java
@@ -116,7 +116,7 @@ public void parse() throws IOException, NoSuchElementException {
tracks[i].trak = moov.trak[i];
if (moov.mvexTrex != null) {
- for (Trex mvexTrex : moov.mvexTrex) {
+ for (final Trex mvexTrex : moov.mvexTrex) {
if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) {
tracks[i].trex = mvexTrex;
}
@@ -174,7 +174,7 @@ public Mp4Track[] getAvailableTracks() {
}
public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException {
- Mp4Track track = tracks[selectedTrack];
+ final Mp4Track track = tracks[selectedTrack];
while (stream.available()) {
@@ -233,7 +233,7 @@ public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException {
continue; // find another chunk
}
- Mp4DashChunk chunk = new Mp4DashChunk();
+ final Mp4DashChunk chunk = new Mp4DashChunk();
chunk.moof = moof;
if (!infoOnly) {
chunk.data = stream.getView(moof.traf.trun.chunkSize);
@@ -261,13 +261,13 @@ private String boxName(final Box ref) {
private String boxName(final int type) {
try {
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
- } catch (UnsupportedEncodingException e) {
+ } catch (final UnsupportedEncodingException e) {
return "0x" + Integer.toHexString(type);
}
}
private Box readBox() throws IOException {
- Box b = new Box();
+ final Box b = new Box();
b.offset = stream.position();
b.size = stream.readUnsignedInt();
b.type = stream.readInt();
@@ -280,7 +280,7 @@ private Box readBox() throws IOException {
}
private Box readBox(final int expected) throws IOException {
- Box b = readBox();
+ final Box b = readBox();
if (b.type != expected) {
throw new NoSuchElementException("expected " + boxName(expected)
+ " found " + boxName(b));
@@ -290,13 +290,13 @@ private Box readBox(final int expected) throws IOException {
private byte[] readFullBox(final Box ref) throws IOException {
// full box reading is limited to 2 GiB, and should be enough
- int size = (int) ref.size;
+ final int size = (int) ref.size;
- ByteBuffer buffer = ByteBuffer.allocate(size);
+ final ByteBuffer buffer = ByteBuffer.allocate(size);
buffer.putInt(size);
buffer.putInt(ref.type);
- int read = size - 8;
+ final int read = size - 8;
if (stream.read(buffer.array(), 8, read) != read) {
throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s",
@@ -307,7 +307,7 @@ private byte[] readFullBox(final Box ref) throws IOException {
}
private void ensure(final Box ref) throws IOException {
- long skip = ref.offset + ref.size - stream.position();
+ final long skip = ref.offset + ref.size - stream.position();
if (skip == 0) {
return;
@@ -325,7 +325,7 @@ private Box untilBox(final Box ref, final int... expected) throws IOException {
Box b;
while (stream.position() < (ref.offset + ref.size)) {
b = readBox();
- for (int type : expected) {
+ for (final int type : expected) {
if (b.type == type) {
return b;
}
@@ -345,7 +345,7 @@ private Box untilAnyBox(final Box ref) throws IOException {
}
private Moof parseMoof(final Box ref, final int trackId) throws IOException {
- Moof obj = new Moof();
+ final Moof obj = new Moof();
Box b = readBox(ATOM_MFHD);
obj.mfhdSequenceNumber = parseMfhd();
@@ -372,7 +372,7 @@ private int parseMfhd() throws IOException {
}
private Traf parseTraf(final Box ref, final int trackId) throws IOException {
- Traf traf = new Traf();
+ final Traf traf = new Traf();
Box b = readBox(ATOM_TFHD);
traf.tfhd = parseTfhd(trackId);
@@ -397,7 +397,7 @@ private Traf parseTraf(final Box ref, final int trackId) throws IOException {
}
private Tfhd parseTfhd(final int trackId) throws IOException {
- Tfhd obj = new Tfhd();
+ final Tfhd obj = new Tfhd();
obj.bFlags = stream.readInt();
obj.trackId = stream.readInt();
@@ -426,13 +426,13 @@ private Tfhd parseTfhd(final int trackId) throws IOException {
}
private long parseTfdt() throws IOException {
- int version = stream.read();
+ final int version = stream.read();
stream.skipBytes(3); // flags
return version == 0 ? stream.readUnsignedInt() : stream.readLong();
}
private Trun parseTrun() throws IOException {
- Trun obj = new Trun();
+ final Trun obj = new Trun();
obj.bFlags = stream.readInt();
obj.entryCount = stream.readInt(); // unsigned int
@@ -461,7 +461,7 @@ private Trun parseTrun() throws IOException {
stream.read(obj.bEntries);
for (int i = 0; i < obj.entryCount; i++) {
- TrunEntry entry = obj.getEntry(i);
+ final TrunEntry entry = obj.getEntry(i);
if (hasFlag(obj.bFlags, 0x0100)) {
obj.chunkDuration += entry.sampleDuration;
}
@@ -480,7 +480,7 @@ private Trun parseTrun() throws IOException {
private int[] parseFtyp(final Box ref) throws IOException {
int i = 0;
- int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)];
+ final int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)];
list[i++] = stream.readInt(); // major brand
@@ -494,14 +494,14 @@ private int[] parseFtyp(final Box ref) throws IOException {
}
private Mvhd parseMvhd() throws IOException {
- int version = stream.read();
+ final int version = stream.read();
stream.skipBytes(3); // flags
// creation entries_time
// modification entries_time
stream.skipBytes(2 * (version == 0 ? 4 : 8));
- Mvhd obj = new Mvhd();
+ final Mvhd obj = new Mvhd();
obj.timeScale = stream.readUnsignedInt();
// chunkDuration
@@ -520,9 +520,9 @@ private Mvhd parseMvhd() throws IOException {
}
private Tkhd parseTkhd() throws IOException {
- int version = stream.read();
+ final int version = stream.read();
- Tkhd obj = new Tkhd();
+ final Tkhd obj = new Tkhd();
// flags
// creation entries_time
@@ -553,7 +553,7 @@ private Tkhd parseTkhd() throws IOException {
}
private Trak parseTrak(final Box ref) throws IOException {
- Trak trak = new Trak();
+ final Trak trak = new Trak();
Box b = readBox(ATOM_TKHD);
trak.tkhd = parseTkhd();
@@ -576,7 +576,7 @@ private Trak parseTrak(final Box ref) throws IOException {
}
private Mdia parseMdia(final Box ref) throws IOException {
- Mdia obj = new Mdia();
+ final Mdia obj = new Mdia();
Box b;
while ((b = untilBox(ref, ATOM_MDHD, ATOM_HDLR, ATOM_MINF)) != null) {
@@ -585,8 +585,8 @@ private Mdia parseMdia(final Box ref) throws IOException {
obj.mdhd = readFullBox(b);
// read time scale
- ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
- byte version = buffer.get(8);
+ final ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
+ final byte version = buffer.get(8);
buffer.position(12 + ((version == 0 ? 4 : 8) * 2));
obj.mdhdTimeScale = buffer.getInt();
break;
@@ -608,7 +608,7 @@ private Hdlr parseHdlr(final Box ref) throws IOException {
// flags
stream.skipBytes(4);
- Hdlr obj = new Hdlr();
+ final Hdlr obj = new Hdlr();
obj.bReserved = new byte[12];
obj.type = stream.readInt();
@@ -623,11 +623,11 @@ private Hdlr parseHdlr(final Box ref) throws IOException {
private Moov parseMoov(final Box ref) throws IOException {
Box b = readBox(ATOM_MVHD);
- Moov moov = new Moov();
+ final Moov moov = new Moov();
moov.mvhd = parseMvhd();
ensure(b);
- ArrayList tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
+ final ArrayList tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
while ((b = untilBox(ref, ATOM_TRAK, ATOM_MVEX)) != null) {
switch (b.type) {
@@ -648,7 +648,7 @@ private Moov parseMoov(final Box ref) throws IOException {
}
private Trex[] parseMvex(final Box ref, final int possibleTrackCount) throws IOException {
- ArrayList tmp = new ArrayList<>(possibleTrackCount);
+ final ArrayList tmp = new ArrayList<>(possibleTrackCount);
Box b;
while ((b = untilBox(ref, ATOM_TREX)) != null) {
@@ -664,7 +664,7 @@ private Trex parseTrex() throws IOException {
// flags
stream.skipBytes(4);
- Trex obj = new Trex();
+ final Trex obj = new Trex();
obj.trackId = stream.readInt();
obj.defaultSampleDescriptionIndex = stream.readInt();
obj.defaultSampleDuration = stream.readInt();
@@ -675,17 +675,17 @@ private Trex parseTrex() throws IOException {
}
private Elst parseEdts(final Box ref) throws IOException {
- Box b = untilBox(ref, ATOM_ELST);
+ final Box b = untilBox(ref, ATOM_ELST);
if (b == null) {
return null;
}
- Elst obj = new Elst();
+ final Elst obj = new Elst();
- boolean v1 = stream.read() == 1;
+ final boolean v1 = stream.read() == 1;
stream.skipBytes(3); // flags
- int entryCount = stream.readInt();
+ final int entryCount = stream.readInt();
if (entryCount < 1) {
obj.bMediaRate = 0x00010000; // default media rate (1.0)
return obj;
@@ -707,7 +707,7 @@ private Elst parseEdts(final Box ref) throws IOException {
}
private Minf parseMinf(final Box ref) throws IOException {
- Minf obj = new Minf();
+ final Minf obj = new Minf();
Box b;
while ((b = untilAnyBox(ref)) != null) {
@@ -738,7 +738,7 @@ private Minf parseMinf(final Box ref) throws IOException {
* @return stsd box inside
*/
private byte[] parseStbl(final Box ref) throws IOException {
- Box b = untilBox(ref, ATOM_STSD);
+ final Box b = untilBox(ref, ATOM_STSD);
if (b == null) {
return new byte[0]; // this never should happens (missing codec startup data)
@@ -796,8 +796,8 @@ public class Trun {
int entriesRowSize;
public TrunEntry getEntry(final int i) {
- ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize);
- TrunEntry entry = new TrunEntry();
+ final ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize);
+ final TrunEntry entry = new TrunEntry();
if (hasFlag(bFlags, 0x0100)) {
entry.sampleDuration = buffer.getInt();
@@ -819,7 +819,7 @@ public TrunEntry getEntry(final int i) {
}
public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) {
- TrunEntry entry = getEntry(i);
+ final TrunEntry entry = getEntry(i);
if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) {
entry.sampleFlags = header.defaultSampleFlags;
@@ -928,7 +928,7 @@ public Mp4DashSample getNextSample() throws IOException {
return null;
}
- Mp4DashSample sample = new Mp4DashSample();
+ final Mp4DashSample sample = new Mp4DashSample();
sample.info = moof.traf.trun.getAbsoluteEntry(i++, moof.traf.tfhd);
sample.data = new byte[sample.info.sampleSize];
diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java
index 2baf8fe55ff..8f71c69342d 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java
@@ -51,7 +51,7 @@ public class Mp4FromDashWriter {
private final ArrayList compatibleBrands = new ArrayList<>(5);
public Mp4FromDashWriter(final SharpStream... sources) throws IOException {
- for (SharpStream src : sources) {
+ for (final SharpStream src : sources) {
if (!src.canRewind() && !src.canRead()) {
throw new IOException("All sources must be readable and allow rewind");
}
@@ -128,7 +128,7 @@ public void close() throws IOException {
done = true;
parsed = true;
- for (SharpStream src : sourceTracks) {
+ for (final SharpStream src : sourceTracks) {
src.close();
}
@@ -157,17 +157,17 @@ public void build(final SharpStream output) throws IOException {
outStream = output;
long read = 8; // mdat box header size
long totalSampleSize = 0;
- int[] sampleExtra = new int[readers.length];
- int[] defaultMediaTime = new int[readers.length];
- int[] defaultSampleDuration = new int[readers.length];
- int[] sampleCount = new int[readers.length];
+ final int[] sampleExtra = new int[readers.length];
+ final int[] defaultMediaTime = new int[readers.length];
+ final int[] defaultSampleDuration = new int[readers.length];
+ final int[] sampleCount = new int[readers.length];
- TablesInfo[] tablesInfo = new TablesInfo[tracks.length];
+ final TablesInfo[] tablesInfo = new TablesInfo[tracks.length];
for (int i = 0; i < tablesInfo.length; i++) {
tablesInfo[i] = new TablesInfo();
}
- int singleSampleBuffer;
+ final int singleSampleBuffer;
if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) {
// near 1 second of audio data per chunk, avoid split the audio stream in large chunks
singleSampleBuffer = tracks[0].trak.mdia.mdhdTimeScale / 1000;
@@ -250,10 +250,10 @@ public void build(final SharpStream output) throws IOException {
}
- boolean is64 = read > THRESHOLD_FOR_CO64;
+ final boolean is64 = read > THRESHOLD_FOR_CO64;
// calculate the moov size
- int auxSize = makeMoov(defaultMediaTime, tablesInfo, is64);
+ final int auxSize = makeMoov(defaultMediaTime, tablesInfo, is64);
if (auxSize < THRESHOLD_MOOV_LENGTH) {
auxBuffer = ByteBuffer.allocate(auxSize); // cache moov in the memory
@@ -267,9 +267,9 @@ public void build(final SharpStream output) throws IOException {
// reserve moov space in the output stream
if (auxSize > 0) {
int length = auxSize;
- byte[] buffer = new byte[64 * 1024]; // 64 KiB
+ final byte[] buffer = new byte[64 * 1024]; // 64 KiB
while (length > 0) {
- int count = Math.min(length, buffer.length);
+ final int count = Math.min(length, buffer.length);
outWrite(buffer, count);
length -= count;
}
@@ -305,9 +305,10 @@ public void build(final SharpStream output) throws IOException {
outWrite(makeMdat(totalSampleSize, is64));
- int[] sampleIndex = new int[readers.length];
- int[] sizes = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
- int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
+ final int[] sampleIndex = new int[readers.length];
+ final int[] sizes
+ = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
+ final int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
int written = readers.length;
while (written > 0) {
@@ -318,9 +319,9 @@ public void build(final SharpStream output) throws IOException {
continue; // track is done
}
- long chunkOffset = writeOffset;
+ final long chunkOffset = writeOffset;
int syncCount = 0;
- int limit;
+ final int limit;
if (singleSampleBuffer > 0) {
limit = singleSampleBuffer;
} else {
@@ -329,7 +330,7 @@ public void build(final SharpStream output) throws IOException {
int j = 0;
for (; j < limit; j++) {
- Mp4DashSample sample = getNextSample(i);
+ final Mp4DashSample sample = getNextSample(i);
if (sample == null) {
if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) {
@@ -409,7 +410,7 @@ private Mp4DashSample getNextSample(final int track) throws IOException {
}
}
- Mp4DashSample sample = readersChunks[track].getNextSample();
+ final Mp4DashSample sample = readersChunks[track].getNextSample();
if (sample == null) {
readersChunks[track] = null;
return getNextSample(track);
@@ -434,8 +435,8 @@ private int writeEntryArray(final int offset, final int count, final int... valu
auxSeek(offset);
- int size = count * 4;
- ByteBuffer buffer = ByteBuffer.allocate(size);
+ final int size = count * 4;
+ final ByteBuffer buffer = ByteBuffer.allocate(size);
for (int i = 0; i < count; i++) {
buffer.putInt(values[i]);
@@ -466,10 +467,10 @@ private void outRestore() throws IOException {
private void initChunkTables(final TablesInfo tables, final int firstCount,
final int successiveCount) {
// tables.stsz holds amount of samples of the track (total)
- int totalSamples = (tables.stsz - firstCount);
- float chunkAmount = totalSamples / (float) successiveCount;
- int remainChunkOffset = (int) Math.ceil(chunkAmount);
- boolean remain = remainChunkOffset != (int) chunkAmount;
+ final int totalSamples = (tables.stsz - firstCount);
+ final float chunkAmount = totalSamples / (float) successiveCount;
+ final int remainChunkOffset = (int) Math.ceil(chunkAmount);
+ final boolean remain = remainChunkOffset != (int) chunkAmount;
int index = 0;
tables.stsc = 1;
@@ -529,7 +530,7 @@ private void outSkip(final long amount) throws IOException {
}
private int lengthFor(final int offset) throws IOException {
- int size = auxOffset() - offset;
+ final int size = auxOffset() - offset;
if (moovSimulation) {
return size;
@@ -545,7 +546,7 @@ private int lengthFor(final int offset) throws IOException {
private int make(final int type, final int extra, final int columns, final int rows)
throws IOException {
final byte base = 16;
- int size = columns * rows * 4;
+ final int size = columns * rows * 4;
int total = size + base;
int offset = auxOffset();
@@ -618,7 +619,7 @@ private int makeFtyp() throws IOException {
size += 4;
}
- ByteBuffer buffer = ByteBuffer.allocate(size);
+ final ByteBuffer buffer = ByteBuffer.allocate(size);
buffer.putInt(size);
buffer.putInt(0x66747970); // "ftyp"
@@ -631,7 +632,7 @@ private int makeFtyp() throws IOException {
buffer.putInt(0x6D703432); // "mp42" compatible brand
}
- for (Integer brand : compatibleBrands) {
+ for (final Integer brand : compatibleBrands) {
buffer.putInt(brand); // compatible brand
}
@@ -648,7 +649,7 @@ private byte[] makeMdat(final long refSize, final boolean is64) {
size += 8;
}
- ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
+ final ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
.putInt(is64 ? 0x01 : (int) size)
.putInt(0x6D646174); // mdat
@@ -689,14 +690,14 @@ private void makeMvhd(final long longestTrack) throws IOException {
private int makeMoov(final int[] defaultMediaTime, final TablesInfo[] tablesInfo,
final boolean is64) throws RuntimeException, IOException {
- int start = auxOffset();
+ final int start = auxOffset();
auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x00, 0x6D, 0x6F, 0x6F, 0x76
});
long longestTrack = 0;
- long[] durations = new long[tracks.length];
+ final long[] durations = new long[tracks.length];
for (int i = 0; i < durations.length; i++) {
durations[i] = (long) Math.ceil(
@@ -723,7 +724,7 @@ private int makeMoov(final int[] defaultMediaTime, final TablesInfo[] tablesInfo
private void makeTrak(final int index, final long duration, final int defaultMediaTime,
final TablesInfo tables, final boolean is64) throws IOException {
- int start = auxOffset();
+ final int start = auxOffset();
auxWrite(new byte[]{
// trak header
@@ -732,7 +733,7 @@ private void makeTrak(final int index, final long duration, final int defaultMed
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03
});
- ByteBuffer buffer = ByteBuffer.allocate(48);
+ final ByteBuffer buffer = ByteBuffer.allocate(48);
buffer.putLong(time);
buffer.putLong(time);
buffer.putInt(index + 1);
@@ -757,8 +758,8 @@ private void makeTrak(final int index, final long duration, final int defaultMed
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 // elst header
});
- int bMediaRate;
- int mediaTime;
+ final int bMediaRate;
+ final int mediaTime;
if (tracks[index].trak.edstElst == null) {
// is a audio track ¿is edst/elst optional for audio tracks?
@@ -784,17 +785,17 @@ private void makeTrak(final int index, final long duration, final int defaultMed
private void makeMdia(final Mdia mdia, final TablesInfo tablesInfo, final boolean is64,
final boolean isAudio) throws IOException {
- int startMdia = auxOffset();
+ final int startMdia = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61}); // mdia
auxWrite(mdia.mdhd);
auxWrite(makeHdlr(mdia.hdlr));
- int startMinf = auxOffset();
+ final int startMinf = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66}); // minf
auxWrite(mdia.minf.mhd);
auxWrite(mdia.minf.dinf);
- int startStbl = auxOffset();
+ final int startStbl = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C}); // stbl
auxWrite(mdia.minf.stblStsd);
@@ -838,7 +839,7 @@ private void makeMdia(final Mdia mdia, final TablesInfo tablesInfo, final boolea
}
private byte[] makeHdlr(final Hdlr hdlr) {
- ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
+ final ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, // hdlr
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -854,7 +855,7 @@ private byte[] makeHdlr(final Hdlr hdlr) {
}
private int makeSbgp() throws IOException {
- int offset = auxOffset();
+ final int offset = auxOffset();
auxWrite(new byte[] {
0x00, 0x00, 0x00, 0x1C, // box size
@@ -883,7 +884,7 @@ private byte[] makeSgpd() {
* most of m4a encoders and ffmpeg uses this box with dummy values (same values)
*/
- ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
+ final ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
0x00, 0x00, 0x00, 0x1A, // box size
0x73, 0x67, 0x70, 0x64, // "sgpd"
0x01, 0x00, 0x00, 0x00, // box flags (unknown flag sets)
diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java
index 00a29c7ab3b..44104f133de 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java
@@ -1,416 +1,416 @@
-package org.schabi.newpipe.streams;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.schabi.newpipe.streams.WebMReader.Cluster;
-import org.schabi.newpipe.streams.WebMReader.Segment;
-import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
-import org.schabi.newpipe.streams.WebMReader.WebMTrack;
-import org.schabi.newpipe.streams.io.SharpStream;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * @author kapodamy
- */
-public class OggFromWebMWriter implements Closeable {
- private static final byte FLAG_UNSET = 0x00;
- //private static final byte FLAG_CONTINUED = 0x01;
- private static final byte FLAG_FIRST = 0x02;
- private static final byte FLAG_LAST = 0x04;
-
- private static final byte HEADER_CHECKSUM_OFFSET = 22;
- private static final byte HEADER_SIZE = 27;
-
- private static final int TIME_SCALE_NS = 1000000000;
-
- private boolean done = false;
- private boolean parsed = false;
-
- private SharpStream source;
- private SharpStream output;
-
- private int sequenceCount = 0;
- private final int streamId;
- private byte packetFlag = FLAG_FIRST;
-
- private WebMReader webm = null;
- private WebMTrack webmTrack = null;
- private Segment webmSegment = null;
- private Cluster webmCluster = null;
- private SimpleBlock webmBlock = null;
-
- private long webmBlockLastTimecode = 0;
- private long webmBlockNearDuration = 0;
-
- private short segmentTableSize = 0;
- private final byte[] segmentTable = new byte[255];
- private long segmentTableNextTimestamp = TIME_SCALE_NS;
-
- private final int[] crc32Table = new int[256];
-
- public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
- if (!source.canRead() || !source.canRewind()) {
- throw new IllegalArgumentException("source stream must be readable and allows seeking");
- }
- if (!target.canWrite() || !target.canRewind()) {
- throw new IllegalArgumentException("output stream must be writable and allows seeking");
- }
-
- this.source = source;
- this.output = target;
-
- this.streamId = (int) System.currentTimeMillis();
-
- populateCrc32Table();
- }
-
- public boolean isDone() {
- return done;
- }
-
- public boolean isParsed() {
- return parsed;
- }
-
- public WebMTrack[] getTracksFromSource() throws IllegalStateException {
- if (!parsed) {
- throw new IllegalStateException("source must be parsed first");
- }
-
- return webm.getAvailableTracks();
- }
-
- public void parseSource() throws IOException, IllegalStateException {
- if (done) {
- throw new IllegalStateException("already done");
- }
- if (parsed) {
- throw new IllegalStateException("already parsed");
- }
-
- try {
- webm = new WebMReader(source);
- webm.parse();
- webmSegment = webm.getNextSegment();
- } finally {
- parsed = true;
- }
- }
-
- public void selectTrack(final int trackIndex) throws IOException {
- if (!parsed) {
- throw new IllegalStateException("source must be parsed first");
- }
- if (done) {
- throw new IOException("already done");
- }
- if (webmTrack != null) {
- throw new IOException("tracks already selected");
- }
-
- switch (webm.getAvailableTracks()[trackIndex].kind) {
- case Audio:
- case Video:
- break;
- default:
- throw new UnsupportedOperationException("the track must an audio or video stream");
- }
-
- try {
- webmTrack = webm.selectTrack(trackIndex);
- } finally {
- parsed = true;
- }
- }
-
- @Override
- public void close() throws IOException {
- done = true;
- parsed = true;
-
- webmTrack = null;
- webm = null;
-
- if (!output.isClosed()) {
- output.flush();
- }
-
- source.close();
- output.close();
- }
-
- public void build() throws IOException {
- float resolution;
- SimpleBlock bloq;
- ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
- ByteBuffer page = ByteBuffer.allocate(64 * 1024);
-
- header.order(ByteOrder.LITTLE_ENDIAN);
-
- /* step 1: get the amount of frames per seconds */
- switch (webmTrack.kind) {
- case Audio:
- resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
- if (resolution == 0f) {
- throw new RuntimeException("cannot get the audio sample rate");
- }
- break;
- case Video:
- // WARNING: untested
- if (webmTrack.defaultDuration == 0) {
- throw new RuntimeException("missing default frame time");
- }
- resolution = 1000f / ((float) webmTrack.defaultDuration
- / webmSegment.info.timecodeScale);
- break;
- default:
- throw new RuntimeException("not implemented");
- }
-
- /* step 2: create packet with code init data */
- if (webmTrack.codecPrivate != null) {
- addPacketSegment(webmTrack.codecPrivate.length);
- makePacketheader(0x00, header, webmTrack.codecPrivate);
- write(header);
- output.write(webmTrack.codecPrivate);
- }
-
- /* step 3: create packet with metadata */
- byte[] buffer = makeMetadata();
- if (buffer != null) {
- addPacketSegment(buffer.length);
- makePacketheader(0x00, header, buffer);
- write(header);
- output.write(buffer);
- }
-
- /* step 4: calculate amount of packets */
- while (webmSegment != null) {
- bloq = getNextBlock();
-
- if (bloq != null && addPacketSegment(bloq)) {
- int pos = page.position();
- //noinspection ResultOfMethodCallIgnored
- bloq.data.read(page.array(), pos, bloq.dataSize);
- page.position(pos + bloq.dataSize);
- continue;
- }
-
- // calculate the current packet duration using the next block
- double elapsedNs = webmTrack.codecDelay;
-
- if (bloq == null) {
- packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
- elapsedNs += webmBlockLastTimecode;
-
- if (webmTrack.defaultDuration > 0) {
- elapsedNs += webmTrack.defaultDuration;
- } else {
- // hardcoded way, guess the sample duration
- elapsedNs += webmBlockNearDuration;
- }
- } else {
- elapsedNs += bloq.absoluteTimeCodeNs;
- }
-
- // get the sample count in the page
- elapsedNs = elapsedNs / TIME_SCALE_NS;
- elapsedNs = Math.ceil(elapsedNs * resolution);
-
- // create header and calculate page checksum
- int checksum = makePacketheader((long) elapsedNs, header, null);
- checksum = calcCrc32(checksum, page.array(), page.position());
-
- header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
-
- // dump data
- write(header);
- write(page);
-
- webmBlock = bloq;
- }
- }
-
- private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
- final byte[] immediatePage) {
- short length = HEADER_SIZE;
-
- buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
- buffer.put((byte) 0x00); // version
- buffer.put(packetFlag); // type
-
- buffer.putLong(granPos); // granulate position
-
- buffer.putInt(streamId); // bitstream serial number
- buffer.putInt(sequenceCount++); // page sequence number
-
- buffer.putInt(0x00); // page checksum
-
- buffer.put((byte) segmentTableSize); // segment table
- buffer.put(segmentTable, 0, segmentTableSize); // segment size
-
- length += segmentTableSize;
-
- clearSegmentTable(); // clear segment table for next header
-
- int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
-
- if (immediatePage != null) {
- checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
- buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
- segmentTableNextTimestamp -= TIME_SCALE_NS;
- }
-
- return checksumCrc32;
- }
-
- @Nullable
- private byte[] makeMetadata() {
- if ("A_OPUS".equals(webmTrack.codecId)) {
- return new byte[]{
- 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
- 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
- 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
- };
- } else if ("A_VORBIS".equals(webmTrack.codecId)) {
- return new byte[]{
- 0x03, // ¿¿¿???
- 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
- 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
- 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
- };
- }
-
- // not implemented for the desired codec
- return null;
- }
-
- private void write(final ByteBuffer buffer) throws IOException {
- output.write(buffer.array(), 0, buffer.position());
- buffer.position(0);
- }
-
- @Nullable
- private SimpleBlock getNextBlock() throws IOException {
- SimpleBlock res;
-
- if (webmBlock != null) {
- res = webmBlock;
- webmBlock = null;
- return res;
- }
-
- if (webmSegment == null) {
- webmSegment = webm.getNextSegment();
- if (webmSegment == null) {
- return null; // no more blocks in the selected track
- }
- }
-
- if (webmCluster == null) {
- webmCluster = webmSegment.getNextCluster();
- if (webmCluster == null) {
- webmSegment = null;
- return getNextBlock();
- }
- }
-
- res = webmCluster.getNextSimpleBlock();
- if (res == null) {
- webmCluster = null;
- return getNextBlock();
- }
-
- webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
- webmBlockLastTimecode = res.absoluteTimeCodeNs;
-
- return res;
- }
-
- private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
- // hardcoded way
- ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
-
- while (buffer.remaining() >= 6) {
- int id = buffer.getShort() & 0xFFFF;
- if (id == 0x0000B584) {
- return buffer.getFloat();
- }
- }
-
- return 0f;
- }
-
- private void clearSegmentTable() {
- segmentTableNextTimestamp += TIME_SCALE_NS;
- packetFlag = FLAG_UNSET;
- segmentTableSize = 0;
- }
-
- private boolean addPacketSegment(final SimpleBlock block) {
- long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
-
- if (timestamp >= segmentTableNextTimestamp) {
- return false;
- }
-
- return addPacketSegment(block.dataSize);
- }
-
- private boolean addPacketSegment(final int size) {
- if (size > 65025) {
- throw new UnsupportedOperationException("page size cannot be larger than 65025");
- }
-
- int available = (segmentTable.length - segmentTableSize) * 255;
- boolean extra = (size % 255) == 0;
-
- if (extra) {
- // add a zero byte entry in the table
- // required to indicate the sample size is multiple of 255
- available -= 255;
- }
-
- // check if possible add the segment, without overflow the table
- if (available < size) {
- return false; // not enough space on the page
- }
-
- for (int seg = size; seg > 0; seg -= 255) {
- segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255);
- }
-
- if (extra) {
- segmentTable[segmentTableSize++] = 0x00;
- }
-
- return true;
- }
-
- private void populateCrc32Table() {
- for (int i = 0; i < 0x100; i++) {
- int crc = i << 24;
- for (int j = 0; j < 8; j++) {
- long b = crc >>> 31;
- crc <<= 1;
- crc ^= (int) (0x100000000L - b) & 0x04c11db7;
- }
- crc32Table[i] = crc;
- }
- }
-
- private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) {
- int crc = initialCrc;
- for (int i = 0; i < size; i++) {
- int reg = (crc >>> 24) & 0xff;
- crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
- }
-
- return crc;
- }
-}
+package org.schabi.newpipe.streams;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.schabi.newpipe.streams.WebMReader.Cluster;
+import org.schabi.newpipe.streams.WebMReader.Segment;
+import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
+import org.schabi.newpipe.streams.WebMReader.WebMTrack;
+import org.schabi.newpipe.streams.io.SharpStream;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @author kapodamy
+ */
+public class OggFromWebMWriter implements Closeable {
+ private static final byte FLAG_UNSET = 0x00;
+ //private static final byte FLAG_CONTINUED = 0x01;
+ private static final byte FLAG_FIRST = 0x02;
+ private static final byte FLAG_LAST = 0x04;
+
+ private static final byte HEADER_CHECKSUM_OFFSET = 22;
+ private static final byte HEADER_SIZE = 27;
+
+ private static final int TIME_SCALE_NS = 1000000000;
+
+ private boolean done = false;
+ private boolean parsed = false;
+
+ private SharpStream source;
+ private SharpStream output;
+
+ private int sequenceCount = 0;
+ private final int streamId;
+ private byte packetFlag = FLAG_FIRST;
+
+ private WebMReader webm = null;
+ private WebMTrack webmTrack = null;
+ private Segment webmSegment = null;
+ private Cluster webmCluster = null;
+ private SimpleBlock webmBlock = null;
+
+ private long webmBlockLastTimecode = 0;
+ private long webmBlockNearDuration = 0;
+
+ private short segmentTableSize = 0;
+ private final byte[] segmentTable = new byte[255];
+ private long segmentTableNextTimestamp = TIME_SCALE_NS;
+
+ private final int[] crc32Table = new int[256];
+
+ public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
+ if (!source.canRead() || !source.canRewind()) {
+ throw new IllegalArgumentException("source stream must be readable and allows seeking");
+ }
+ if (!target.canWrite() || !target.canRewind()) {
+ throw new IllegalArgumentException("output stream must be writable and allows seeking");
+ }
+
+ this.source = source;
+ this.output = target;
+
+ this.streamId = (int) System.currentTimeMillis();
+
+ populateCrc32Table();
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+
+ public boolean isParsed() {
+ return parsed;
+ }
+
+ public WebMTrack[] getTracksFromSource() throws IllegalStateException {
+ if (!parsed) {
+ throw new IllegalStateException("source must be parsed first");
+ }
+
+ return webm.getAvailableTracks();
+ }
+
+ public void parseSource() throws IOException, IllegalStateException {
+ if (done) {
+ throw new IllegalStateException("already done");
+ }
+ if (parsed) {
+ throw new IllegalStateException("already parsed");
+ }
+
+ try {
+ webm = new WebMReader(source);
+ webm.parse();
+ webmSegment = webm.getNextSegment();
+ } finally {
+ parsed = true;
+ }
+ }
+
+ public void selectTrack(final int trackIndex) throws IOException {
+ if (!parsed) {
+ throw new IllegalStateException("source must be parsed first");
+ }
+ if (done) {
+ throw new IOException("already done");
+ }
+ if (webmTrack != null) {
+ throw new IOException("tracks already selected");
+ }
+
+ switch (webm.getAvailableTracks()[trackIndex].kind) {
+ case Audio:
+ case Video:
+ break;
+ default:
+ throw new UnsupportedOperationException("the track must an audio or video stream");
+ }
+
+ try {
+ webmTrack = webm.selectTrack(trackIndex);
+ } finally {
+ parsed = true;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ done = true;
+ parsed = true;
+
+ webmTrack = null;
+ webm = null;
+
+ if (!output.isClosed()) {
+ output.flush();
+ }
+
+ source.close();
+ output.close();
+ }
+
+ public void build() throws IOException {
+ final float resolution;
+ SimpleBlock bloq;
+ final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
+ final ByteBuffer page = ByteBuffer.allocate(64 * 1024);
+
+ header.order(ByteOrder.LITTLE_ENDIAN);
+
+ /* step 1: get the amount of frames per seconds */
+ switch (webmTrack.kind) {
+ case Audio:
+ resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
+ if (resolution == 0f) {
+ throw new RuntimeException("cannot get the audio sample rate");
+ }
+ break;
+ case Video:
+ // WARNING: untested
+ if (webmTrack.defaultDuration == 0) {
+ throw new RuntimeException("missing default frame time");
+ }
+ resolution = 1000f / ((float) webmTrack.defaultDuration
+ / webmSegment.info.timecodeScale);
+ break;
+ default:
+ throw new RuntimeException("not implemented");
+ }
+
+ /* step 2: create packet with code init data */
+ if (webmTrack.codecPrivate != null) {
+ addPacketSegment(webmTrack.codecPrivate.length);
+ makePacketheader(0x00, header, webmTrack.codecPrivate);
+ write(header);
+ output.write(webmTrack.codecPrivate);
+ }
+
+ /* step 3: create packet with metadata */
+ final byte[] buffer = makeMetadata();
+ if (buffer != null) {
+ addPacketSegment(buffer.length);
+ makePacketheader(0x00, header, buffer);
+ write(header);
+ output.write(buffer);
+ }
+
+ /* step 4: calculate amount of packets */
+ while (webmSegment != null) {
+ bloq = getNextBlock();
+
+ if (bloq != null && addPacketSegment(bloq)) {
+ final int pos = page.position();
+ //noinspection ResultOfMethodCallIgnored
+ bloq.data.read(page.array(), pos, bloq.dataSize);
+ page.position(pos + bloq.dataSize);
+ continue;
+ }
+
+ // calculate the current packet duration using the next block
+ double elapsedNs = webmTrack.codecDelay;
+
+ if (bloq == null) {
+ packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
+ elapsedNs += webmBlockLastTimecode;
+
+ if (webmTrack.defaultDuration > 0) {
+ elapsedNs += webmTrack.defaultDuration;
+ } else {
+ // hardcoded way, guess the sample duration
+ elapsedNs += webmBlockNearDuration;
+ }
+ } else {
+ elapsedNs += bloq.absoluteTimeCodeNs;
+ }
+
+ // get the sample count in the page
+ elapsedNs = elapsedNs / TIME_SCALE_NS;
+ elapsedNs = Math.ceil(elapsedNs * resolution);
+
+ // create header and calculate page checksum
+ int checksum = makePacketheader((long) elapsedNs, header, null);
+ checksum = calcCrc32(checksum, page.array(), page.position());
+
+ header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
+
+ // dump data
+ write(header);
+ write(page);
+
+ webmBlock = bloq;
+ }
+ }
+
+ private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
+ final byte[] immediatePage) {
+ short length = HEADER_SIZE;
+
+ buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
+ buffer.put((byte) 0x00); // version
+ buffer.put(packetFlag); // type
+
+ buffer.putLong(granPos); // granulate position
+
+ buffer.putInt(streamId); // bitstream serial number
+ buffer.putInt(sequenceCount++); // page sequence number
+
+ buffer.putInt(0x00); // page checksum
+
+ buffer.put((byte) segmentTableSize); // segment table
+ buffer.put(segmentTable, 0, segmentTableSize); // segment size
+
+ length += segmentTableSize;
+
+ clearSegmentTable(); // clear segment table for next header
+
+ int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
+
+ if (immediatePage != null) {
+ checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
+ buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
+ segmentTableNextTimestamp -= TIME_SCALE_NS;
+ }
+
+ return checksumCrc32;
+ }
+
+ @Nullable
+ private byte[] makeMetadata() {
+ if ("A_OPUS".equals(webmTrack.codecId)) {
+ return new byte[]{
+ 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
+ 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
+ 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
+ };
+ } else if ("A_VORBIS".equals(webmTrack.codecId)) {
+ return new byte[]{
+ 0x03, // ¿¿¿???
+ 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
+ 0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
+ 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
+ };
+ }
+
+ // not implemented for the desired codec
+ return null;
+ }
+
+ private void write(final ByteBuffer buffer) throws IOException {
+ output.write(buffer.array(), 0, buffer.position());
+ buffer.position(0);
+ }
+
+ @Nullable
+ private SimpleBlock getNextBlock() throws IOException {
+ SimpleBlock res;
+
+ if (webmBlock != null) {
+ res = webmBlock;
+ webmBlock = null;
+ return res;
+ }
+
+ if (webmSegment == null) {
+ webmSegment = webm.getNextSegment();
+ if (webmSegment == null) {
+ return null; // no more blocks in the selected track
+ }
+ }
+
+ if (webmCluster == null) {
+ webmCluster = webmSegment.getNextCluster();
+ if (webmCluster == null) {
+ webmSegment = null;
+ return getNextBlock();
+ }
+ }
+
+ res = webmCluster.getNextSimpleBlock();
+ if (res == null) {
+ webmCluster = null;
+ return getNextBlock();
+ }
+
+ webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
+ webmBlockLastTimecode = res.absoluteTimeCodeNs;
+
+ return res;
+ }
+
+ private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
+ // hardcoded way
+ final ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
+
+ while (buffer.remaining() >= 6) {
+ final int id = buffer.getShort() & 0xFFFF;
+ if (id == 0x0000B584) {
+ return buffer.getFloat();
+ }
+ }
+
+ return 0.0f;
+ }
+
+ private void clearSegmentTable() {
+ segmentTableNextTimestamp += TIME_SCALE_NS;
+ packetFlag = FLAG_UNSET;
+ segmentTableSize = 0;
+ }
+
+ private boolean addPacketSegment(final SimpleBlock block) {
+ final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
+
+ if (timestamp >= segmentTableNextTimestamp) {
+ return false;
+ }
+
+ return addPacketSegment(block.dataSize);
+ }
+
+ private boolean addPacketSegment(final int size) {
+ if (size > 65025) {
+ throw new UnsupportedOperationException("page size cannot be larger than 65025");
+ }
+
+ int available = (segmentTable.length - segmentTableSize) * 255;
+ final boolean extra = (size % 255) == 0;
+
+ if (extra) {
+ // add a zero byte entry in the table
+ // required to indicate the sample size is multiple of 255
+ available -= 255;
+ }
+
+ // check if possible add the segment, without overflow the table
+ if (available < size) {
+ return false; // not enough space on the page
+ }
+
+ for (int seg = size; seg > 0; seg -= 255) {
+ segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255);
+ }
+
+ if (extra) {
+ segmentTable[segmentTableSize++] = 0x00;
+ }
+
+ return true;
+ }
+
+ private void populateCrc32Table() {
+ for (int i = 0; i < 0x100; i++) {
+ int crc = i << 24;
+ for (int j = 0; j < 8; j++) {
+ final long b = crc >>> 31;
+ crc <<= 1;
+ crc ^= (int) (0x100000000L - b) & 0x04c11db7;
+ }
+ crc32Table[i] = crc;
+ }
+ }
+
+ private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) {
+ int crc = initialCrc;
+ for (int i = 0; i < size; i++) {
+ final int reg = (crc >>> 24) & 0xff;
+ crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
+ }
+
+ return crc;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java b/app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
index eddb951e59e..8cb31141bef 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
@@ -65,23 +65,23 @@ public void build(final SharpStream ttml) throws IOException {
*/
// parse XML
- byte[] buffer = new byte[(int) ttml.available()];
+ final byte[] buffer = new byte[(int) ttml.available()];
ttml.read(buffer);
- Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "",
+ final Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "",
Parser.xmlParser());
- StringBuilder text = new StringBuilder(128);
- Elements paragraphList = doc.select("body > div > p");
+ final StringBuilder text = new StringBuilder(128);
+ final Elements paragraphList = doc.select("body > div > p");
// check if has frames
if (paragraphList.size() < 1) {
return;
}
- for (Element paragraph : paragraphList) {
+ for (final Element paragraph : paragraphList) {
text.setLength(0);
- for (Node children : paragraph.childNodes()) {
+ for (final Node children : paragraph.childNodes()) {
if (children instanceof TextNode) {
text.append(((TextNode) children).text());
} else if (children instanceof Element
@@ -94,8 +94,8 @@ public void build(final SharpStream ttml) throws IOException {
continue;
}
- String begin = getTimestamp(paragraph, "begin");
- String end = getTimestamp(paragraph, "end");
+ final String begin = getTimestamp(paragraph, "begin");
+ final String end = getTimestamp(paragraph, "end");
writeFrame(begin, end, text);
}
diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java
index 56cea9f2d8c..6e2ba8360bd 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java
@@ -99,7 +99,7 @@ public Segment getNextSegment() throws IOException {
ensure(segment.ref);
// WARNING: track cannot be the same or have different index in new segments
- Element elem = untilElement(null, ID_SEGMENT);
+ final Element elem = untilElement(null, ID_SEGMENT);
if (elem == null) {
done = true;
return null;
@@ -113,7 +113,7 @@ private long readNumber(final Element parent) throws IOException {
int length = (int) parent.contentSize;
long value = 0;
while (length-- > 0) {
- int read = stream.read();
+ final int read = stream.read();
if (read == -1) {
throw new EOFException();
}
@@ -127,9 +127,9 @@ private String readString(final Element parent) throws IOException {
}
private byte[] readBlob(final Element parent) throws IOException {
- long length = parent.contentSize;
- byte[] buffer = new byte[(int) length];
- int read = stream.read(buffer);
+ final long length = parent.contentSize;
+ final byte[] buffer = new byte[(int) length];
+ final int read = stream.read(buffer);
if (read < length) {
throw new EOFException();
}
@@ -168,7 +168,7 @@ private long readEncodedNumber() throws IOException {
}
private Element readElement() throws IOException {
- Element elem = new Element();
+ final Element elem = new Element();
elem.offset = stream.position();
elem.type = (int) readEncodedNumber();
elem.contentSize = readEncodedNumber();
@@ -178,7 +178,7 @@ private Element readElement() throws IOException {
}
private Element readElement(final int expected) throws IOException {
- Element elem = readElement();
+ final Element elem = readElement();
if (expected != 0 && elem.type != expected) {
throw new NoSuchElementException("expected " + elementID(expected)
+ " found " + elementID(elem.type));
@@ -194,7 +194,7 @@ private Element untilElement(final Element ref, final int... expected) throws IO
if (expected.length < 1) {
return elem;
}
- for (int type : expected) {
+ for (final int type : expected) {
if (elem.type == type) {
return elem;
}
@@ -211,7 +211,7 @@ private String elementID(final long type) {
}
private void ensure(final Element ref) throws IOException {
- long skip = (ref.offset + ref.size) - stream.position();
+ final long skip = (ref.offset + ref.size) - stream.position();
if (skip == 0) {
return;
@@ -249,7 +249,7 @@ private boolean readEbml(final Element ref, final int minReadVersion,
private Info readInfo(final Element ref) throws IOException {
Element elem;
- Info info = new Info();
+ final Info info = new Info();
while ((elem = untilElement(ref, ID_TIMECODE_SCALE, ID_DURATION)) != null) {
switch (elem.type) {
@@ -272,7 +272,7 @@ private Info readInfo(final Element ref) throws IOException {
private Segment readSegment(final Element ref, final int trackLacingExpected,
final boolean metadataExpected) throws IOException {
- Segment obj = new Segment(ref);
+ final Segment obj = new Segment(ref);
Element elem;
while ((elem = untilElement(ref, ID_INFO, ID_TRACKS, ID_CLUSTER)) != null) {
if (elem.type == ID_CLUSTER) {
@@ -300,11 +300,11 @@ private Segment readSegment(final Element ref, final int trackLacingExpected,
}
private WebMTrack[] readTracks(final Element ref, final int lacingExpected) throws IOException {
- ArrayList trackEntries = new ArrayList<>(2);
+ final ArrayList trackEntries = new ArrayList<>(2);
Element elemTrackEntry;
while ((elemTrackEntry = untilElement(ref, ID_TRACK_ENTRY)) != null) {
- WebMTrack entry = new WebMTrack();
+ final WebMTrack entry = new WebMTrack();
boolean drop = false;
Element elem;
while ((elem = untilElement(elemTrackEntry)) != null) {
@@ -348,10 +348,10 @@ private WebMTrack[] readTracks(final Element ref, final int lacingExpected) thro
ensure(elemTrackEntry);
}
- WebMTrack[] entries = new WebMTrack[trackEntries.size()];
+ final WebMTrack[] entries = new WebMTrack[trackEntries.size()];
trackEntries.toArray(entries);
- for (WebMTrack entry : entries) {
+ for (final WebMTrack entry : entries) {
switch (entry.trackType) {
case 1:
entry.kind = TrackKind.Video;
@@ -369,7 +369,7 @@ private WebMTrack[] readTracks(final Element ref, final int lacingExpected) thro
}
private SimpleBlock readSimpleBlock(final Element ref) throws IOException {
- SimpleBlock obj = new SimpleBlock(ref);
+ final SimpleBlock obj = new SimpleBlock(ref);
obj.trackNumber = readEncodedNumber();
obj.relativeTimeCode = stream.readShort();
obj.flags = (byte) stream.read();
@@ -385,9 +385,9 @@ private SimpleBlock readSimpleBlock(final Element ref) throws IOException {
}
private Cluster readCluster(final Element ref) throws IOException {
- Cluster obj = new Cluster(ref);
+ final Cluster obj = new Cluster(ref);
- Element elem = untilElement(ref, ID_TIMECODE);
+ final Element elem = untilElement(ref, ID_TIMECODE);
if (elem == null) {
throw new NoSuchElementException("Cluster at " + String.valueOf(ref.offset)
+ " without Timecode element");
@@ -443,7 +443,7 @@ public Cluster getNextCluster() throws IOException {
}
ensure(segment.currentCluster);
- Element elem = untilElement(segment.ref, ID_CLUSTER);
+ final Element elem = untilElement(segment.ref, ID_CLUSTER);
if (elem == null) {
return null;
}
diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java
index 02b22965d5b..55792d09991 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java
@@ -107,7 +107,7 @@ public void close() {
done = true;
parsed = true;
- for (SharpStream src : sourceTracks) {
+ for (final SharpStream src : sourceTracks) {
src.close();
}
@@ -128,12 +128,12 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
makeEBML(out);
- long offsetSegmentSizeSet = written + 5;
- long offsetInfoDurationSet = written + 94;
- long offsetClusterSet = written + 58;
- long offsetCuesSet = written + 75;
+ final long offsetSegmentSizeSet = written + 5;
+ final long offsetInfoDurationSet = written + 94;
+ final long offsetClusterSet = written + 58;
+ final long offsetCuesSet = written + 75;
- ArrayList listBuffer = new ArrayList<>(4);
+ final ArrayList listBuffer = new ArrayList<>(4);
/* segment */
listBuffer.add(new byte[]{
@@ -141,7 +141,7 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// segment content size
});
- long segmentOffset = written + listBuffer.get(0).length;
+ final long segmentOffset = written + listBuffer.get(0).length;
/* seek head */
listBuffer.add(new byte[]{
@@ -177,11 +177,11 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
dump(listBuffer, out);
// reserve space for Cues element
- long cueOffset = written;
+ final long cueOffset = written;
makeEbmlVoid(out, CUE_RESERVE_SIZE, true);
- int[] defaultSampleDuration = new int[infoTracks.length];
- long[] duration = new long[infoTracks.length];
+ final int[] defaultSampleDuration = new int[infoTracks.length];
+ final long[] duration = new long[infoTracks.length];
for (int i = 0; i < infoTracks.length; i++) {
if (infoTracks[i].defaultDuration < 0) {
@@ -194,9 +194,9 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
}
// Select a track for the cue
- int cuesForTrackId = selectTrackForCue();
+ final int cuesForTrackId = selectTrackForCue();
long nextCueTime = infoTracks[cuesForTrackId].trackType == 1 ? -1 : 0;
- ArrayList keyFrames = new ArrayList<>(32);
+ final ArrayList keyFrames = new ArrayList<>(32);
int firstClusterOffset = (int) written;
long currentClusterOffset = makeCluster(out, 0, 0, true);
@@ -213,7 +213,7 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
blockWritten = 0;
int i = 0;
while (i < readers.length) {
- Block bloq = getNextBlockFrom(i);
+ final Block bloq = getNextBlockFrom(i);
if (bloq == null) {
i++;
continue;
@@ -272,7 +272,7 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
makeCluster(out, -1, currentClusterOffset, false);
- long segmentSize = written - offsetSegmentSizeSet - 7;
+ final long segmentSize = written - offsetSegmentSizeSet - 7;
/* Segment size */
seekTo(out, offsetSegmentSizeSet);
@@ -303,8 +303,8 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
short cueSize = 0;
dump(new byte[]{0x1c, 0x53, (byte) 0xbb, 0x6b, 0x20, 0x00, 0x00}, out); // header size is 7
- for (KeyFrame keyFrame : keyFrames) {
- int size = makeCuePoint(cuesForTrackId, keyFrame, outBuffer);
+ for (final KeyFrame keyFrame : keyFrames) {
+ final int size = makeCuePoint(cuesForTrackId, keyFrame, outBuffer);
if ((cueSize + size + 7 + MINIMUM_EBML_VOID_SIZE) > CUE_RESERVE_SIZE) {
break; // no space left
@@ -323,7 +323,7 @@ public void build(final SharpStream out) throws IOException, RuntimeException {
/* seek head, seek for cues element */
writeInt(out, offsetCuesSet, (int) (cueOffset - segmentOffset));
- for (ClusterInfo cluster : clustersOffsetsSizes) {
+ for (final ClusterInfo cluster : clustersOffsetsSizes) {
writeInt(out, cluster.offset, cluster.size | 0x10000000);
}
}
@@ -344,13 +344,13 @@ private Block getNextBlockFrom(final int internalTrackId) throws IOException {
}
}
- SimpleBlock res = readersCluster[internalTrackId].getNextSimpleBlock();
+ final SimpleBlock res = readersCluster[internalTrackId].getNextSimpleBlock();
if (res == null) {
readersCluster[internalTrackId] = null;
return new Block(); // fake block to indicate the end of the cluster
}
- Block bloq = new Block();
+ final Block bloq = new Block();
bloq.data = res.data;
bloq.dataSize = res.dataSize;
bloq.trackNumber = internalTrackId;
@@ -384,13 +384,13 @@ private void writeInt(final SharpStream stream, final long offset, final int num
private void writeBlock(final SharpStream stream, final Block bloq, final long clusterTimecode)
throws IOException {
- long relativeTimeCode = bloq.absoluteTimecode - clusterTimecode;
+ final long relativeTimeCode = bloq.absoluteTimecode - clusterTimecode;
if (relativeTimeCode < Short.MIN_VALUE || relativeTimeCode > Short.MAX_VALUE) {
throw new IndexOutOfBoundsException("SimpleBlock timecode overflow.");
}
- ArrayList listBuffer = new ArrayList<>(5);
+ final ArrayList listBuffer = new ArrayList<>(5);
listBuffer.add(new byte[]{(byte) 0xa3});
listBuffer.add(null); // block size
listBuffer.add(encode(bloq.trackNumber + 1, false));
@@ -458,7 +458,7 @@ private void makeEBML(final SharpStream stream) throws IOException {
}
private ArrayList makeTracks() {
- ArrayList buffer = new ArrayList<>(1);
+ final ArrayList buffer = new ArrayList<>(1);
buffer.add(new byte[]{0x16, 0x54, (byte) 0xae, 0x6b});
buffer.add(null);
@@ -470,8 +470,8 @@ private ArrayList makeTracks() {
}
private ArrayList makeTrackEntry(final int internalTrackId, final WebMTrack track) {
- byte[] id = encode(internalTrackId + 1, true);
- ArrayList buffer = new ArrayList<>(12);
+ final byte[] id = encode(internalTrackId + 1, true);
+ final ArrayList buffer = new ArrayList<>(12);
/* track */
buffer.add(new byte[]{(byte) 0xae});
@@ -536,7 +536,7 @@ private ArrayList makeTrackEntry(final int internalTrackId, final WebMTr
private int makeCuePoint(final int internalTrackId, final KeyFrame keyFrame,
final byte[] buffer) {
- ArrayList cue = new ArrayList<>(5);
+ final ArrayList cue = new ArrayList<>(5);
/* CuePoint */
cue.add(new byte[]{(byte) 0xbb});
@@ -552,7 +552,7 @@ private int makeCuePoint(final int internalTrackId, final KeyFrame keyFrame,
int size = 0;
lengthFor(cue);
- for (byte[] buff : cue) {
+ for (final byte[] buff : cue) {
System.arraycopy(buff, 0, buffer, size, buff.length);
size += buff.length;
}
@@ -562,7 +562,7 @@ private int makeCuePoint(final int internalTrackId, final KeyFrame keyFrame,
private ArrayList makeCueTrackPosition(final int internalTrackId,
final KeyFrame keyFrame) {
- ArrayList buffer = new ArrayList<>(8);
+ final ArrayList buffer = new ArrayList<>(8);
/* CueTrackPositions */
buffer.add(new byte[]{(byte) 0xb7});
@@ -598,7 +598,7 @@ private void makeEbmlVoid(final SharpStream out, final int amount, final boolean
if (wipe) {
size -= 4;
while (size > 0) {
- int write = Math.min(size, outBuffer.length);
+ final int write = Math.min(size, outBuffer.length);
dump(outBuffer, write, out);
size -= write;
}
@@ -617,7 +617,7 @@ private void dump(final byte[] buffer, final int count, final SharpStream stream
private void dump(final ArrayList buffers, final SharpStream stream)
throws IOException {
- for (byte[] buffer : buffers) {
+ for (final byte[] buffer : buffers) {
stream.write(buffer);
written += buffer.length;
}
@@ -649,9 +649,9 @@ private byte[] encode(final long number, final boolean withLength) {
length++;
}
- int offset = withLength ? 1 : 0;
- byte[] buffer = new byte[offset + length];
- long marker = (long) Math.floor((length - 1f) / 8f);
+ final int offset = withLength ? 1 : 0;
+ final byte[] buffer = new byte[offset + length];
+ final long marker = (long) Math.floor((length - 1f) / 8f);
int shift = 0;
for (int i = length - 1; i >= 0; i--, shift += 8) {
@@ -670,10 +670,9 @@ private byte[] encode(final long number, final boolean withLength) {
}
private ArrayList encode(final String value) {
- byte[] str;
- str = value.getBytes(StandardCharsets.UTF_8); // or use "utf-8"
+ final byte[] str = value.getBytes(StandardCharsets.UTF_8); // or use "utf-8"
- ArrayList buffer = new ArrayList<>(2);
+ final ArrayList buffer = new ArrayList<>(2);
buffer.add(encode(str.length, false));
buffer.add(str);
@@ -700,7 +699,7 @@ private int selectTrackForCue() {
}
}
- int kind;
+ final int kind;
if (audioTracks == infoTracks.length) {
kind = 2;
} else if (videoTracks == infoTracks.length) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
index 4fa14ed014a..9220891a98a 100644
--- a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java
@@ -84,11 +84,11 @@ public static void animateView(final View view, final Type animationType,
String id;
try {
id = view.getResources().getResourceEntryName(view.getId());
- } catch (Exception e) {
+ } catch (final Exception e) {
id = view.getId() + "";
}
- String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit,
+ final String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit,
view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd);
Log.d(TAG, "animateView()" + msg);
}
@@ -158,7 +158,7 @@ public static void animateBackgroundColor(final View view, final long duration,
}
final int[][] empty = new int[][]{new int[0]};
- ValueAnimator viewPropertyAnimator = ValueAnimator
+ final ValueAnimator viewPropertyAnimator = ValueAnimator
.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
viewPropertyAnimator.setDuration(duration);
@@ -201,7 +201,7 @@ public static void animateTextColor(final TextView view, final long duration,
+ "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
}
- ValueAnimator viewPropertyAnimator = ValueAnimator
+ final ValueAnimator viewPropertyAnimator = ValueAnimator
.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
viewPropertyAnimator.setDuration(duration);
@@ -233,7 +233,7 @@ public static ValueAnimator animateHeight(final View view, final long duration,
+ "from " + height + " to → " + targetHeight + " in: " + view);
}
- ValueAnimator animator = ValueAnimator.ofFloat(height, targetHeight);
+ final ValueAnimator animator = ValueAnimator.ofFloat(height, targetHeight);
animator.setInterpolator(new FastOutSlowInInterpolator());
animator.setDuration(duration);
animator.addUpdateListener(animation -> {
@@ -462,7 +462,7 @@ public void onAnimationEnd(final Animator animation) {
public static void slideUp(final View view, final long duration, final long delay,
@FloatRange(from = 0.0f, to = 1.0f)
final float translationPercent) {
- int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels
+ final int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels
* (translationPercent));
view.animate().setListener(null).cancel();
diff --git a/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java b/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java
index 5b1c4637214..b6f1eaf491b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/BitmapUtils.java
@@ -14,14 +14,14 @@ public static Bitmap centerCrop(final Bitmap inputBitmap, final int newWidth,
return null;
}
- float sourceWidth = inputBitmap.getWidth();
- float sourceHeight = inputBitmap.getHeight();
+ final float sourceWidth = inputBitmap.getWidth();
+ final float sourceHeight = inputBitmap.getHeight();
- float xScale = newWidth / sourceWidth;
- float yScale = newHeight / sourceHeight;
+ final float xScale = newWidth / sourceWidth;
+ final float yScale = newHeight / sourceHeight;
- float newXScale;
- float newYScale;
+ final float newXScale;
+ final float newYScale;
if (yScale > xScale) {
newXScale = xScale / yScale;
@@ -31,15 +31,14 @@ public static Bitmap centerCrop(final Bitmap inputBitmap, final int newWidth,
newYScale = yScale / xScale;
}
- float scaledWidth = newXScale * sourceWidth;
- float scaledHeight = newYScale * sourceHeight;
+ final float scaledWidth = newXScale * sourceWidth;
+ final float scaledHeight = newYScale * sourceHeight;
- int left = (int) ((sourceWidth - scaledWidth) / 2);
- int top = (int) ((sourceHeight - scaledHeight) / 2);
- int width = (int) scaledWidth;
- int height = (int) scaledHeight;
+ final int left = (int) ((sourceWidth - scaledWidth) / 2);
+ final int top = (int) ((sourceHeight - scaledHeight) / 2);
+ final int width = (int) scaledWidth;
+ final int height = (int) scaledHeight;
return Bitmap.createBitmap(inputBitmap, left, top, width, height);
}
-
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index 770592537c1..cf347e7c48d 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -37,12 +37,12 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (!(v instanceof TextView)) {
return false;
}
- TextView widget = (TextView) v;
- Object text = widget.getText();
+ final TextView widget = (TextView) v;
+ final Object text = widget.getText();
if (text instanceof Spanned) {
- Spannable buffer = (Spannable) text;
+ final Spannable buffer = (Spannable) text;
- int action = event.getAction();
+ final int action = event.getAction();
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_DOWN) {
@@ -55,11 +55,11 @@ public boolean onTouch(final View v, final MotionEvent event) {
x += widget.getScrollX();
y += widget.getScrollY();
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
- int off = layout.getOffsetForHorizontal(line, x);
+ final Layout layout = widget.getLayout();
+ final int line = layout.getLineForVertical(y);
+ final int off = layout.getOffsetForHorizontal(line, x);
- ClickableSpan[] link = buffer.getSpans(off, off,
+ final ClickableSpan[] link = buffer.getSpans(off, off,
ClickableSpan.class);
if (link.length != 0) {
@@ -86,17 +86,17 @@ public boolean onTouch(final View v, final MotionEvent event) {
private boolean handleUrl(final Context context, final URLSpan urlSpan) {
String url = urlSpan.getURL();
int seconds = -1;
- Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
+ final Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
if (matcher.matches()) {
url = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
}
- StreamingService service;
- StreamingService.LinkType linkType;
+ final StreamingService service;
+ final StreamingService.LinkType linkType;
try {
service = NewPipe.getServiceByUrl(url);
linkType = service.getLinkTypeByUrl(url);
- } catch (ExtractionException e) {
+ } catch (final ExtractionException e) {
return false;
}
if (linkType == StreamingService.LinkType.NONE) {
@@ -112,18 +112,20 @@ private boolean handleUrl(final Context context, final URLSpan urlSpan) {
private boolean playOnPopup(final Context context, final String url,
final StreamingService service, final int seconds) {
- LinkHandlerFactory factory = service.getStreamLHFactory();
- String cleanUrl = null;
+ final LinkHandlerFactory factory = service.getStreamLHFactory();
+ final String cleanUrl;
try {
cleanUrl = factory.getUrl(factory.getId(url));
- } catch (ParsingException e) {
+ } catch (final ParsingException e) {
return false;
}
- Single single = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
+ final Single single
+ = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
- PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds * 1000);
+ final PlayQueue playQueue
+ = new SinglePlayQueue((StreamInfo) info, seconds * 1000);
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
});
return true;
diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java
index d8b81b4cef8..d970ec472e9 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java
@@ -12,8 +12,8 @@ private CookieUtils() {
}
public static String concatCookies(final Collection cookieStrings) {
- Set cookieSet = new HashSet<>();
- for (String cookies : cookieStrings) {
+ final Set cookieSet = new HashSet<>();
+ for (final String cookies : cookieStrings) {
cookieSet.addAll(splitCookies(cookies));
}
return TextUtils.join("; ", cookieSet).trim();
diff --git a/app/src/main/java/org/schabi/newpipe/util/AndroidTvUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
similarity index 74%
rename from app/src/main/java/org/schabi/newpipe/util/AndroidTvUtils.java
rename to app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
index db2ab4aa709..d852c2296fe 100644
--- a/app/src/main/java/org/schabi/newpipe/util/AndroidTvUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
@@ -8,25 +8,26 @@
import android.os.Build;
import android.view.KeyEvent;
+import androidx.annotation.NonNull;
import org.schabi.newpipe.App;
import static android.content.Context.BATTERY_SERVICE;
import static android.content.Context.UI_MODE_SERVICE;
-public final class AndroidTvUtils {
+public final class DeviceUtils {
private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
private static Boolean isTV = null;
- private AndroidTvUtils() {
+ private DeviceUtils() {
}
public static boolean isTv(final Context context) {
- if (AndroidTvUtils.isTV != null) {
- return AndroidTvUtils.isTV;
+ if (isTV != null) {
+ return isTV;
}
- PackageManager pm = App.getApp().getPackageManager();
+ final PackageManager pm = App.getApp().getPackageManager();
// from doc: https://developer.android.com/training/tv/start/hardware.html#runtime-check
boolean isTv = ((UiModeManager) context.getSystemService(UI_MODE_SERVICE))
@@ -36,7 +37,8 @@ public static boolean isTv(final Context context) {
// from https://stackoverflow.com/a/58932366
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- boolean isBatteryAbsent = ((BatteryManager) context.getSystemService(BATTERY_SERVICE))
+ final boolean isBatteryAbsent
+ = ((BatteryManager) context.getSystemService(BATTERY_SERVICE))
.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) == 0;
isTv = isTv || (isBatteryAbsent
&& !pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
@@ -48,8 +50,15 @@ public static boolean isTv(final Context context) {
isTv = isTv || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
- AndroidTvUtils.isTV = isTv;
- return AndroidTvUtils.isTV;
+ DeviceUtils.isTV = isTv;
+ return DeviceUtils.isTV;
+ }
+
+ public static boolean isTablet(@NonNull final Context context) {
+ return (context
+ .getResources()
+ .getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
+ >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
public static boolean isConfirmKey(final int keyCode) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 9b8b2494e67..a1a73d7acbf 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -101,7 +101,7 @@ public static Single getMoreSearchItems(final int serviceId,
public static Single> suggestionsFor(final int serviceId, final String query) {
checkServiceId(serviceId);
return Single.fromCallable(() -> {
- SuggestionExtractor extractor = NewPipe.getService(serviceId)
+ final SuggestionExtractor extractor = NewPipe.getService(serviceId)
.getSuggestionExtractor();
return extractor != null
? extractor.suggestionList(query)
@@ -212,10 +212,10 @@ private static Single checkCache(final boolean forceLoad,
final InfoItem.InfoType infoType,
final Single loadFromNetwork) {
checkServiceId(serviceId);
- Single actualLoadFromNetwork = loadFromNetwork
+ final Single actualLoadFromNetwork = loadFromNetwork
.doOnSuccess(info -> CACHE.putInfo(serviceId, url, info, infoType));
- Single load;
+ final Single load;
if (forceLoad) {
CACHE.removeInfo(serviceId, url, infoType);
load = actualLoadFromNetwork;
@@ -243,7 +243,7 @@ private static Maybe loadFromCache(final int serviceId, fina
checkServiceId(serviceId);
return Maybe.defer(() -> {
//noinspection unchecked
- I info = (I) CACHE.getFromKey(serviceId, url, infoType);
+ final I info = (I) CACHE.getFromKey(serviceId, url, infoType);
if (MainActivity.DEBUG) {
Log.d(TAG, "loadFromCache() called, info > " + info);
}
@@ -283,7 +283,7 @@ public static void handleGeneralException(final Context context, final int servi
if (exception instanceof ReCaptchaException) {
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
- Intent intent = new Intent(context, ReCaptchaActivity.class);
+ final Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} else if (ExceptionUtils.isNetworkRelated(exception)) {
@@ -293,7 +293,7 @@ public static void handleGeneralException(final Context context, final int servi
} else if (exception instanceof ContentNotSupportedException) {
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
} else {
- int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
+ final int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
? R.string.youtube_signature_decryption_error
: exception instanceof ParsingException
? R.string.parsing_error : R.string.general_error;
diff --git a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
index 3179662bab1..dda01b60cee 100644
--- a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.java
@@ -22,7 +22,7 @@ private FilenameUtils() { }
* @return the filename
*/
public static String createFilename(final Context context, final String title) {
- SharedPreferences sharedPreferences = PreferenceManager
+ final SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);
final String charsetLd = context.getString(R.string.charset_letters_and_digits_value);
@@ -48,7 +48,7 @@ public static String createFilename(final Context context, final String title) {
charset = selectedCharset; // Is the user using a custom charset?
}
- Pattern pattern = Pattern.compile(charset);
+ final Pattern pattern = Pattern.compile(charset);
return createFilename(title, pattern, replacementChar);
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
index 035416dcdbc..a07f05828fe 100644
--- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
+++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
@@ -59,7 +59,7 @@ private static String keyOf(final int serviceId, @NonNull final String url,
}
private static void removeStaleCache() {
- for (Map.Entry entry : InfoCache.LRU_CACHE.snapshot().entrySet()) {
+ for (final Map.Entry entry : InfoCache.LRU_CACHE.snapshot().entrySet()) {
final CacheData data = entry.getValue();
if (data != null && data.isExpired()) {
InfoCache.LRU_CACHE.remove(entry.getKey());
diff --git a/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java b/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java
index 2ca1284099e..fd50d2edbd8 100644
--- a/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java
+++ b/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java
@@ -20,7 +20,7 @@ public LayoutManagerSmoothScroller(final Context context, final int orientation,
@Override
public void smoothScrollToPosition(final RecyclerView recyclerView,
final RecyclerView.State state, final int position) {
- RecyclerView.SmoothScroller smoothScroller
+ final RecyclerView.SmoothScroller smoothScroller
= new TopSnappedSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
index 189b6823e93..be0630cace8 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
@@ -44,7 +44,7 @@ private ListHelper() { }
*/
public static int getDefaultResolutionIndex(final Context context,
final List videoStreams) {
- String defaultResolution = computeDefaultResolution(context,
+ final String defaultResolution = computeDefaultResolution(context,
R.string.default_resolution_key, R.string.default_resolution_value);
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
}
@@ -70,7 +70,7 @@ public static int getResolutionIndex(final Context context,
*/
public static int getPopupDefaultResolutionIndex(final Context context,
final List videoStreams) {
- String defaultResolution = computeDefaultResolution(context,
+ final String defaultResolution = computeDefaultResolution(context,
R.string.default_popup_resolution_key, R.string.default_popup_resolution_value);
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
}
@@ -90,8 +90,8 @@ public static int getPopupResolutionIndex(final Context context,
public static int getDefaultAudioFormat(final Context context,
final List audioStreams) {
- MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key,
- R.string.default_audio_format_value);
+ final MediaFormat defaultFormat = getDefaultFormat(context,
+ R.string.default_audio_format_key, R.string.default_audio_format_value);
// If the user has chosen to limit resolution to conserve mobile data
// usage then we should also limit our audio usage.
@@ -117,12 +117,13 @@ public static List getSortedStreamVideosList(final Context context,
final List
videoOnlyStreams,
final boolean ascendingOrder) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ final SharedPreferences preferences
+ = PreferenceManager.getDefaultSharedPreferences(context);
- boolean showHigherResolutions = preferences.getBoolean(
+ final boolean showHigherResolutions = preferences.getBoolean(
context.getString(R.string.show_higher_resolutions_key), false);
- MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key,
- R.string.default_video_format_value);
+ final MediaFormat defaultFormat = getDefaultFormat(context,
+ R.string.default_video_format_key, R.string.default_video_format_value);
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams,
videoOnlyStreams, ascendingOrder);
@@ -134,14 +135,15 @@ public static List getSortedStreamVideosList(final Context context,
private static String computeDefaultResolution(final Context context, final int key,
final int value) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ final SharedPreferences preferences
+ = PreferenceManager.getDefaultSharedPreferences(context);
// Load the prefered resolution otherwise the best available
String resolution = preferences != null
? preferences.getString(context.getString(key), context.getString(value))
: context.getString(R.string.best_resolution_key);
- String maxResolution = getResolutionLimit(context);
+ final String maxResolution = getResolutionLimit(context);
if (maxResolution != null
&& (resolution.equals(context.getString(R.string.best_resolution_key))
|| compareVideoStreamResolution(maxResolution, resolution) < 1)) {
@@ -173,7 +175,7 @@ static int getDefaultResolutionIndex(final String defaultResolution,
return 0;
}
- int defaultStreamIndex
+ final int defaultStreamIndex
= getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams);
// this is actually an error,
@@ -200,11 +202,11 @@ static List getSortedStreamVideosList(final MediaFormat defaultForm
final List videoStreams,
final List videoOnlyStreams,
final boolean ascendingOrder) {
- ArrayList retList = new ArrayList<>();
- HashMap hashMap = new HashMap<>();
+ final ArrayList retList = new ArrayList<>();
+ final HashMap hashMap = new HashMap<>();
if (videoOnlyStreams != null) {
- for (VideoStream stream : videoOnlyStreams) {
+ for (final VideoStream stream : videoOnlyStreams) {
if (!showHigherResolutions
&& HIGH_RESOLUTION_LIST.contains(stream.getResolution())) {
continue;
@@ -213,7 +215,7 @@ static List getSortedStreamVideosList(final MediaFormat defaultForm
}
}
if (videoStreams != null) {
- for (VideoStream stream : videoStreams) {
+ for (final VideoStream stream : videoStreams) {
if (!showHigherResolutions
&& HIGH_RESOLUTION_LIST.contains(stream.getResolution())) {
continue;
@@ -223,12 +225,12 @@ static List getSortedStreamVideosList(final MediaFormat defaultForm
}
// Add all to the hashmap
- for (VideoStream videoStream : retList) {
+ for (final VideoStream videoStream : retList) {
hashMap.put(videoStream.getResolution(), videoStream);
}
// Override the values when the key == resolution, with the defaultFormat
- for (VideoStream videoStream : retList) {
+ for (final VideoStream videoStream : retList) {
if (videoStream.getFormat() == defaultFormat) {
hashMap.put(videoStream.getResolution(), videoStream);
}
@@ -262,7 +264,7 @@ static List getSortedStreamVideosList(final MediaFormat defaultForm
private static void sortStreamList(final List