Merge pull request #4288 from avently/performance-increase

Performance increase
This commit is contained in:
opusforlife2 2020-09-28 17:45:25 +00:00 committed by GitHub
commit 160a04c3c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 363 additions and 201 deletions

View File

@ -3,11 +3,9 @@ package org.schabi.newpipe.fragments.detail;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.app.Activity; import android.app.Activity;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.database.ContentObserver; import android.database.ContentObserver;
@ -16,7 +14,8 @@ import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.Looper;
import android.view.ViewTreeObserver;
import androidx.core.text.HtmlCompat; import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import android.provider.Settings; import android.provider.Settings;
@ -60,6 +59,7 @@ import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.download.DownloadDialog; import org.schabi.newpipe.download.DownloadDialog;
@ -83,12 +83,11 @@ import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.BasePlayer; import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.VideoPlayerImpl; import org.schabi.newpipe.player.VideoPlayerImpl;
import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
@ -98,12 +97,10 @@ import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.InfoCache;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.AnimatedProgressBar; import org.schabi.newpipe.views.AnimatedProgressBar;
@ -136,8 +133,7 @@ public class VideoDetailFragment
SharedPreferences.OnSharedPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener,
View.OnClickListener, View.OnClickListener,
View.OnLongClickListener, View.OnLongClickListener,
PlayerEventListener, PlayerServiceExtendedEventListener,
PlayerServiceEventListener,
OnKeyDownListener { OnKeyDownListener {
public static final String AUTO_PLAY = "auto_play"; public static final String AUTO_PLAY = "auto_play";
@ -159,9 +155,6 @@ public class VideoDetailFragment
private static final String RELATED_TAB_TAG = "NEXT VIDEO"; private static final String RELATED_TAB_TAG = "NEXT VIDEO";
private static final String EMPTY_TAB_TAG = "EMPTY TAB"; private static final String EMPTY_TAB_TAG = "EMPTY TAB";
private static final String INFO_KEY = "info_key";
private static final String STACK_KEY = "stack_key";
private boolean showRelatedStreams; private boolean showRelatedStreams;
private boolean showComments; private boolean showComments;
private String selectedTabTag; private String selectedTabTag;
@ -174,14 +167,13 @@ public class VideoDetailFragment
protected String name; protected String name;
@State @State
protected String url; protected String url;
@State protected static PlayQueue playQueue;
protected PlayQueue playQueue;
@State @State
int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED;
@State @State
protected boolean autoPlayEnabled = true; protected boolean autoPlayEnabled = true;
private StreamInfo currentInfo; private static StreamInfo currentInfo;
private Disposable currentWorker; private Disposable currentWorker;
@NonNull @NonNull
private CompositeDisposable disposables = new CompositeDisposable(); private CompositeDisposable disposables = new CompositeDisposable();
@ -250,8 +242,6 @@ public class VideoDetailFragment
private FrameLayout relatedStreamsLayout; private FrameLayout relatedStreamsLayout;
private ContentObserver settingsContentObserver; private ContentObserver settingsContentObserver;
private ServiceConnection serviceConnection;
private boolean bound;
private MainPlayer playerService; private MainPlayer playerService;
private VideoPlayerImpl player; private VideoPlayerImpl player;
@ -259,30 +249,12 @@ public class VideoDetailFragment
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Service management // Service management
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override
private ServiceConnection getServiceConnection(final Context context, public void onServiceConnected(final VideoPlayerImpl connectedPlayer,
final MainPlayer connectedPlayerService,
final boolean playAfterConnect) { final boolean playAfterConnect) {
return new ServiceConnection() { player = connectedPlayer;
@Override playerService = connectedPlayerService;
public void onServiceDisconnected(final ComponentName compName) {
if (DEBUG) {
Log.d(TAG, "Player service is disconnected");
}
unbind(context);
}
@Override
public void onServiceConnected(final ComponentName compName, final IBinder service) {
if (DEBUG) {
Log.d(TAG, "Player service is connected");
}
final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service;
playerService = localBinder.getService();
player = localBinder.getPlayer();
startPlayerListener();
// It will do nothing if the player is not in fullscreen mode // It will do nothing if the player is not in fullscreen mode
hideSystemUiIfNeeded(); hideSystemUiIfNeeded();
@ -291,20 +263,20 @@ public class VideoDetailFragment
return; return;
} }
if (playerIsNotStopped() && player.videoPlayerSelected()) {
addVideoPlayerView();
}
if (isLandscape()) { if (isLandscape()) {
// If the video is playing but orientation changed // If the video is playing but orientation changed
// let's make the video in fullscreen again // let's make the video in fullscreen again
checkLandscape(); checkLandscape();
} else if (player.isFullscreen()) { } else if (player.isFullscreen() && !player.isVerticalVideo()) {
// Device is in portrait orientation after rotation but UI is in fullscreen. // Device is in portrait orientation after rotation but UI is in fullscreen.
// Return back to non-fullscreen state // Return back to non-fullscreen state
player.toggleFullscreen(); player.toggleFullscreen();
} }
if (playerIsNotStopped() && player.videoPlayerSelected()) {
addVideoPlayerView();
}
if (playAfterConnect if (playAfterConnect
|| (currentInfo != null || (currentInfo != null
&& isAutoplayEnabled() && isAutoplayEnabled()
@ -312,70 +284,21 @@ public class VideoDetailFragment
openVideoPlayer(); openVideoPlayer();
} }
} }
};
}
private void bind(final Context context) { @Override
if (DEBUG) { public void onServiceDisconnected() {
Log.d(TAG, "bind() called");
}
final Intent serviceIntent = new Intent(context, MainPlayer.class);
bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
if (!bound) {
context.unbindService(serviceConnection);
}
}
private void unbind(final Context context) {
if (DEBUG) {
Log.d(TAG, "unbind() called");
}
if (bound) {
context.unbindService(serviceConnection);
bound = false;
stopPlayerListener();
playerService = null; playerService = null;
player = null; player = null;
restoreDefaultBrightness(); restoreDefaultBrightness();
} }
}
private void startPlayerListener() {
if (player != null) {
player.setFragmentListener(this);
}
}
private void stopPlayerListener() {
if (player != null) {
player.removeFragmentListener(this);
}
}
private void startService(final Context context, final boolean playAfterConnect) {
// startService() can be called concurrently and it will give a random crashes
// and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first
unbind(context);
context.startService(new Intent(context, MainPlayer.class));
serviceConnection = getServiceConnection(context, playAfterConnect);
bind(context);
}
private void stopService(final Context context) {
unbind(context);
context.stopService(new Intent(context, MainPlayer.class));
}
/*////////////////////////////////////////////////////////////////////////*/ /*////////////////////////////////////////////////////////////////////////*/
public static VideoDetailFragment getInstance(final int serviceId, final String videoUrl, public static VideoDetailFragment getInstance(final int serviceId, final String videoUrl,
final String name, final PlayQueue playQueue) { final String name, final PlayQueue queue) {
final VideoDetailFragment instance = new VideoDetailFragment(); final VideoDetailFragment instance = new VideoDetailFragment();
instance.setInitialData(serviceId, videoUrl, name, playQueue); instance.setInitialData(serviceId, videoUrl, name, queue);
return instance; return instance;
} }
@ -478,9 +401,9 @@ public class VideoDetailFragment
// Stop the service when user leaves the app with double back press // Stop the service when user leaves the app with double back press
// if video player is selected. Otherwise unbind // if video player is selected. Otherwise unbind
if (activity.isFinishing() && player != null && player.videoPlayerSelected()) { if (activity.isFinishing() && player != null && player.videoPlayerSelected()) {
stopService(requireContext()); PlayerHolder.stopService(App.getApp());
} else { } else {
unbind(requireContext()); PlayerHolder.removeListener();
} }
PreferenceManager.getDefaultSharedPreferences(activity) PreferenceManager.getDefaultSharedPreferences(activity)
@ -498,6 +421,12 @@ public class VideoDetailFragment
positionSubscriber = null; positionSubscriber = null;
currentWorker = null; currentWorker = null;
bottomSheetBehavior.setBottomSheetCallback(null); bottomSheetBehavior.setBottomSheetCallback(null);
if (activity.isFinishing()) {
playQueue = null;
currentInfo = null;
stack = new LinkedList<>();
}
} }
@Override @Override
@ -530,62 +459,6 @@ public class VideoDetailFragment
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
if (!isLoading.get() && currentInfo != null && isVisible()) {
final String infoCacheKey = SerializedCache.getInstance()
.put(currentInfo, StreamInfo.class);
if (infoCacheKey != null) {
outState.putString(INFO_KEY, infoCacheKey);
}
}
if (playQueue != null) {
final String queueCacheKey = SerializedCache.getInstance()
.put(playQueue, PlayQueue.class);
if (queueCacheKey != null) {
outState.putString(VideoPlayer.PLAY_QUEUE_KEY, queueCacheKey);
}
}
final String stackCacheKey = SerializedCache.getInstance().put(stack, LinkedList.class);
if (stackCacheKey != null) {
outState.putString(STACK_KEY, stackCacheKey);
}
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
final String infoCacheKey = savedState.getString(INFO_KEY);
if (infoCacheKey != null) {
currentInfo = SerializedCache.getInstance().take(infoCacheKey, StreamInfo.class);
if (currentInfo != null) {
InfoCache.getInstance()
.putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM);
}
}
final String stackCacheKey = savedState.getString(STACK_KEY);
if (stackCacheKey != null) {
final LinkedList<StackItem> cachedStack =
SerializedCache.getInstance().take(stackCacheKey, LinkedList.class);
if (cachedStack != null) {
stack.addAll(cachedStack);
}
}
final String queueCacheKey = savedState.getString(VideoPlayer.PLAY_QUEUE_KEY);
if (queueCacheKey != null) {
playQueue = SerializedCache.getInstance().take(queueCacheKey, PlayQueue.class);
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// OnClick // OnClick
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -780,8 +653,6 @@ public class VideoDetailFragment
relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout); relatedStreamsLayout = rootView.findViewById(R.id.relatedStreamsLayout);
setHeightThumbnail();
thumbnailBackgroundButton.requestFocus(); thumbnailBackgroundButton.requestFocus();
if (DeviceUtils.isTv(getContext())) { if (DeviceUtils.isTv(getContext())) {
@ -827,7 +698,11 @@ public class VideoDetailFragment
detailControlsPopup.setOnTouchListener(getOnControlsTouchListener()); detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
setupBottomPlayer(); setupBottomPlayer();
startService(requireContext(), false); if (!PlayerHolder.bound) {
setHeightThumbnail();
} else {
PlayerHolder.startService(App.getApp(), false, this);
}
} }
private View.OnTouchListener getOnControlsTouchListener() { private View.OnTouchListener getOnControlsTouchListener() {
@ -882,7 +757,7 @@ public class VideoDetailFragment
* Stack that contains the "navigation history".<br> * Stack that contains the "navigation history".<br>
* The peek is the current video. * The peek is the current video.
*/ */
protected final LinkedList<StackItem> stack = new LinkedList<>(); private static LinkedList<StackItem> stack = new LinkedList<>();
@Override @Override
public boolean onKeyDown(final int keyCode) { public boolean onKeyDown(final int keyCode) {
@ -966,7 +841,7 @@ public class VideoDetailFragment
if (currentInfo == null) { if (currentInfo == null) {
prepareAndLoadInfo(); prepareAndLoadInfo();
} else { } else {
prepareAndHandleInfo(currentInfo, false); prepareAndHandleInfoIfNeededAfterDelay(currentInfo, false, 50);
} }
} }
@ -985,6 +860,21 @@ public class VideoDetailFragment
startLoading(false, true); startLoading(false, true);
} }
private void prepareAndHandleInfoIfNeededAfterDelay(final StreamInfo info,
final boolean scrollToTop,
final long delay) {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (activity == null) {
return;
}
// Data can already be drawn, don't spend time twice
if (info.getName().equals(videoTitleTextView.getText().toString())) {
return;
}
prepareAndHandleInfo(info, scrollToTop);
}, delay);
}
private void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) { private void prepareAndHandleInfo(final StreamInfo info, final boolean scrollToTop) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "prepareAndHandleInfo() called with: " Log.d(TAG, "prepareAndHandleInfo() called with: "
@ -1144,8 +1034,8 @@ public class VideoDetailFragment
} }
// See UI changes while remote playQueue changes // See UI changes while remote playQueue changes
if (!bound) { if (player == null) {
startService(requireContext(), false); PlayerHolder.startService(App.getApp(), false, this);
} }
// If a user watched video inside fullscreen mode and than chose another player // If a user watched video inside fullscreen mode and than chose another player
@ -1174,8 +1064,8 @@ public class VideoDetailFragment
private void openNormalBackgroundPlayer(final boolean append) { private void openNormalBackgroundPlayer(final boolean append) {
// See UI changes while remote playQueue changes // See UI changes while remote playQueue changes
if (!bound) { if (player == null) {
startService(requireContext(), false); PlayerHolder.startService(App.getApp(), false, this);
} }
final PlayQueue queue = setupPlayQueueForIntent(append); final PlayQueue queue = setupPlayQueueForIntent(append);
@ -1189,7 +1079,7 @@ public class VideoDetailFragment
private void openMainPlayer() { private void openMainPlayer() {
if (playerService == null) { if (playerService == null) {
startService(requireContext(), true); PlayerHolder.startService(App.getApp(), true, this);
return; return;
} }
if (currentInfo == null) { if (currentInfo == null) {
@ -1278,7 +1168,7 @@ public class VideoDetailFragment
// Check if viewHolder already contains a child // Check if viewHolder already contains a child
if (player.getRootView().getParent() != playerPlaceholder) { if (player.getRootView().getParent() != playerPlaceholder) {
removeVideoPlayerView(); playerService.removeViewFromParent();
} }
setHeightThumbnail(); setHeightThumbnail();
@ -1334,6 +1224,23 @@ public class VideoDetailFragment
} }
} }
private final ViewTreeObserver.OnPreDrawListener preDrawListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (getView() != null) {
final int height = isInMultiWindow()
? requireView().getHeight()
: activity.getWindow().getDecorView().getHeight();
setHeightThumbnail(height, metrics);
getView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
}
return false;
}
};
/** /**
* Method which controls the size of thumbnail and the size of main player inside * Method which controls the size of thumbnail and the size of main player inside
* a layout with thumbnail. It decides what height the player should have in both * a layout with thumbnail. It decides what height the player should have in both
@ -1344,24 +1251,35 @@ public class VideoDetailFragment
private void setHeightThumbnail() { private void setHeightThumbnail() {
final DisplayMetrics metrics = getResources().getDisplayMetrics(); final DisplayMetrics metrics = getResources().getDisplayMetrics();
final boolean isPortrait = metrics.heightPixels > metrics.widthPixels; final boolean isPortrait = metrics.heightPixels > metrics.widthPixels;
requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
final int height;
if (player != null && player.isFullscreen()) { if (player != null && player.isFullscreen()) {
height = isInMultiWindow() final int height = isInMultiWindow()
? requireView().getHeight() ? requireView().getHeight()
: activity.getWindow().getDecorView().getHeight(); : activity.getWindow().getDecorView().getHeight();
// Height is zero when the view is not yet displayed like after orientation change
if (height != 0) {
setHeightThumbnail(height, metrics);
} else { } else {
height = isPortrait requireView().getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
} else {
final int height = isPortrait
? (int) (metrics.widthPixels / (16.0f / 9.0f)) ? (int) (metrics.widthPixels / (16.0f / 9.0f))
: (int) (metrics.heightPixels / 2.0f); : (int) (metrics.heightPixels / 2.0f);
setHeightThumbnail(height, metrics);
}
} }
private void setHeightThumbnail(final int newHeight, final DisplayMetrics metrics) {
thumbnailImageView.setLayoutParams( thumbnailImageView.setLayoutParams(
new FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); new FrameLayout.LayoutParams(
thumbnailImageView.setMinimumHeight(height); RelativeLayout.LayoutParams.MATCH_PARENT, newHeight));
thumbnailImageView.setMinimumHeight(newHeight);
if (player != null) { if (player != null) {
final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT); final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT);
player.getSurfaceView().setHeights(height, player.isFullscreen() ? height : maxHeight); player.getSurfaceView()
.setHeights(newHeight, player.isFullscreen() ? newHeight : maxHeight);
} }
} }
@ -1880,7 +1798,10 @@ public class VideoDetailFragment
currentInfo = info; currentInfo = info;
setInitialData(info.getServiceId(), info.getUrl(), info.getName(), queue); setInitialData(info.getServiceId(), info.getUrl(), info.getName(), queue);
setAutoplay(false); setAutoplay(false);
prepareAndHandleInfo(info, true); // Delay execution just because it freezes the main thread, and while playing
// next/previous video you see visual glitches
// (when non-vertical video goes after vertical video)
prepareAndHandleInfoIfNeededAfterDelay(info, true, 200);
} }
@Override @Override
@ -1897,7 +1818,6 @@ public class VideoDetailFragment
@Override @Override
public void onServiceStopped() { public void onServiceStopped() {
unbind(requireContext());
setOverlayPlayPauseImage(); setOverlayPlayPauseImage();
if (currentInfo != null) { if (currentInfo != null) {
updateOverlayData(currentInfo.getName(), updateOverlayData(currentInfo.getName(),
@ -2189,7 +2109,7 @@ public class VideoDetailFragment
if (currentWorker != null) { if (currentWorker != null) {
currentWorker.dispose(); currentWorker.dispose();
} }
stopService(requireContext()); PlayerHolder.stopService(App.getApp());
setInitialData(0, null, "", null); setInitialData(0, null, "", null);
currentInfo = null; currentInfo = null;
updateOverlayData(null, null, null); updateOverlayData(null, null, null);

View File

@ -500,12 +500,19 @@ public abstract class BasePlayer implements
@Override @Override
public void onLoadingComplete(final String imageUri, final View view, public void onLoadingComplete(final String imageUri, final View view,
final Bitmap loadedImage) { final Bitmap loadedImage) {
final float width = Math.min(
context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
loadedImage.getWidth());
currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
(int) width,
(int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "], " + "imageUri = [" + imageUri + "], view = [" + view + "], "
+ "loadedImage = [" + loadedImage + "]"); + "loadedImage = [" + loadedImage + "], "
+ loadedImage.getWidth() + "x" + loadedImage.getHeight()
+ ", scaled width = " + width);
} }
currentThumbnail = loadedImage;
} }
@Override @Override

View File

@ -2107,4 +2107,8 @@ public class VideoPlayerImpl extends VideoPlayer
public View getClosingOverlayView() { public View getClosingOverlayView() {
return closingOverlayView; return closingOverlayView;
} }
public boolean isVerticalVideo() {
return isVerticalVideo;
}
} }

View File

@ -0,0 +1,11 @@
package org.schabi.newpipe.player.event;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayerImpl;
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
void onServiceConnected(VideoPlayerImpl player,
MainPlayer playerService,
boolean playAfterConnect);
void onServiceDisconnected();
}

View File

@ -0,0 +1,219 @@
package org.schabi.newpipe.player.helper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayerImpl;
import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueue;
public final class PlayerHolder {
private PlayerHolder() {
}
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = "PlayerHolder";
private static PlayerServiceExtendedEventListener listener;
private static ServiceConnection serviceConnection;
public static boolean bound;
private static MainPlayer playerService;
private static VideoPlayerImpl player;
public static void setListener(final PlayerServiceExtendedEventListener newListener) {
listener = newListener;
// Force reload data from service
if (player != null) {
listener.onServiceConnected(player, playerService, false);
startPlayerListener();
}
}
public static void removeListener() {
listener = null;
}
public static void startService(final Context context,
final boolean playAfterConnect,
final PlayerServiceExtendedEventListener newListener) {
setListener(newListener);
if (bound) {
return;
}
// startService() can be called concurrently and it will give a random crashes
// and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first
unbind(context);
context.startService(new Intent(context, MainPlayer.class));
serviceConnection = getServiceConnection(context, playAfterConnect);
bind(context);
}
public static void stopService(final Context context) {
unbind(context);
context.stopService(new Intent(context, MainPlayer.class));
}
private static ServiceConnection getServiceConnection(final Context context,
final boolean playAfterConnect) {
return new ServiceConnection() {
@Override
public void onServiceDisconnected(final ComponentName compName) {
if (DEBUG) {
Log.d(TAG, "Player service is disconnected");
}
unbind(context);
}
@Override
public void onServiceConnected(final ComponentName compName, final IBinder service) {
if (DEBUG) {
Log.d(TAG, "Player service is connected");
}
final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service;
playerService = localBinder.getService();
player = localBinder.getPlayer();
if (listener != null) {
listener.onServiceConnected(player, playerService, playAfterConnect);
}
startPlayerListener();
}
};
}
private static void bind(final Context context) {
if (DEBUG) {
Log.d(TAG, "bind() called");
}
final Intent serviceIntent = new Intent(context, MainPlayer.class);
bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
if (!bound) {
context.unbindService(serviceConnection);
}
}
private static void unbind(final Context context) {
if (DEBUG) {
Log.d(TAG, "unbind() called");
}
if (bound) {
context.unbindService(serviceConnection);
bound = false;
stopPlayerListener();
playerService = null;
player = null;
if (listener != null) {
listener.onServiceDisconnected();
}
}
}
private static void startPlayerListener() {
if (player != null) {
player.setFragmentListener(INNER_LISTENER);
}
}
private static void stopPlayerListener() {
if (player != null) {
player.removeFragmentListener(INNER_LISTENER);
}
}
private static final PlayerServiceEventListener INNER_LISTENER =
new PlayerServiceEventListener() {
@Override
public void onFullscreenStateChanged(final boolean fullscreen) {
if (listener != null) {
listener.onFullscreenStateChanged(fullscreen);
}
}
@Override
public void onScreenRotationButtonClicked() {
if (listener != null) {
listener.onScreenRotationButtonClicked();
}
}
@Override
public void onMoreOptionsLongClicked() {
if (listener != null) {
listener.onMoreOptionsLongClicked();
}
}
@Override
public void onPlayerError(final ExoPlaybackException error) {
if (listener != null) {
listener.onPlayerError(error);
}
}
@Override
public void hideSystemUiIfNeeded() {
if (listener != null) {
listener.hideSystemUiIfNeeded();
}
}
@Override
public void onQueueUpdate(final PlayQueue queue) {
if (listener != null) {
listener.onQueueUpdate(queue);
}
}
@Override
public void onPlaybackUpdate(final int state,
final int repeatMode,
final boolean shuffled,
final PlaybackParameters parameters) {
if (listener != null) {
listener.onPlaybackUpdate(state, repeatMode, shuffled, parameters);
}
}
@Override
public void onProgressUpdate(final int currentProgress,
final int duration,
final int bufferPercent) {
if (listener != null) {
listener.onProgressUpdate(currentProgress, duration, bufferPercent);
}
}
@Override
public void onMetadataUpdate(final StreamInfo info, final PlayQueue queue) {
if (listener != null) {
listener.onMetadataUpdate(info, queue);
}
}
@Override
public void onServiceStopped() {
if (listener != null) {
listener.onServiceStopped();
}
unbind(App.getApp());
}
};
}

View File

@ -48,6 +48,7 @@
<dimen name="player_main_buttons_padding">6dp</dimen> <dimen name="player_main_buttons_padding">6dp</dimen>
<dimen name="player_popup_buttons_padding">1dp</dimen> <dimen name="player_popup_buttons_padding">1dp</dimen>
<dimen name="player_main_buttons_min_width">40dp</dimen> <dimen name="player_main_buttons_min_width">40dp</dimen>
<dimen name="player_notification_thumbnail_width">200dp</dimen>
<!-- Miscellaneous --> <!-- Miscellaneous -->
<dimen name="popup_default_width">180dp</dimen> <dimen name="popup_default_width">180dp</dimen>