Merge branch 'dev' into pr3178
This commit is contained in:
commit
4dd47f8adf
130 changed files with 7247 additions and 7180 deletions
|
|
@ -1,592 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||
* BackgroundPlayer.java is part of NewPipe
|
||||
*
|
||||
* License: GPL-3.0+
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
/**
|
||||
* Service Background Player implementing {@link VideoPlayer}.
|
||||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
public final class BackgroundPlayer extends Service {
|
||||
private static final String TAG = "BackgroundPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
|
||||
public static final String ACTION_CLOSE
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
|
||||
public static final String ACTION_PLAY_PAUSE
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
|
||||
public static final String ACTION_REPEAT
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
|
||||
public static final String ACTION_PLAY_NEXT
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_NEXT";
|
||||
public static final String ACTION_PLAY_PREVIOUS
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_PLAY_PREVIOUS";
|
||||
public static final String ACTION_FAST_REWIND
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
|
||||
public static final String ACTION_FAST_FORWARD
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
|
||||
public static final String ACTION_BUFFERING
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_BUFFERING";
|
||||
public static final String ACTION_SHUFFLE
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_SHUFFLE";
|
||||
|
||||
public static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
|
||||
|
||||
|
||||
private BasePlayerImpl basePlayerImpl;
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private boolean shouldUpdateOnProgress; // only used for old notifications
|
||||
private boolean isForwardPressed;
|
||||
private boolean isRewindPressed;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service-Activity Binder
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private PlayerEventListener activityListener;
|
||||
private IBinder mBinder;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCreate() called");
|
||||
}
|
||||
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
assureCorrectAppLanguage(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
basePlayerImpl = new BasePlayerImpl(this);
|
||||
basePlayerImpl.setup();
|
||||
|
||||
mBinder = new PlayerServiceBinder(basePlayerImpl);
|
||||
shouldUpdateOnProgress = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onStartCommand() called with: intent = [" + intent + "], "
|
||||
+ "flags = [" + flags + "], startId = [" + startId + "]");
|
||||
}
|
||||
basePlayerImpl.handleIntent(intent);
|
||||
if (basePlayerImpl.mediaSessionManager != null) {
|
||||
basePlayerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "destroy() called");
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(final Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Actions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private void onClose() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onClose() called");
|
||||
}
|
||||
|
||||
if (basePlayerImpl != null) {
|
||||
basePlayerImpl.savePlaybackState();
|
||||
basePlayerImpl.stopActivityBinding();
|
||||
basePlayerImpl.destroy();
|
||||
}
|
||||
NotificationUtil.getInstance()
|
||||
.cancelNotification(NotificationUtil.NOTIFICATION_ID_BACKGROUND);
|
||||
mBinder = null;
|
||||
basePlayerImpl = null;
|
||||
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
private void onScreenOnOff(final boolean on) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||
}
|
||||
shouldUpdateOnProgress = on;
|
||||
basePlayerImpl.triggerProgressUpdate();
|
||||
if (on) {
|
||||
basePlayerImpl.startProgressLoop();
|
||||
} else {
|
||||
basePlayerImpl.stopProgressLoop();
|
||||
}
|
||||
}
|
||||
|
||||
protected class BasePlayerImpl extends BasePlayer {
|
||||
@NonNull
|
||||
private final AudioPlaybackResolver resolver;
|
||||
|
||||
BasePlayerImpl(final Context context) {
|
||||
super(context);
|
||||
this.resolver = new AudioPlaybackResolver(context, dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
super.initPlayer(playOnReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleIntent(final Intent intent) {
|
||||
super.handleIntent(intent);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ handleIntent()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences, true); // false
|
||||
NotificationUtil.getInstance().setProgressbarOnOldNotifications(100, 0, false);
|
||||
startForeground(NotificationUtil.NOTIFICATION_ID_BACKGROUND,
|
||||
NotificationUtil.getInstance().notificationBuilder.build());
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Thumbnail Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(final String imageUri, final View view,
|
||||
final Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onLoadingComplete()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences, true); //true
|
||||
NotificationUtil.getInstance().updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(final String imageUri, final View view,
|
||||
final FailReason failReason) {
|
||||
super.onLoadingFailed(imageUri, view, failReason);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onLoadingFailed()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences, true); //true
|
||||
NotificationUtil.getInstance().updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// States Implementation
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPrepared(final boolean playWhenReady) {
|
||||
super.onPrepared(playWhenReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleClicked() {
|
||||
super.onShuffleClicked();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onShuffleClicked:");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
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);
|
||||
|
||||
// setMetadata only updates the metadata when any of the metadata keys are null
|
||||
basePlayerImpl.mediaSessionManager.setMetadata(basePlayerImpl.getVideoTitle(),
|
||||
basePlayerImpl.getUploaderName(), basePlayerImpl.getThumbnail(), duration);
|
||||
|
||||
boolean areOldNotificationsEnabled = sharedPreferences.getBoolean(
|
||||
getString(R.string.enable_old_notifications_key), false);
|
||||
if (areOldNotificationsEnabled) {
|
||||
if (!shouldUpdateOnProgress) {
|
||||
return;
|
||||
}
|
||||
if (NotificationUtil.timesNotificationUpdated
|
||||
> NotificationUtil.NOTIFICATION_UPDATES_BEFORE_RESET) {
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationUtil.getInstance()
|
||||
.updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationUtil.getInstance().setCachedDuration(currentProgress, duration);
|
||||
NotificationUtil.getInstance().setProgressbarOnOldNotifications(duration,
|
||||
currentProgress, false);
|
||||
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayPrevious() {
|
||||
super.onPlayPrevious();
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayNext() {
|
||||
super.onPlayNext();
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
NotificationUtil.getInstance().unsetImageInOldNotifications();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// ExoPlayer Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(final PlaybackParameters playbackParameters) {
|
||||
super.onPlaybackParametersChanged(playbackParameters);
|
||||
updatePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingChanged(final boolean isLoading) {
|
||||
// Disable default behavior
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(final int i) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onRepeatModeChanged()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
updatePlayback();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
super.onMetadataChanged(tag);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onMetadataChanged()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
NotificationUtil.getInstance().updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
return resolver.resolve(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackShutdown() {
|
||||
super.onPlaybackShutdown();
|
||||
onClose();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// 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(), 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;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast Receiver
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected void setupBroadcastReceiver(final IntentFilter intentFilter) {
|
||||
super.setupBroadcastReceiver(intentFilter);
|
||||
intentFilter.addAction(ACTION_CLOSE);
|
||||
intentFilter.addAction(ACTION_PLAY_PAUSE);
|
||||
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(ACTION_BUFFERING);
|
||||
intentFilter.addAction(ACTION_SHUFFLE);
|
||||
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) {
|
||||
// FIXME remove N_
|
||||
Log.d(TAG, "N_ onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||
}
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_CLOSE:
|
||||
onClose();
|
||||
break;
|
||||
case ACTION_PLAY_PAUSE:
|
||||
onPlayPause();
|
||||
break;
|
||||
case ACTION_REPEAT:
|
||||
onRepeatClicked();
|
||||
break;
|
||||
case ACTION_PLAY_NEXT:
|
||||
onPlayNext();
|
||||
break;
|
||||
case ACTION_PLAY_PREVIOUS:
|
||||
onPlayPrevious();
|
||||
break;
|
||||
case ACTION_FAST_FORWARD:
|
||||
isForwardPressed = true;
|
||||
onFastForward();
|
||||
break;
|
||||
case ACTION_FAST_REWIND:
|
||||
isRewindPressed = true;
|
||||
onFastRewind();
|
||||
break;
|
||||
case Intent.ACTION_SCREEN_ON:
|
||||
onScreenOnOff(true);
|
||||
break;
|
||||
case Intent.ACTION_SCREEN_OFF:
|
||||
onScreenOnOff(false);
|
||||
break;
|
||||
case ACTION_BUFFERING:
|
||||
onBuffering();
|
||||
break;
|
||||
case ACTION_SHUFFLE:
|
||||
onShuffleClicked();
|
||||
break;
|
||||
case "android.intent.action.HEADSET_PLUG": //FIXME
|
||||
/*notificationManager.cancel(NOTIFICATION_ID);
|
||||
mediaSessionManager.dispose();
|
||||
mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// States
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
super.onBuffering();
|
||||
if (NotificationUtil.getInstance().notificationSlot0.contains("buffering")
|
||||
|| NotificationUtil.getInstance().notificationSlot1.contains("buffering")
|
||||
|| NotificationUtil.getInstance().notificationSlot2.contains("buffering")
|
||||
|| NotificationUtil.getInstance().notificationSlot3.contains("buffering")
|
||||
|| NotificationUtil.getInstance().notificationSlot4.contains("buffering")) {
|
||||
if (basePlayerImpl.getCurrentState() == BasePlayer.STATE_PREFLIGHT
|
||||
|| basePlayerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED
|
||||
|| basePlayerImpl.getCurrentState() == BasePlayer.STATE_BUFFERING) {
|
||||
if (!(isForwardPressed || isRewindPressed)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onBuffering()");
|
||||
}
|
||||
NotificationUtil.getInstance().updateBackgroundPlayerNotification(-1,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
} else {
|
||||
isForwardPressed = false;
|
||||
isRewindPressed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeState(final int state) {
|
||||
super.changeState(state);
|
||||
updatePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaying() {
|
||||
super.onPlaying();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onPlaying()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
NotificationUtil.getInstance().updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
NotificationUtil.getInstance()
|
||||
.updateBackgroundPlayerNotification(R.drawable.ic_pause_white_24dp,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused() {
|
||||
super.onPaused();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onPaused()");
|
||||
}
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
NotificationUtil.getInstance().updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
NotificationUtil.getInstance()
|
||||
.updateBackgroundPlayerNotification(R.drawable.ic_play_arrow_white_24dp,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
super.onCompleted();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "N_ onCompleted()");
|
||||
}
|
||||
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(context,
|
||||
basePlayerImpl.mediaSessionManager.getSessionToken(), basePlayerImpl,
|
||||
sharedPreferences);
|
||||
NotificationUtil.getInstance().setProgressbarOnOldNotifications(100, 100, false);
|
||||
NotificationUtil.getInstance().updateOldNotificationsThumbnail(basePlayerImpl);
|
||||
NotificationUtil.getInstance()
|
||||
.updateBackgroundPlayerNotification(R.drawable.ic_replay_white_24dp,
|
||||
getBaseContext(), basePlayerImpl, sharedPreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE;
|
||||
|
||||
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
||||
|
||||
private static final String TAG = "BackgroundPlayerActivity";
|
||||
|
|
@ -19,25 +19,25 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||
|
||||
@Override
|
||||
public String getSupportActionTitle() {
|
||||
return getResources().getString(R.string.title_activity_background_player);
|
||||
return getResources().getString(R.string.title_activity_play_queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getBindIntent() {
|
||||
return new Intent(this, BackgroundPlayer.class);
|
||||
return new Intent(this, MainPlayer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPlayerListener() {
|
||||
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
|
||||
((BackgroundPlayer.BasePlayerImpl) player).setActivityListener(this);
|
||||
if (player instanceof VideoPlayerImpl) {
|
||||
((VideoPlayerImpl) player).setActivityListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopPlayerListener() {
|
||||
if (player != null && player instanceof BackgroundPlayer.BasePlayerImpl) {
|
||||
((BackgroundPlayer.BasePlayerImpl) player).removeActivityListener(this);
|
||||
if (player instanceof VideoPlayerImpl) {
|
||||
((VideoPlayerImpl) player).removeActivityListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,18 +56,30 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
|
|||
}
|
||||
|
||||
this.player.setRecovery();
|
||||
getApplicationContext().sendBroadcast(getPlayerShutdownIntent());
|
||||
getApplicationContext().startService(
|
||||
getSwitchIntent(PopupVideoPlayer.class)
|
||||
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying())
|
||||
);
|
||||
NavigationHelper.playOnPopupPlayer(
|
||||
getApplicationContext(), player.playQueue, this.player.isPlaying());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.action_switch_background) {
|
||||
this.player.setRecovery();
|
||||
NavigationHelper.playOnBackgroundPlayer(
|
||||
getApplicationContext(), player.playQueue, this.player.isPlaying());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getPlayerShutdownIntent() {
|
||||
return new Intent(ACTION_CLOSE);
|
||||
public void setupMenu(final Menu menu) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.findItem(R.id.action_switch_popup)
|
||||
.setVisible(!((VideoPlayerImpl) player).popupPlayerSelected());
|
||||
menu.findItem(R.id.action_switch_background)
|
||||
.setVisible(!((VideoPlayerImpl) player).audioPlayerSelected());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
|||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.R;
|
||||
|
|
@ -128,13 +129,15 @@ public abstract class BasePlayer implements
|
|||
@NonNull
|
||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
@NonNull
|
||||
public static final String PLAYER_TYPE = "player_type";
|
||||
@NonNull
|
||||
public static final String IS_MUTED = "is_muted";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
|
||||
protected static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
||||
|
||||
protected PlayQueue playQueue;
|
||||
protected PlayQueueAdapter playQueueAdapter;
|
||||
|
|
@ -159,6 +162,10 @@ public abstract class BasePlayer implements
|
|||
protected static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds
|
||||
protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
|
||||
|
||||
public static final int PLAYER_TYPE_VIDEO = 0;
|
||||
public static final int PLAYER_TYPE_AUDIO = 1;
|
||||
public static final int PLAYER_TYPE_POPUP = 2;
|
||||
|
||||
protected SimpleExoPlayer simpleExoPlayer;
|
||||
protected AudioReactor audioReactor;
|
||||
protected MediaSessionManager mediaSessionManager;
|
||||
|
|
@ -173,6 +180,8 @@ public abstract class BasePlayer implements
|
|||
@NonNull
|
||||
protected final HistoryRecordManager recordManager;
|
||||
@NonNull
|
||||
protected final SharedPreferences sharedPreferences;
|
||||
@NonNull
|
||||
protected final CustomTrackSelector trackSelector;
|
||||
@NonNull
|
||||
protected final PlayerDataSource dataSource;
|
||||
|
|
@ -204,6 +213,7 @@ public abstract class BasePlayer implements
|
|||
setupBroadcastReceiver(intentFilter);
|
||||
|
||||
this.recordManager = new HistoryRecordManager(context);
|
||||
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
this.progressUpdateReactor = new SerialDisposable();
|
||||
this.databaseUpdateReactor = new CompositeDisposable();
|
||||
|
|
@ -223,7 +233,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
public void setup() {
|
||||
if (simpleExoPlayer == null) {
|
||||
initPlayer(/*playOnInit=*/true);
|
||||
initPlayer(true);
|
||||
}
|
||||
initListeners();
|
||||
}
|
||||
|
|
@ -250,7 +260,8 @@ public abstract class BasePlayer implements
|
|||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
public void initListeners() { }
|
||||
public void initListeners() {
|
||||
}
|
||||
|
||||
public void handleIntent(final Intent intent) {
|
||||
if (DEBUG) {
|
||||
|
|
@ -288,34 +299,72 @@ public abstract class BasePlayer implements
|
|||
final float playbackPitch = savedParameters.pitch;
|
||||
final boolean playbackSkipSilence = savedParameters.skipSilence;
|
||||
|
||||
final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
|
||||
|
||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||
final boolean isMuted = intent
|
||||
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
|
||||
|
||||
/*
|
||||
* There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
|
||||
* 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
|
||||
* 2. User changed a player from, for example. main to popup, or from audio to main, etc
|
||||
* 3. User chose to resume a video based on a saved timestamp from history of played videos
|
||||
* In those cases time will be saved because re-init of the play queue is a not an instant
|
||||
* task and requires network calls
|
||||
* */
|
||||
// seek to timestamp if stream is already playing
|
||||
if (simpleExoPlayer != null
|
||||
&& queue.size() == 1
|
||||
&& playQueue != null
|
||||
&& playQueue.size() == 1
|
||||
&& playQueue.getItem() != null
|
||||
&& queue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
||||
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET
|
||||
) {
|
||||
&& queue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
|
||||
// Player can have state = IDLE when playback is stopped or failed
|
||||
// and we should retry() in this case
|
||||
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
|
||||
simpleExoPlayer.retry();
|
||||
}
|
||||
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
|
||||
return;
|
||||
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false) && isPlaybackResumeEnabled()) {
|
||||
|
||||
} else if (samePlayQueue && !playQueue.isDisposed() && simpleExoPlayer != null) {
|
||||
// Do not re-init the same PlayQueue. Save time
|
||||
// Player can have state = IDLE when playback is stopped or failed
|
||||
// and we should retry() in this case
|
||||
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
|
||||
simpleExoPlayer.retry();
|
||||
}
|
||||
return;
|
||||
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
|
||||
&& isPlaybackResumeEnabled()
|
||||
&& !samePlayQueue) {
|
||||
final PlayQueueItem item = queue.getItem();
|
||||
if (item != null && item.getRecoveryPosition() == PlayQueueItem.RECOVERY_UNSET) {
|
||||
stateLoader = recordManager.loadStreamState(item)
|
||||
.observeOn(mainThread())
|
||||
.doFinally(() -> initPlayback(queue, repeatMode, playbackSpeed,
|
||||
playbackPitch, playbackSkipSilence, true, isMuted))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Do not place initPlayback() in doFinally() because
|
||||
// it restarts playback after destroy()
|
||||
//.doFinally()
|
||||
.subscribe(
|
||||
state -> queue
|
||||
.setRecovery(queue.getIndex(), state.getProgressTime()),
|
||||
state -> {
|
||||
queue.setRecovery(queue.getIndex(), state.getProgressTime());
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, true, isMuted);
|
||||
},
|
||||
error -> {
|
||||
if (DEBUG) {
|
||||
error.printStackTrace();
|
||||
}
|
||||
// In case any error we can start playback without history
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, true, isMuted);
|
||||
},
|
||||
() -> {
|
||||
// Completed but not found in history
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, true, isMuted);
|
||||
}
|
||||
);
|
||||
databaseUpdateReactor.add(stateLoader);
|
||||
|
|
@ -323,8 +372,11 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
}
|
||||
// Good to go...
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted);
|
||||
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
|
||||
initPlayback(samePlayQueue ? playQueue : queue, repeatMode,
|
||||
playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||
!intent.getBooleanExtra(START_PAUSED, false),
|
||||
isMuted);
|
||||
}
|
||||
|
||||
private PlaybackParameters retrievePlaybackParametersFromPreferences() {
|
||||
|
|
@ -410,6 +462,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
databaseUpdateReactor.clear();
|
||||
progressUpdateReactor.set(null);
|
||||
ImageLoader.getInstance().stop();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -561,7 +614,8 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
}
|
||||
|
||||
public void onPausedSeek() { }
|
||||
public void onPausedSeek() {
|
||||
}
|
||||
|
||||
public void onCompleted() {
|
||||
if (DEBUG) {
|
||||
|
|
@ -1089,6 +1143,7 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
savePlaybackState();
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
|
|
@ -1101,6 +1156,7 @@ public abstract class BasePlayer implements
|
|||
|
||||
audioReactor.abandonAudioFocus();
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
savePlaybackState();
|
||||
}
|
||||
|
||||
public void onPlayPause() {
|
||||
|
|
@ -1433,6 +1489,10 @@ public abstract class BasePlayer implements
|
|||
return simpleExoPlayer != null && simpleExoPlayer.isPlaying();
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return simpleExoPlayer != null && simpleExoPlayer.isLoading();
|
||||
}
|
||||
|
||||
@Player.RepeatMode
|
||||
public int getRepeatMode() {
|
||||
return simpleExoPlayer == null
|
||||
|
|
@ -1473,8 +1533,9 @@ public abstract class BasePlayer implements
|
|||
/**
|
||||
* Sets the playback parameters of the player, and also saves them to shared preferences.
|
||||
* Speed and pitch are rounded up to 2 decimal places before being used or saved.
|
||||
* @param speed the playback speed, will be rounded to up to 2 decimal places
|
||||
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
|
||||
*
|
||||
* @param speed the playback speed, will be rounded to up to 2 decimal places
|
||||
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
|
||||
* @param skipSilence skip silence during playback
|
||||
*/
|
||||
public void setPlaybackParameters(final float speed, final float pitch,
|
||||
|
|
@ -1490,11 +1551,11 @@ public abstract class BasePlayer implements
|
|||
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
|
||||
final boolean skipSilence) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putFloat(context.getString(R.string.playback_speed_key), speed)
|
||||
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
|
||||
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
|
||||
.apply();
|
||||
.edit()
|
||||
.putFloat(context.getString(R.string.playback_speed_key), speed)
|
||||
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
|
||||
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public PlayQueue getPlayQueue() {
|
||||
|
|
|
|||
263
app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
Normal file
263
app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||
* Part of NewPipe
|
||||
*
|
||||
* License: GPL-3.0+
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
|
||||
/**
|
||||
* One service for all players.
|
||||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
public final class MainPlayer extends Service {
|
||||
private static final String TAG = "MainPlayer";
|
||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||
|
||||
private VideoPlayerImpl playerImpl;
|
||||
private WindowManager windowManager;
|
||||
|
||||
private final IBinder mBinder = new MainPlayer.LocalBinder();
|
||||
|
||||
public enum PlayerType {
|
||||
VIDEO,
|
||||
AUDIO,
|
||||
POPUP
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Notification
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
static final String ACTION_CLOSE
|
||||
= "org.schabi.newpipe.player.MainPlayer.CLOSE";
|
||||
static final String ACTION_PLAY_PAUSE
|
||||
= "org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
|
||||
static final String ACTION_OPEN_CONTROLS
|
||||
= "org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
|
||||
static final String ACTION_REPEAT
|
||||
= "org.schabi.newpipe.player.MainPlayer.REPEAT";
|
||||
static final String ACTION_PLAY_NEXT
|
||||
= "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
|
||||
static final String ACTION_PLAY_PREVIOUS
|
||||
= "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
|
||||
static final String ACTION_FAST_REWIND
|
||||
= "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
|
||||
static final String ACTION_FAST_FORWARD
|
||||
= "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";
|
||||
static final String ACTION_BUFFERING
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_BUFFERING";
|
||||
static final String ACTION_SHUFFLE
|
||||
= "org.schabi.newpipe.player.BackgroundPlayer.ACTION_SHUFFLE";
|
||||
|
||||
static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCreate() called");
|
||||
}
|
||||
assureCorrectAppLanguage(this);
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
|
||||
ThemeHelper.setTheme(this);
|
||||
createView();
|
||||
}
|
||||
|
||||
private void createView() {
|
||||
final View layout = View.inflate(this, R.layout.player, null);
|
||||
|
||||
playerImpl = new VideoPlayerImpl(this);
|
||||
playerImpl.setup(layout);
|
||||
playerImpl.shouldUpdateOnProgress = true;
|
||||
}
|
||||
|
||||
@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 (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|
||||
&& playerImpl.playQueue == null) {
|
||||
// Player is not working, no need to process media button's action
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|
||||
|| intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) != null) {
|
||||
showNotificationAndStartForeground();
|
||||
}
|
||||
|
||||
playerImpl.handleIntent(intent);
|
||||
if (playerImpl.mediaSessionManager != null) {
|
||||
playerImpl.mediaSessionManager.handleMediaButtonIntent(intent);
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
public void stop(final boolean autoplayEnabled) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "stop() called");
|
||||
}
|
||||
|
||||
if (playerImpl.getPlayer() != null) {
|
||||
playerImpl.wasPlaying = playerImpl.getPlayer().getPlayWhenReady();
|
||||
// Releases wifi & cpu, disables keepScreenOn, etc.
|
||||
if (!autoplayEnabled) {
|
||||
playerImpl.onPause();
|
||||
}
|
||||
// We can't just pause the player here because it will make transition
|
||||
// from one stream to a new stream not smooth
|
||||
playerImpl.getPlayer().stop(false);
|
||||
playerImpl.setRecovery();
|
||||
// Android TV will handle back button in case controls will be visible
|
||||
// (one more additional unneeded click while the player is hidden)
|
||||
playerImpl.hideControls(0, 0);
|
||||
// Notification shows information about old stream but if a user selects
|
||||
// a stream from backStack it's not actual anymore
|
||||
// So we should hide the notification at all.
|
||||
// When autoplay enabled such notification flashing is annoying so skip this case
|
||||
if (!autoplayEnabled) {
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(final Intent rootIntent) {
|
||||
super.onTaskRemoved(rootIntent);
|
||||
onDestroy();
|
||||
// Unload from memory completely
|
||||
Runtime.getRuntime().halt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "destroy() called");
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(final Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Actions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private void onClose() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onClose() called");
|
||||
}
|
||||
|
||||
if (playerImpl != null) {
|
||||
removeViewFromParent();
|
||||
|
||||
playerImpl.setRecovery();
|
||||
playerImpl.savePlaybackState();
|
||||
playerImpl.stopActivityBinding();
|
||||
playerImpl.removePopupFromView();
|
||||
playerImpl.destroy();
|
||||
}
|
||||
NotificationUtil.getInstance().cancelNotification();
|
||||
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
boolean isLandscape() {
|
||||
// DisplayMetrics from activity context knows about MultiWindow feature
|
||||
// while DisplayMetrics from app context doesn't
|
||||
final DisplayMetrics metrics = (playerImpl != null
|
||||
&& playerImpl.getParentActivity() != null)
|
||||
? playerImpl.getParentActivity().getResources().getDisplayMetrics()
|
||||
: getResources().getDisplayMetrics();
|
||||
return metrics.heightPixels < metrics.widthPixels;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
if (playerImpl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return playerImpl.getRootView();
|
||||
}
|
||||
|
||||
public void removeViewFromParent() {
|
||||
if (getView().getParent() != null) {
|
||||
if (playerImpl.getParentActivity() != null) {
|
||||
// This means view was added to fragment
|
||||
final ViewGroup parent = (ViewGroup) getView().getParent();
|
||||
parent.removeView(getView());
|
||||
} else {
|
||||
// This means view was added by windowManager for popup player
|
||||
windowManager.removeViewImmediate(getView());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showNotificationAndStartForeground() {
|
||||
NotificationUtil.getInstance().recreateBackgroundPlayerNotification(playerImpl, true);
|
||||
NotificationUtil.getInstance().setProgressbarOnOldNotifications(100, 0, false);
|
||||
NotificationUtil.getInstance().startForegroundServiceWithNotification(this);
|
||||
}
|
||||
|
||||
|
||||
public class LocalBinder extends Binder {
|
||||
|
||||
public MainPlayer getService() {
|
||||
return MainPlayer.this;
|
||||
}
|
||||
|
||||
public VideoPlayerImpl getPlayer() {
|
||||
return MainPlayer.this.playerImpl;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,17 +27,21 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
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 abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
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 abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
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 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
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 abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
|
||||
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 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
||||
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
|
||||
player.getPlaybackSkipSilence(), this).show(getSupportFragmentManager(), getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -571,6 +591,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
// 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 abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@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());
|
||||
|
|
|
|||
|
|
@ -34,16 +34,16 @@ import android.os.Build;
|
|||
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.playqueue.PlayQueueItem;
|
|||
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;
|
||||
|
|
@ -182,7 +182,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
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 abstract class VideoPlayer extends BasePlayer
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
@ -520,7 +517,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
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 abstract class VideoPlayer extends BasePlayer
|
|||
+ "unappliedRotationDegrees = [" + unappliedRotationDegrees + "], "
|
||||
+ "pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
|
||||
}
|
||||
aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
|
||||
getSurfaceView().setAspectRatio(((float) width) / height);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -620,12 +616,6 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
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 abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
}
|
||||
|
||||
protected void onFullScreenButtonClicked() {
|
||||
protected void toggleFullscreen() {
|
||||
changeState(STATE_BLOCKED);
|
||||
}
|
||||
|
||||
|
|
@ -799,16 +789,16 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
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 abstract class VideoPlayer extends BasePlayer
|
|||
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
|
||||
|
|
@ -1020,6 +1010,9 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
animateView(controlsRoot, false, duration);
|
||||
};
|
||||
}
|
||||
|
||||
public abstract void hideSystemUIIfNeeded();
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getters and Setters
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -1033,11 +1026,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
this.resolver.setPlaybackQuality(quality);
|
||||
}
|
||||
|
||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
||||
return aspectRatioFrameLayout;
|
||||
}
|
||||
|
||||
public SurfaceView getSurfaceView() {
|
||||
public ExpandableSurfaceView getSurfaceView() {
|
||||
return surfaceView;
|
||||
}
|
||||
|
||||
|
|
@ -1096,7 +1085,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return playbackEndTime;
|
||||
}
|
||||
|
||||
public View getTopControlsRoot() {
|
||||
public LinearLayout getTopControlsRoot() {
|
||||
return topControlsRoot;
|
||||
}
|
||||
|
||||
|
|
@ -1108,6 +1097,10 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
return qualityPopupMenu;
|
||||
}
|
||||
|
||||
public TextView getPlaybackSpeedTextView() {
|
||||
return playbackSpeedTextView;
|
||||
}
|
||||
|
||||
public PopupMenu getPlaybackSpeedPopupMenu() {
|
||||
return playbackSpeedPopupMenu;
|
||||
}
|
||||
|
|
|
|||
2206
app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
Normal file
2206
app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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<FrameLayout> {
|
||||
|
||||
public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
boolean visible;
|
||||
Rect globalRect = new Rect();
|
||||
private boolean skippingInterception = false;
|
||||
private final List<Integer> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package org.schabi.newpipe.player.event;
|
||||
|
||||
public interface OnKeyDownListener {
|
||||
boolean onKeyDown(int keyCode);
|
||||
}
|
||||
|
|
@ -4,14 +4,13 @@ package org.schabi.newpipe.player.event;
|
|||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
|
||||
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
|
||||
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(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));
|
||||
|
||||
double popupWidth = playerImpl.getPopupWidth();
|
||||
// change co-ordinates of popup so the center stays at the same position
|
||||
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) {
|
||||
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) {
|
||||
int resId = context.getResources()
|
||||
.getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resId > 0) {
|
||||
return context.getResources().getDimensionPixelSize(resId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -92,8 +92,10 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
|
||||
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
||||
final double playbackPitch,
|
||||
final boolean playbackSkipSilence) {
|
||||
final boolean playbackSkipSilence,
|
||||
final Callback callback) {
|
||||
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||
dialog.callback = callback;
|
||||
dialog.initialTempo = playbackTempo;
|
||||
dialog.initialPitch = playbackPitch;
|
||||
|
||||
|
|
@ -111,9 +113,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||
@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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
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_MOD
|
|||
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() { }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -203,6 +216,11 @@ public final class PlayerHelper {
|
|||
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 final class PlayerHelper {
|
|||
}
|
||||
}
|
||||
|
||||
@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 final class PlayerHelper {
|
|||
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 final class PlayerHelper {
|
|||
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
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -396,6 +433,12 @@ public final class PlayerHelper {
|
|||
.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);
|
||||
|
|
|
|||
|
|
@ -51,16 +51,24 @@ public abstract class PlayQueue implements Serializable {
|
|||
|
||||
@NonNull
|
||||
private final AtomicInteger queueIndex;
|
||||
private final ArrayList<PlayQueueItem> history;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
private transient Subscription reportingReactor;
|
||||
|
||||
private transient boolean disposed;
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> 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 abstract class PlayQueue implements Serializable {
|
|||
eventBroadcast = null;
|
||||
broadcastReceiver = null;
|
||||
reportingReactor = null;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -149,6 +158,9 @@ public abstract class PlayQueue implements Serializable {
|
|||
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 abstract class PlayQueue implements Serializable {
|
|||
* @param items {@link PlayQueueItem}s to append
|
||||
*/
|
||||
public synchronized void append(@NonNull final List<PlayQueueItem> items) {
|
||||
List<PlayQueueItem> itemList = new ArrayList<>(items);
|
||||
final List<PlayQueueItem> itemList = new ArrayList<>(items);
|
||||
|
||||
if (isShuffled()) {
|
||||
backup.addAll(itemList);
|
||||
|
|
@ -314,6 +326,9 @@ public abstract class PlayQueue implements Serializable {
|
|||
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 @@ public abstract class PlayQueue implements Serializable {
|
|||
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 abstract class PlayQueue implements Serializable {
|
|||
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 abstract class PlayQueue implements Serializable {
|
|||
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 abstract class PlayQueue implements Serializable {
|
|||
} 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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue