From f284a799efa389ebb4aea5cc05d66892db719750 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 26 Oct 2017 19:59:25 -0700 Subject: [PATCH] -Added wake and wifi lock to popup video player. -Added seek time display to player binding activity. -Added button effect for all image buttons on player binding activity. -Added click to scroll to current selected on metadata layout for player binding activity. -Refactored player utilities and preference getters into PlayerHelper. -Refactored player caching into CacheFactory. -Refactored player audio related methods into AudioReactor. -Refactored player locks into LockManager. -Refactored player loading and buffering mechanics into LoadController. -Fixed outdated names for background player. --- .../newpipe/player/BackgroundPlayer.java | 117 ++++------ .../org/schabi/newpipe/player/BasePlayer.java | 211 ++++-------------- .../newpipe/player/MainVideoPlayer.java | 20 +- .../newpipe/player/PopupVideoPlayer.java | 43 ++-- .../newpipe/player/ServicePlayerActivity.java | 52 +++-- .../schabi/newpipe/player/VideoPlayer.java | 3 +- .../DeferredMediaSource.java | 2 +- .../player/playback/MediaSourceManager.java | 5 +- .../newpipe/player/refactor/AudioReactor.java | 182 +++++++++++++++ .../newpipe/player/refactor/CacheFactory.java | 85 +++++++ .../player/refactor/LoadController.java | 76 +++++++ .../newpipe/player/refactor/LockManager.java | 44 ++++ .../newpipe/player/refactor/PlayerHelper.java | 103 +++++++++ .../activity_player_queue_control.xml | 42 +++- .../layout/activity_player_queue_control.xml | 94 +++++--- .../res/layout/player_popup_notification.xml | 4 + 16 files changed, 747 insertions(+), 336 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{mediasource => playback}/DeferredMediaSource.java (99%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/refactor/AudioReactor.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/refactor/CacheFactory.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/refactor/LoadController.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/refactor/LockManager.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/refactor/PlayerHelper.java diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 2881dd69c..69b91b970 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -26,10 +26,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; -import android.net.wifi.WifiManager; import android.os.Build; import android.os.IBinder; -import android.os.PowerManager; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -48,10 +46,13 @@ import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.event.PlayerEventListener; +import org.schabi.newpipe.player.refactor.LockManager; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ThemeHelper; +import static org.schabi.newpipe.player.refactor.PlayerHelper.getTimeString; + /** * Base players joining the common properties @@ -64,18 +65,13 @@ public final class BackgroundPlayer extends Service { 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_OPEN_DETAIL = "org.schabi.newpipe.player.BackgroundPlayer.OPEN_DETAIL"; + public static final String ACTION_OPEN_CONTROLS = "org.schabi.newpipe.player.BackgroundPlayer.OPEN_CONTROLS"; public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT"; - 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_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"; private BasePlayerImpl basePlayerImpl; - private PowerManager powerManager; - private WifiManager wifiManager; - - private PowerManager.WakeLock wakeLock; - private WifiManager.WifiLock wifiLock; - + private LockManager lockManager; /*////////////////////////////////////////////////////////////////////////// // Service-Activity Binder //////////////////////////////////////////////////////////////////////////*/ @@ -93,7 +89,6 @@ public final class BackgroundPlayer extends Service { private RemoteViews notRemoteView; private RemoteViews bigNotRemoteView; private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha"; - private final String setImageResourceMethodName = "setImageResource"; private boolean shouldUpdateOnProgress; @@ -105,11 +100,10 @@ public final class BackgroundPlayer extends Service { public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate() called"); notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); - powerManager = ((PowerManager) getSystemService(POWER_SERVICE)); - wifiManager = ((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE)); + lockManager = new LockManager(this); ThemeHelper.setTheme(this); - basePlayerImpl = new BasePlayerImpl(getApplicationContext()); + basePlayerImpl = new BasePlayerImpl(this); basePlayerImpl.setup(); mBinder = new PlayerServiceBinder(basePlayerImpl); @@ -150,8 +144,9 @@ public final class BackgroundPlayer extends Service { private void onClose() { if (DEBUG) Log.d(TAG, "onClose() called"); - releaseWifiAndCpu(); - + if (lockManager != null) { + lockManager.releaseWifiAndCpu(); + } if (basePlayerImpl != null) { basePlayerImpl.stopActivityBinding(); basePlayerImpl.destroy(); @@ -159,6 +154,7 @@ public final class BackgroundPlayer extends Service { if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); mBinder = null; basePlayerImpl = null; + lockManager = null; stopForeground(true); stopSelf(); @@ -198,19 +194,22 @@ public final class BackgroundPlayer extends Service { } private void setupNotification(RemoteViews remoteViews) { + bigNotRemoteView.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle()); + bigNotRemoteView.setTextViewText(R.id.notificationArtist, basePlayerImpl.getVideoTitle()); + remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationStop, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationContent, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_CONTROLS), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationRepeat, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT)); remoteViews.setOnClickPendingIntent(R.id.notificationFForward, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT)); setRepeatModeIcon(remoteViews, basePlayerImpl.getRepeatMode()); } @@ -244,36 +243,18 @@ public final class BackgroundPlayer extends Service { // Utils //////////////////////////////////////////////////////////////////////////*/ - private void lockWifiAndCpu() { - if (DEBUG) Log.d(TAG, "lockWifiAndCpu() called"); - if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return; - - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - - if (wakeLock != null) wakeLock.acquire(); - if (wifiLock != null) wifiLock.acquire(); - } - - private void releaseWifiAndCpu() { - if (DEBUG) Log.d(TAG, "releaseWifiAndCpu() called"); - if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); - if (wifiLock != null && wifiLock.isHeld()) wifiLock.release(); - - wakeLock = null; - wifiLock = null; - } - private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) { + final String methodName = "setImageResource"; + switch (repeatMode) { case Player.REPEAT_MODE_OFF: - remoteViews.setInt(R.id.notificationRepeat, setImageResourceMethodName, R.drawable.exo_controls_repeat_off); + remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off); break; case Player.REPEAT_MODE_ONE: - remoteViews.setInt(R.id.notificationRepeat, setImageResourceMethodName, R.drawable.exo_controls_repeat_one); + remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one); break; case Player.REPEAT_MODE_ALL: - remoteViews.setInt(R.id.notificationRepeat, setImageResourceMethodName, R.drawable.exo_controls_repeat_all); + remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all); break; } } @@ -336,23 +317,12 @@ public final class BackgroundPlayer extends Service { @Override public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { updateProgress(currentProgress, duration, bufferPercent); - if (!shouldUpdateOnProgress) return; - - resetNotification(); if (bigNotRemoteView != null) { - if (currentItem != null) { - bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); - bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName()); - } bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration)); } if (notRemoteView != null) { - if (currentItem != null) { - notRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle()); - notRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName()); - } notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); } @@ -360,18 +330,14 @@ public final class BackgroundPlayer extends Service { } @Override - public void onFastRewind() { - if (!isPlayerReady()) return; - - onPlayPrevious(); + public void onPlayPrevious() { + super.onPlayPrevious(); triggerProgressUpdate(); } @Override - public void onFastForward() { - if (!isPlayerReady()) return; - - onPlayNext(); + public void onPlayNext() { + super.onPlayNext(); triggerProgressUpdate(); } @@ -464,14 +430,14 @@ public final class BackgroundPlayer extends Service { // Activity Event Listener //////////////////////////////////////////////////////////////////////////*/ - public void setActivityListener(PlayerEventListener listener) { + /*package-private*/ void setActivityListener(PlayerEventListener listener) { activityListener = listener; updateMetadata(); updatePlayback(); triggerProgressUpdate(); } - public void removeActivityListener(PlayerEventListener listener) { + /*package-private*/ void removeActivityListener(PlayerEventListener listener) { if (activityListener == listener) { activityListener = null; } @@ -511,10 +477,10 @@ public final class BackgroundPlayer extends Service { super.setupBroadcastReceiver(intentFilter); intentFilter.addAction(ACTION_CLOSE); intentFilter.addAction(ACTION_PLAY_PAUSE); - intentFilter.addAction(ACTION_OPEN_DETAIL); + intentFilter.addAction(ACTION_OPEN_CONTROLS); intentFilter.addAction(ACTION_REPEAT); - intentFilter.addAction(ACTION_FAST_FORWARD); - intentFilter.addAction(ACTION_FAST_REWIND); + intentFilter.addAction(ACTION_PLAY_PREVIOUS); + intentFilter.addAction(ACTION_PLAY_NEXT); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); @@ -525,6 +491,7 @@ public final class BackgroundPlayer extends Service { @Override public void onBroadcastReceived(Intent intent) { super.onBroadcastReceived(intent); + if (intent == null || intent.getAction() == null) return; if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]"); switch (intent.getAction()) { case ACTION_CLOSE: @@ -533,17 +500,17 @@ public final class BackgroundPlayer extends Service { case ACTION_PLAY_PAUSE: onVideoPlayPause(); break; - case ACTION_OPEN_DETAIL: + case ACTION_OPEN_CONTROLS: openControl(getApplicationContext()); break; case ACTION_REPEAT: onRepeatClicked(); break; - case ACTION_FAST_REWIND: - onFastRewind(); + case ACTION_PLAY_NEXT: + onPlayNext(); break; - case ACTION_FAST_FORWARD: - onFastForward(); + case ACTION_PLAY_PREVIOUS: + onPlayPrevious(); break; case Intent.ACTION_SCREEN_ON: onScreenOnOff(true); @@ -579,7 +546,7 @@ public final class BackgroundPlayer extends Service { setControlsOpacity(255); updateNotification(R.drawable.ic_pause_white); - lockWifiAndCpu(); + lockManager.acquireWifiAndCpu(); } @Override @@ -589,7 +556,7 @@ public final class BackgroundPlayer extends Service { updateNotification(R.drawable.ic_play_arrow_white); if (isProgressLoopRunning()) stopProgressLoop(); - releaseWifiAndCpu(); + lockManager.releaseWifiAndCpu(); } @Override @@ -601,7 +568,7 @@ public final class BackgroundPlayer extends Service { if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false); updateNotification(R.drawable.ic_replay_white); - releaseWifiAndCpu(); + lockManager.releaseWifiAndCpu(); } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 67f4fc311..2e3fd1303 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -29,7 +29,6 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.media.AudioManager; -import android.media.audiofx.AudioEffect; import android.net.Uri; import android.preference.PreferenceManager; import android.support.annotation.Nullable; @@ -39,18 +38,15 @@ import android.view.View; import android.widget.Toast; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.audio.AudioRendererEventListener; -import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; @@ -63,31 +59,23 @@ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.cache.CacheDataSource; -import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; -import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; -import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; -import org.schabi.newpipe.Downloader; -import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; +import org.schabi.newpipe.player.refactor.AudioReactor; +import org.schabi.newpipe.player.refactor.CacheFactory; +import org.schabi.newpipe.player.refactor.LoadController; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueAdapter; import org.schabi.newpipe.playlist.PlayQueueItem; -import java.io.File; import java.io.Serializable; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.Formatter; -import java.util.Locale; import java.util.concurrent.TimeUnit; import io.reactivex.Observable; @@ -97,22 +85,21 @@ import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.functions.Predicate; +import static org.schabi.newpipe.player.refactor.PlayerHelper.getTimeString; + /** * Base for the players, joining the common properties * * @author mauriciocolli */ @SuppressWarnings({"WeakerAccess", "unused"}) -public abstract class BasePlayer implements Player.EventListener, - AudioManager.OnAudioFocusChangeListener, PlaybackListener, AudioRendererEventListener { - // TODO: Check api version for deprecated audio manager methods +public abstract class BasePlayer implements Player.EventListener, PlaybackListener { public static final boolean DEBUG = true; public static final String TAG = "BasePlayer"; protected Context context; protected SharedPreferences sharedPreferences; - protected AudioManager audioManager; protected BroadcastReceiver broadcastReceiver; protected IntentFilter intentFilter; @@ -144,6 +131,7 @@ public abstract class BasePlayer implements Player.EventListener, protected PlayQueueItem currentItem; protected Toast errorToast; + /*////////////////////////////////////////////////////////////////////////// // Player //////////////////////////////////////////////////////////////////////////*/ @@ -151,15 +139,15 @@ public abstract class BasePlayer implements Player.EventListener, protected final static int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds protected final static int PLAY_PREV_ACTIVATION_LIMIT = 5000; // 5 seconds protected final static int PROGRESS_LOOP_INTERVAL = 500; - protected final static String CACHE_FOLDER_NAME = "exoplayer"; protected SimpleExoPlayer simpleExoPlayer; + protected AudioReactor audioReactor; + protected boolean isPrepared = false; protected DefaultTrackSelector trackSelector; - protected CacheDataSourceFactory cacheDataSourceFactory; - protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); - protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + protected DataSource.Factory cacheDataSourceFactory; + protected DefaultExtractorsFactory extractorsFactory; protected Disposable progressUpdateReactor; @@ -185,37 +173,21 @@ public abstract class BasePlayer implements Player.EventListener, initListeners(); } - private void initExoPlayerCache() { - if (cacheDataSourceFactory == null) { - DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Downloader.USER_AGENT, bandwidthMeter); - File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME); - if (!cacheDir.exists()) { - //noinspection ResultOfMethodCallIgnored - cacheDir.mkdir(); - } - - if (DEBUG) Log.d(TAG, "initExoPlayerCache: cacheDir = " + cacheDir.getAbsolutePath()); - SimpleCache simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(64 * 1024 * 1024L)); - cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE, 512 * 1024); - } - } - public void initPlayer() { if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]"); - initExoPlayerCache(); - - if (audioManager == null) { - this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); - } - - AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); - trackSelector = new DefaultTrackSelector(trackSelectionFactory); - - DefaultLoadControl loadControl = new DefaultLoadControl(); + final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + final AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); + final LoadControl loadControl = new LoadController(context); final RenderersFactory renderFactory = new DefaultRenderersFactory(context); + + trackSelector = new DefaultTrackSelector(trackSelectionFactory); + extractorsFactory = new DefaultExtractorsFactory(); + cacheDataSourceFactory = new CacheFactory(context); + simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl); - simpleExoPlayer.setAudioDebugListener(this); + audioReactor = new AudioReactor(context, simpleExoPlayer); + simpleExoPlayer.addListener(this); simpleExoPlayer.setPlayWhenReady(true); } @@ -306,10 +278,7 @@ public abstract class BasePlayer implements Player.EventListener, simpleExoPlayer.release(); } if (isProgressLoopRunning()) stopProgressLoop(); - if (audioManager != null) { - audioManager.abandonAudioFocus(this); - audioManager = null; - } + if (audioReactor != null) audioReactor.abandonAudioFocus(); } public void destroy() { @@ -318,14 +287,8 @@ public abstract class BasePlayer implements Player.EventListener, clearThumbnailCache(); unregisterBroadcastReceiver(); - if (playQueue != null) { - playQueue.dispose(); - playQueue = null; - } - if (playbackManager != null) { - playbackManager.dispose(); - playbackManager = null; - } + if (playQueue != null) playQueue.dispose(); + if (playbackManager != null) playbackManager.dispose(); trackSelector = null; simpleExoPlayer = null; @@ -372,6 +335,7 @@ public abstract class BasePlayer implements Player.EventListener, } public void onBroadcastReceived(Intent intent) { + if (intent == null || intent.getAction() == null) return; switch (intent.getAction()) { case AudioManager.ACTION_AUDIO_BECOMING_NOISY: if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false); @@ -386,84 +350,6 @@ public abstract class BasePlayer implements Player.EventListener, } } - /*////////////////////////////////////////////////////////////////////////// - // AudioFocus - //////////////////////////////////////////////////////////////////////////*/ - - private static final int DUCK_DURATION = 1500; - private static final float DUCK_AUDIO_TO = .2f; - - @Override - public void onAudioFocusChange(int focusChange) { - if (DEBUG) Log.d(TAG, "onAudioFocusChange() called with: focusChange = [" + focusChange + "]"); - if (simpleExoPlayer == null) return; - switch (focusChange) { - case AudioManager.AUDIOFOCUS_GAIN: - onAudioFocusGain(); - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - onAudioFocusLossCanDuck(); - break; - case AudioManager.AUDIOFOCUS_LOSS: - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - onAudioFocusLoss(); - break; - } - } - - private boolean isResumeAfterAudioFocusGain() { - return sharedPreferences != null && context != null - && sharedPreferences.getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), false); - } - - protected void onAudioFocusGain() { - if (DEBUG) Log.d(TAG, "onAudioFocusGain() called"); - if (simpleExoPlayer != null) simpleExoPlayer.setVolume(DUCK_AUDIO_TO); - animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION); - - if (isResumeAfterAudioFocusGain()) { - simpleExoPlayer.setPlayWhenReady(true); - } - } - - protected void onAudioFocusLoss() { - if (DEBUG) Log.d(TAG, "onAudioFocusLoss() called"); - simpleExoPlayer.setPlayWhenReady(false); - } - - protected void onAudioFocusLossCanDuck() { - if (DEBUG) Log.d(TAG, "onAudioFocusLossCanDuck() called"); - // Set the volume to 1/10 on ducking - animateAudio(simpleExoPlayer.getVolume(), DUCK_AUDIO_TO, DUCK_DURATION); - } - - /*////////////////////////////////////////////////////////////////////////// - // Audio Processing - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onAudioEnabled(DecoderCounters decoderCounters) {} - - @Override - public void onAudioSessionId(int i) { - final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i); - intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); - context.sendBroadcast(intent); - } - - @Override - public void onAudioDecoderInitialized(String s, long l, long l1) {} - - @Override - public void onAudioInputFormatChanged(Format format) {} - - @Override - public void onAudioTrackUnderrun(int i, long l, long l1) {} - - @Override - public void onAudioDisabled(DecoderCounters decoderCounters) {} - /*////////////////////////////////////////////////////////////////////////// // States Implementation //////////////////////////////////////////////////////////////////////////*/ @@ -790,7 +676,7 @@ public abstract class BasePlayer implements Player.EventListener, public void onPrepared(boolean playWhenReady) { if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); - if (playWhenReady) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + if (playWhenReady) audioReactor.requestAudioFocus(); changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); } @@ -799,8 +685,11 @@ public abstract class BasePlayer implements Player.EventListener, public void onVideoPlayPause() { if (DEBUG) Log.d(TAG, "onVideoPlayPause() called"); - if (!isPlaying()) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - else audioManager.abandonAudioFocus(this); + if (!isPlaying()) { + audioReactor.requestAudioFocus(); + } else { + audioReactor.abandonAudioFocus(); + } if (getCurrentState() == STATE_COMPLETED) { playQueue.setIndex(0); @@ -869,32 +758,6 @@ public abstract class BasePlayer implements Player.EventListener, // Utils //////////////////////////////////////////////////////////////////////////*/ - private final StringBuilder stringBuilder = new StringBuilder(); - private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault()); - private final NumberFormat speedFormatter = new DecimalFormat("0.##x"); - private final NumberFormat pitchFormatter = new DecimalFormat("##%"); - - // todo: merge this into Localization - public String getTimeString(int milliSeconds) { - long seconds = (milliSeconds % 60000L) / 1000L; - long minutes = (milliSeconds % 3600000L) / 60000L; - long hours = (milliSeconds % 86400000L) / 3600000L; - long days = (milliSeconds % (86400000L * 7L)) / 86400000L; - - stringBuilder.setLength(0); - return days > 0 ? formatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString() - : hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() - : formatter.format("%02d:%02d", minutes, seconds).toString(); - } - - protected String formatSpeed(float speed) { - return speedFormatter.format(speed); - } - - protected String formatPitch(float pitch) { - return pitchFormatter.format(pitch); - } - protected void reload() { if (playbackManager != null) { playbackManager.reset(); @@ -965,6 +828,10 @@ public abstract class BasePlayer implements Player.EventListener, return sharedPreferences; } + public AudioReactor getAudioReactor() { + return audioReactor; + } + public int getCurrentState() { return currentState; } @@ -1026,6 +893,10 @@ public abstract class BasePlayer implements Player.EventListener, return playQueue; } + public PlayQueueAdapter getPlayQueueAdapter() { + return playQueueAdapter; + } + public boolean isPlayerReady() { return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED; } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 230b963a0..acdca3d67 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -46,11 +46,11 @@ import android.widget.Toast; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.player.refactor.PlayerHelper; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; import org.schabi.newpipe.playlist.PlayQueueItemHolder; @@ -100,7 +100,7 @@ public final class MainVideoPlayer extends Activity { showSystemUi(); setContentView(R.layout.activity_main_player); - playerImpl = new VideoPlayerImpl(getApplicationContext()); + playerImpl = new VideoPlayerImpl(this); playerImpl.setup(findViewById(android.R.id.content)); playerImpl.handleIntent(getIntent()); } @@ -723,12 +723,12 @@ public final class MainVideoPlayer extends Activity { return true; } - private final boolean isGestureControlsEnabled = playerImpl.getSharedPreferences().getBoolean(getString(R.string.player_gesture_controls_key), true); + private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext()); private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f; private float currentBrightness = .5f; - private int currentVolume, maxVolume = playerImpl.audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + private int currentVolume, maxVolume = playerImpl.getAudioReactor().getMaxVolume(); private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0; private final String brightnessUnicode = new String(Character.toChars(0x2600)); @@ -742,7 +742,7 @@ public final class MainVideoPlayer extends Activity { // TODO: Improve video gesture controls @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!isGestureControlsEnabled) return false; + if (!isPlayerGestureEnabled) return false; //noinspection PointlessBooleanExpression if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " + @@ -763,13 +763,14 @@ public final class MainVideoPlayer extends Activity { if (e1.getX() > playerImpl.getRootView().getWidth() / 2) { double floor = Math.floor(up ? stepVolume : -stepVolume); - currentVolume = (int) (playerImpl.audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + floor); + currentVolume = (int) (playerImpl.getAudioReactor().getMaxVolume() + floor); if (currentVolume >= maxVolume) currentVolume = maxVolume; if (currentVolume <= minVolume) currentVolume = (int) minVolume; - playerImpl.audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0); + playerImpl.getAudioReactor().setMaxVolume(currentVolume); if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); - playerImpl.getVolumeTextView().setText(volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%"); + final String volumeText = volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%"; + playerImpl.getVolumeTextView().setText(volumeText); if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200); if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE); @@ -784,7 +785,8 @@ public final class MainVideoPlayer extends Activity { if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness); int brightnessNormalized = Math.round(currentBrightness * 100); - playerImpl.getBrightnessTextView().setText(brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%"); + final String brightnessText = brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%"; + playerImpl.getBrightnessTextView().setText(brightnessText); if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200); if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 7623b1d74..219754ac3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -66,6 +66,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.old.PlayVideoActivity; +import org.schabi.newpipe.player.refactor.LockManager; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.report.ErrorActivity; @@ -84,6 +85,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; +import static org.schabi.newpipe.player.refactor.PlayerHelper.isUsingOldPlayer; import static org.schabi.newpipe.util.AnimationUtils.animateView; /** @@ -99,7 +101,7 @@ public final class PopupVideoPlayer extends Service { private static final int NOTIFICATION_ID = 40028922; public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE"; public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE"; - public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL"; + public static final String ACTION_OPEN_CONTROLS = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_CONTROLS"; public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT"; private static final String POPUP_SAVED_WIDTH = "popup_saved_width"; @@ -116,15 +118,13 @@ public final class PopupVideoPlayer extends Service { private float minimumWidth, minimumHeight; private float maximumWidth, maximumHeight; - private final String setImageResourceMethodName = "setImageResource"; - private NotificationManager notificationManager; private NotificationCompat.Builder notBuilder; private RemoteViews notRemoteView; private VideoPlayerImpl playerImpl; private Disposable currentWorker; - + private LockManager lockManager; /*////////////////////////////////////////////////////////////////////////// // Service-Activity Binder //////////////////////////////////////////////////////////////////////////*/ @@ -141,7 +141,8 @@ public final class PopupVideoPlayer extends Service { windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); - playerImpl = new VideoPlayerImpl(getApplicationContext()); + lockManager = new LockManager(this); + playerImpl = new VideoPlayerImpl(this); ThemeHelper.setTheme(this); mBinder = new PlayerServiceBinder(playerImpl); @@ -261,7 +262,7 @@ public final class PopupVideoPlayer extends Service { notRemoteView.setOnClickPendingIntent(R.id.notificationStop, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT)); notRemoteView.setOnClickPendingIntent(R.id.notificationContent, - PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT)); + PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_CONTROLS), PendingIntent.FLAG_UPDATE_CURRENT)); notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat, PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT)); @@ -302,6 +303,7 @@ public final class PopupVideoPlayer extends Service { playerImpl.stopActivityBinding(); playerImpl.destroy(); } + if (lockManager != null) lockManager.releaseWifiAndCpu(); if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); if (currentWorker != null) currentWorker.dispose(); mBinder = null; @@ -381,17 +383,19 @@ public final class PopupVideoPlayer extends Service { } protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) { + final String methodName = "setImageResource"; + if (remoteViews == null) return; switch (repeatMode) { case Player.REPEAT_MODE_OFF: - remoteViews.setInt(R.id.notificationRepeat, setImageResourceMethodName, R.drawable.exo_controls_repeat_off); + remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_off); break; case Player.REPEAT_MODE_ONE: - remoteViews.setInt(R.id.notificationRepeat, setImageResourceMethodName, R.drawable.exo_controls_repeat_one); + remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_one); break; case Player.REPEAT_MODE_ALL: - remoteViews.setInt(R.id.notificationRepeat, setImageResourceMethodName, R.drawable.exo_controls_repeat_all); + remoteViews.setInt(R.id.notificationRepeat, methodName, R.drawable.exo_controls_repeat_all); break; } } @@ -447,7 +451,7 @@ public final class PopupVideoPlayer extends Service { setRecovery(); Intent intent; - if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) { + if (!isUsingOldPlayer(getApplicationContext())) { intent = NavigationHelper.getPlayerIntent( context, MainVideoPlayer.class, @@ -516,8 +520,8 @@ public final class PopupVideoPlayer extends Service { @Override public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { - super.onUpdateProgress(currentProgress, duration, bufferPercent); updateProgress(currentProgress, duration, bufferPercent); + super.onUpdateProgress(currentProgress, duration, bufferPercent); } @Override @@ -535,14 +539,14 @@ public final class PopupVideoPlayer extends Service { // Activity Event Listener //////////////////////////////////////////////////////////////////////////*/ - public void setActivityListener(PlayerEventListener listener) { + /*package-private*/ void setActivityListener(PlayerEventListener listener) { activityListener = listener; updateMetadata(); updatePlayback(); triggerProgressUpdate(); } - public void removeActivityListener(PlayerEventListener listener) { + /*package-private*/ void removeActivityListener(PlayerEventListener listener) { if (activityListener == listener) { activityListener = null; } @@ -617,7 +621,7 @@ public final class PopupVideoPlayer extends Service { if (DEBUG) Log.d(TAG, "setupBroadcastReceiver() called with: intentFilter = [" + intentFilter + "]"); intentFilter.addAction(ACTION_CLOSE); intentFilter.addAction(ACTION_PLAY_PAUSE); - intentFilter.addAction(ACTION_OPEN_DETAIL); + intentFilter.addAction(ACTION_OPEN_CONTROLS); intentFilter.addAction(ACTION_REPEAT); intentFilter.addAction(Intent.ACTION_SCREEN_ON); @@ -636,7 +640,7 @@ public final class PopupVideoPlayer extends Service { case ACTION_PLAY_PAUSE: onVideoPlayPause(); break; - case ACTION_OPEN_DETAIL: + case ACTION_OPEN_CONTROLS: openControl(getApplicationContext()); break; case ACTION_REPEAT: @@ -671,6 +675,7 @@ public final class PopupVideoPlayer extends Service { public void onPlaying() { super.onPlaying(); updateNotification(R.drawable.ic_pause_white); + lockManager.acquireWifiAndCpu(); } @Override @@ -684,6 +689,7 @@ public final class PopupVideoPlayer extends Service { super.onPaused(); updateNotification(R.drawable.ic_play_arrow_white); showAndAnimateControl(R.drawable.ic_play_arrow_white, false); + lockManager.releaseWifiAndCpu(); } @Override @@ -697,13 +703,14 @@ public final class PopupVideoPlayer extends Service { super.onCompleted(); updateNotification(R.drawable.ic_replay_white); showAndAnimateControl(R.drawable.ic_replay_white, false); + lockManager.releaseWifiAndCpu(); } /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ - public void enableVideoRenderer(final boolean enable) { + /*package-private*/ void enableVideoRenderer(final boolean enable) { final int videoRendererIndex = getVideoRendererIndex(); if (trackSelector != null && videoRendererIndex != -1) { trackSelector.setRendererDisabled(videoRendererIndex, !enable); @@ -894,7 +901,7 @@ public final class PopupVideoPlayer extends Service { this.serviceId = serviceId; } - public void onReceive(final StreamInfo info) { + /*package-private*/ void onReceive(final StreamInfo info) { mainHandler.post(new Runnable() { @Override public void run() { @@ -929,7 +936,7 @@ public final class PopupVideoPlayer extends Service { stopSelf(); } - public void onReCaptchaException() { + /*package-private*/ void onReCaptchaException() { Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); // Starting ReCaptcha Challenge Activity Intent intent = new Intent(context, ReCaptchaActivity.class); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 012f47cd4..cc712b1b8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -17,6 +17,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.ProgressBar; import android.widget.SeekBar; @@ -35,6 +36,9 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; +import static org.schabi.newpipe.player.refactor.PlayerHelper.formatPitch; +import static org.schabi.newpipe.player.refactor.PlayerHelper.formatSpeed; + public abstract class ServicePlayerActivity extends AppCompatActivity implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener { @@ -58,12 +62,14 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private RecyclerView itemsList; private ItemTouchHelper itemTouchHelper; + private LinearLayout metadata; private TextView metadataTitle; private TextView metadataArtist; private SeekBar progressSeekBar; private TextView progressCurrentTime; private TextView progressEndTime; + private TextView seekDisplay; private ImageButton repeatButton; private ImageButton backwardButton; @@ -113,11 +119,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity bind(); } - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_play_queue, menu); @@ -185,7 +186,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity player = ((PlayerServiceBinder) service).getPlayerInstance(); } - if (player == null || player.playQueue == null || player.playQueueAdapter == null || player.simpleExoPlayer == null) { + if (player == null || player.getPlayQueue() == null || + player.getPlayQueueAdapter() == null || player.getPlayer() == null) { unbind(); finish(); } else { @@ -210,25 +212,29 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private void buildQueue() { itemsList = findViewById(R.id.play_queue); itemsList.setLayoutManager(new LinearLayoutManager(this)); - itemsList.setAdapter(player.playQueueAdapter); + itemsList.setAdapter(player.getPlayQueueAdapter()); itemsList.setClickable(true); itemsList.setLongClickable(true); itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(itemsList); - player.playQueueAdapter.setSelectedListener(getOnSelectedListener()); + player.getPlayQueueAdapter().setSelectedListener(getOnSelectedListener()); } private void buildMetadata() { + metadata = rootView.findViewById(R.id.metadata); metadataTitle = rootView.findViewById(R.id.song_name); metadataArtist = rootView.findViewById(R.id.artist_name); + + metadata.setOnClickListener(this); } private void buildSeekBar() { progressCurrentTime = rootView.findViewById(R.id.current_time); progressSeekBar = rootView.findViewById(R.id.seek_bar); progressEndTime = rootView.findViewById(R.id.end_time); + seekDisplay = rootView.findViewById(R.id.seek_display); progressSeekBar.setOnSeekBarChangeListener(this); } @@ -263,7 +269,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity playbackSpeedPopupMenu.getMenu().removeGroup(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID); for (int i = 0; i < BasePlayer.PLAYBACK_SPEEDS.length; i++) { final float playbackSpeed = BasePlayer.PLAYBACK_SPEEDS[i]; - final String formattedSpeed = player.formatSpeed(playbackSpeed); + final String formattedSpeed = formatSpeed(playbackSpeed); final MenuItem item = playbackSpeedPopupMenu.getMenu().add(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedSpeed); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override @@ -281,7 +287,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity playbackPitchPopupMenu.getMenu().removeGroup(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID); for (int i = 0; i < BasePlayer.PLAYBACK_PITCHES.length; i++) { final float playbackPitch = BasePlayer.PLAYBACK_PITCHES[i]; - final String formattedPitch = player.formatPitch(playbackPitch); + final String formattedPitch = formatPitch(playbackPitch); final MenuItem item = playbackPitchPopupMenu.getMenu().add(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedPitch); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override @@ -299,8 +305,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity remove.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { - final int index = player.playQueue.indexOf(item); - if (index != -1) player.playQueue.remove(index); + final int index = player.getPlayQueue().indexOf(item); + if (index != -1) player.getPlayQueue().remove(index); return true; } }); @@ -331,7 +337,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity final int sourceIndex = source.getLayoutPosition(); final int targetIndex = target.getLayoutPosition(); - player.playQueue.move(sourceIndex, targetIndex); + player.getPlayQueue().move(sourceIndex, targetIndex); return true; } @@ -359,7 +365,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity @Override public void held(PlayQueueItem item, View view) { - final int index = player.playQueue.indexOf(item); + final int index = player.getPlayQueue().indexOf(item); if (index != -1) buildItemPopupMenu(item, view); } @@ -375,7 +381,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } private void scrollToSelected() { - itemsList.smoothScrollToPosition(player.playQueue.getIndex()); + itemsList.smoothScrollToPosition(player.getPlayQueue().getIndex()); } //////////////////////////////////////////////////////////////////////////// @@ -404,6 +410,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } else if (view.getId() == playbackPitchButton.getId()) { playbackPitchPopupMenu.show(); + + } else if (view.getId() == metadata.getId()) { + scrollToSelected(); + } } @@ -413,17 +423,23 @@ public abstract class ServicePlayerActivity extends AppCompatActivity @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser) progressCurrentTime.setText(Localization.getDurationString(progress / 1000)); + if (fromUser) { + final String seekTime = Localization.getDurationString(progress / 1000); + progressCurrentTime.setText(seekTime); + seekDisplay.setText(seekTime); + } } @Override public void onStartTrackingTouch(SeekBar seekBar) { seeking = true; + seekDisplay.setVisibility(View.VISIBLE); } @Override public void onStopTrackingTouch(SeekBar seekBar) { player.simpleExoPlayer.seekTo(seekBar.getProgress()); + seekDisplay.setVisibility(View.GONE); seeking = false; } @@ -528,8 +544,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private void onPlaybackParameterChanged(final PlaybackParameters parameters) { if (parameters != null) { - playbackSpeedButton.setText(player.formatSpeed(parameters.speed)); - playbackPitchButton.setText(player.formatPitch(parameters.pitch)); + playbackSpeedButton.setText(formatSpeed(parameters.speed)); + playbackPitchButton.setText(formatPitch(parameters.pitch)); } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index aebc1b55c..74c8f6ac8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -49,7 +49,6 @@ import android.widget.TextView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; @@ -66,6 +65,8 @@ import org.schabi.newpipe.util.ListHelper; import java.util.ArrayList; import java.util.List; +import static org.schabi.newpipe.player.refactor.PlayerHelper.formatSpeed; +import static org.schabi.newpipe.player.refactor.PlayerHelper.getTimeString; import static org.schabi.newpipe.util.AnimationUtils.animateView; /** diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java similarity index 99% rename from app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java rename to app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java index c7e1c5754..b0990f56a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/DeferredMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/DeferredMediaSource.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.mediasource; +package org.schabi.newpipe.player.playback; import android.support.annotation.NonNull; import android.util.Log; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 9a92dc27d..9d778155c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -9,7 +9,6 @@ import com.google.android.exoplayer2.source.MediaSource; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.mediasource.DeferredMediaSource; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.events.MoveEvent; @@ -29,8 +28,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback { // One-side rolling window size for default loading // Effectively loads windowSize * 2 + 1 streams, must be greater than 0 private final int windowSize; - private PlaybackListener playbackListener; - private PlayQueue playQueue; + private final PlaybackListener playbackListener; + private final PlayQueue playQueue; private DynamicConcatenatingMediaSource sources; diff --git a/app/src/main/java/org/schabi/newpipe/player/refactor/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/refactor/AudioReactor.java new file mode 100644 index 000000000..ff56503e2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/refactor/AudioReactor.java @@ -0,0 +1,182 @@ +package org.schabi.newpipe.player.refactor; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.media.audiofx.AudioEffect; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.Log; + +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.decoder.DecoderCounters; + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AudioRendererEventListener { + + private static final String TAG = "AudioFocusReactor"; + + private static final int DUCK_DURATION = 1500; + private static final float DUCK_AUDIO_TO = .2f; + + private static final int FOCUS_GAIN_TYPE = AudioManager.AUDIOFOCUS_GAIN; + private static final int STREAM_TYPE = AudioManager.STREAM_MUSIC; + + private final SimpleExoPlayer player; + private final Context context; + private final AudioManager audioManager; + + private AudioFocusRequest request; + + private final boolean isResumeAfterAudioFocusGain; + + public AudioReactor(@NonNull final Context context, @NonNull final SimpleExoPlayer player) { + this.player = player; + this.context = context; + + this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + this.isResumeAfterAudioFocusGain = PlayerHelper.isResumeAfterAudioFocusGain(context); + + player.setAudioDebugListener(this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE) + .setAcceptsDelayedFocusGain(true) + .setWillPauseWhenDucked(true) + .setOnAudioFocusChangeListener(this) + .build(); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Audio Manager + //////////////////////////////////////////////////////////////////////////*/ + + public void requestAudioFocus() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + audioManager.requestAudioFocus(request); + } else { + audioManager.requestAudioFocus(this, STREAM_TYPE, FOCUS_GAIN_TYPE); + } + } + + public void abandonAudioFocus() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + audioManager.abandonAudioFocusRequest(request); + } else { + audioManager.abandonAudioFocus(this); + } + } + + public int getMaxVolume() { + return audioManager.getStreamMaxVolume(STREAM_TYPE); + } + + public void setMaxVolume(final int volume) { + audioManager.setStreamVolume(STREAM_TYPE, volume, 0); + } + + /*////////////////////////////////////////////////////////////////////////// + // AudioFocus + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onAudioFocusChange(int focusChange) { + Log.d(TAG, "onAudioFocusChange() called with: focusChange = [" + focusChange + "]"); + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + onAudioFocusGain(); + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + onAudioFocusLossCanDuck(); + break; + case AudioManager.AUDIOFOCUS_LOSS: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + onAudioFocusLoss(); + break; + } + } + + private void onAudioFocusGain() { + Log.d(TAG, "onAudioFocusGain() called"); + player.setVolume(DUCK_AUDIO_TO); + animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION); + + if (isResumeAfterAudioFocusGain) { + player.setPlayWhenReady(true); + } + } + + private void onAudioFocusLoss() { + Log.d(TAG, "onAudioFocusLoss() called"); + player.setPlayWhenReady(false); + } + + private void onAudioFocusLossCanDuck() { + Log.d(TAG, "onAudioFocusLossCanDuck() called"); + // Set the volume to 1/10 on ducking + animateAudio(player.getVolume(), DUCK_AUDIO_TO, DUCK_DURATION); + } + + private void animateAudio(final float from, final float to, int duration) { + ValueAnimator valueAnimator = new ValueAnimator(); + valueAnimator.setFloatValues(from, to); + valueAnimator.setDuration(duration); + valueAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + player.setVolume(from); + } + + @Override + public void onAnimationCancel(Animator animation) { + player.setVolume(to); + } + + @Override + public void onAnimationEnd(Animator animation) { + player.setVolume(to); + } + }); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + player.setVolume(((float) animation.getAnimatedValue())); + } + }); + valueAnimator.start(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Audio Processing + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onAudioSessionId(int i) { + final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, i); + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); + context.sendBroadcast(intent); + } + + @Override + public void onAudioEnabled(DecoderCounters decoderCounters) {} + + @Override + public void onAudioDecoderInitialized(String s, long l, long l1) {} + + @Override + public void onAudioInputFormatChanged(Format format) {} + + @Override + public void onAudioTrackUnderrun(int i, long l, long l1) {} + + @Override + public void onAudioDisabled(DecoderCounters decoderCounters) {} +} diff --git a/app/src/main/java/org/schabi/newpipe/player/refactor/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/refactor/CacheFactory.java new file mode 100644 index 000000000..bd7862f49 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/refactor/CacheFactory.java @@ -0,0 +1,85 @@ +package org.schabi.newpipe.player.refactor; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.Log; + +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultDataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.FileDataSource; +import com.google.android.exoplayer2.upstream.cache.CacheDataSink; +import com.google.android.exoplayer2.upstream.cache.CacheDataSource; +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; +import com.google.android.exoplayer2.upstream.cache.SimpleCache; + +import org.schabi.newpipe.Downloader; + +import java.io.File; + +public class CacheFactory implements DataSource.Factory { + private static final String TAG = "CacheFactory"; + private static final String CACHE_FOLDER_NAME = "exoplayer"; + private static final int CACHE_FLAGS = CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR; + + private final DefaultDataSourceFactory dataSourceFactory; + private final File cacheDir; + private final long maxFileSize; + + // Creating cache on every instance may cause problems with multiple players when + // sources are not ExtractorMediaSource + // see: https://stackoverflow.com/questions/28700391/using-cache-in-exoplayer + // todo: make this a singleton? + private static SimpleCache cache; + + public CacheFactory(@NonNull final Context context) { + this(context, PlayerHelper.getPreferredCacheSize(context), PlayerHelper.getPreferredFileSize(context)); + } + + CacheFactory(@NonNull final Context context, final long maxCacheSize, final long maxFileSize) { + super(); + this.maxFileSize = maxFileSize; + + final String userAgent = Downloader.USER_AGENT; + final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + dataSourceFactory = new DefaultDataSourceFactory(context, userAgent, bandwidthMeter); + + cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME); + if (!cacheDir.exists()) { + //noinspection ResultOfMethodCallIgnored + cacheDir.mkdir(); + } + + if (cache == null) { + final LeastRecentlyUsedCacheEvictor evictor = new LeastRecentlyUsedCacheEvictor(maxCacheSize); + cache = new SimpleCache(cacheDir, evictor); + } + } + + @Override + public DataSource createDataSource() { + Log.d(TAG, "initExoPlayerCache: cacheDir = " + cacheDir.getAbsolutePath()); + + final DefaultDataSource dataSource = dataSourceFactory.createDataSource(); + final FileDataSource fileSource = new FileDataSource(); + final CacheDataSink dataSink = new CacheDataSink(cache, maxFileSize); + + return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null); + } + + public void tryDeleteCacheFiles() { + if (!cacheDir.exists() || !cacheDir.isDirectory()) return; + + try { + for (File file : cacheDir.listFiles()) { + final String filePath = file.getAbsolutePath(); + final boolean deleteSuccessful = file.delete(); + + Log.d(TAG, "tryDeleteCacheFiles: " + filePath + " deleted = " + deleteSuccessful); + } + } catch (Exception ignored) { + Log.e(TAG, "Failed to delete file.", ignored); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/player/refactor/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/refactor/LoadController.java new file mode 100644 index 000000000..2e8da8207 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/refactor/LoadController.java @@ -0,0 +1,76 @@ +package org.schabi.newpipe.player.refactor; + +import android.content.Context; + +import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.LoadControl; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.DefaultAllocator; + +public class LoadController implements LoadControl { + + public static final String TAG = "LoadController"; + + private final LoadControl internalLoadControl; + + /*////////////////////////////////////////////////////////////////////////// + // Default Load Control + //////////////////////////////////////////////////////////////////////////*/ + + public LoadController(final Context context) { + this(PlayerHelper.getMinBufferMs(context), + PlayerHelper.getMaxBufferMs(context), + PlayerHelper.getBufferForPlaybackMs(context), + PlayerHelper.getBufferForPlaybackAfterRebufferMs(context)); + } + + public LoadController(final int minBufferMs, + final int maxBufferMs, + final long bufferForPlaybackMs, + final long bufferForPlaybackAfterRebufferMs) { + final DefaultAllocator allocator = new DefaultAllocator(true, 65536); + internalLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs); + } + + /*////////////////////////////////////////////////////////////////////////// + // Custom behaviours + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onPrepared() { + internalLoadControl.onPrepared(); + } + + @Override + public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroupArray, TrackSelectionArray trackSelectionArray) { + internalLoadControl.onTracksSelected(renderers, trackGroupArray, trackSelectionArray); + } + + @Override + public void onStopped() { + internalLoadControl.onStopped(); + } + + @Override + public void onReleased() { + internalLoadControl.onReleased(); + } + + @Override + public Allocator getAllocator() { + return internalLoadControl.getAllocator(); + } + + @Override + public boolean shouldStartPlayback(long l, boolean b) { + return internalLoadControl.shouldStartPlayback(l, b); + } + + @Override + public boolean shouldContinueLoading(long l) { + return internalLoadControl.shouldContinueLoading(l); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/refactor/LockManager.java b/app/src/main/java/org/schabi/newpipe/player/refactor/LockManager.java new file mode 100644 index 000000000..9ec841943 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/refactor/LockManager.java @@ -0,0 +1,44 @@ +package org.schabi.newpipe.player.refactor; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.os.PowerManager; +import android.util.Log; + +import static android.content.Context.POWER_SERVICE; +import static android.content.Context.WIFI_SERVICE; + +public class LockManager { + private final String TAG = "LockManager@" + hashCode(); + + private final PowerManager powerManager; + private final WifiManager wifiManager; + + private PowerManager.WakeLock wakeLock; + private WifiManager.WifiLock wifiLock; + + public LockManager(final Context context) { + powerManager = ((PowerManager) context.getApplicationContext().getSystemService(POWER_SERVICE)); + wifiManager = ((WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE)); + } + + public void acquireWifiAndCpu() { + Log.d(TAG, "acquireWifiAndCpu() called"); + if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return; + + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); + + if (wakeLock != null) wakeLock.acquire(); + if (wifiLock != null) wifiLock.acquire(); + } + + public void releaseWifiAndCpu() { + Log.d(TAG, "releaseWifiAndCpu() called"); + if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); + if (wifiLock != null && wifiLock.isHeld()) wifiLock.release(); + + wakeLock = null; + wifiLock = null; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/refactor/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/refactor/PlayerHelper.java new file mode 100644 index 000000000..8c4fb340d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/refactor/PlayerHelper.java @@ -0,0 +1,103 @@ +package org.schabi.newpipe.player.refactor; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; + +import org.schabi.newpipe.R; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Formatter; +import java.util.Locale; + +public class PlayerHelper { + private PlayerHelper() {} + + private static final StringBuilder stringBuilder = new StringBuilder(); + private static final Formatter stringFormatter = new Formatter(stringBuilder, Locale.getDefault()); + private static final NumberFormat speedFormatter = new DecimalFormat("0.##x"); + private static final NumberFormat pitchFormatter = new DecimalFormat("##%"); + + //////////////////////////////////////////////////////////////////////////// + // Exposed helpers + //////////////////////////////////////////////////////////////////////////// + + public static String getTimeString(int milliSeconds) { + long seconds = (milliSeconds % 60000L) / 1000L; + long minutes = (milliSeconds % 3600000L) / 60000L; + long hours = (milliSeconds % 86400000L) / 3600000L; + long days = (milliSeconds % (86400000L * 7L)) / 86400000L; + + stringBuilder.setLength(0); + return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString() + : hours > 0 ? stringFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() + : stringFormatter.format("%02d:%02d", minutes, seconds).toString(); + } + + public static String formatSpeed(float speed) { + return speedFormatter.format(speed); + } + + public static String formatPitch(float pitch) { + return pitchFormatter.format(pitch); + } + + public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { + return isResumeAfterAudioFocusGain(context, false); + } + + public static boolean isPlayerGestureEnabled(@NonNull final Context context) { + return isPlayerGestureEnabled(context, true); + } + + public static boolean isUsingOldPlayer(@NonNull final Context context) { + return isUsingOldPlayer(context, false); + } + + public static long getPreferredCacheSize(@NonNull final Context context) { + return 64 * 1024 * 1024L; + } + + public static long getPreferredFileSize(@NonNull final Context context) { + return 512 * 1024L; + } + + public static int getMinBufferMs(@NonNull final Context context) { + return 15000; + } + + public static int getMaxBufferMs(@NonNull final Context context) { + return 30000; + } + + public static long getBufferForPlaybackMs(@NonNull final Context context) { + return 2500L; + } + + public static long getBufferForPlaybackAfterRebufferMs(@NonNull final Context context) { + return 5000L; + } + + //////////////////////////////////////////////////////////////////////////// + // Private helpers + //////////////////////////////////////////////////////////////////////////// + + @NonNull + private static SharedPreferences getPreferences(@NonNull final Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + + private static boolean isResumeAfterAudioFocusGain(@NonNull final Context context, final boolean b) { + return getPreferences(context).getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b); + } + + private static boolean isPlayerGestureEnabled(@NonNull final Context context, final boolean b) { + return getPreferences(context).getBoolean(context.getString(R.string.player_gesture_controls_key), b); + } + + private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) { + return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b); + } +} diff --git a/app/src/main/res/layout-land/activity_player_queue_control.xml b/app/src/main/res/layout-land/activity_player_queue_control.xml index 9d0c0c41e..967990ae8 100644 --- a/app/src/main/res/layout-land/activity_player_queue_control.xml +++ b/app/src/main/res/layout-land/activity_player_queue_control.xml @@ -55,6 +55,9 @@ android:layout_above="@+id/playback_controls_top" android:orientation="vertical" android:padding="8dp" + android:clickable="true" + android:focusable="true" + android:background="?attr/selectableItemBackground" tools:ignore="RtlHardcoded,RtlSymmetry"> + tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta." + /> + + @@ -249,7 +274,6 @@ tools:ignore="HardcodedText" tools:text="1:06:29"/> - - + android:id="@+id/center" + android:layout_above="@+id/progress_bar"> + + + + + + + - - - + android:layout_centerInParent="true" + android:background="#c0000000" + android:paddingBottom="5dp" + android:paddingLeft="30dp" + android:paddingRight="30dp" + android:paddingTop="5dp" + android:textColor="@android:color/white" + android:textSize="22sp" + android:textStyle="bold" + android:visibility="gone" + tools:ignore="RtlHardcoded" + tools:text="1:06:29" + tools:visibility="visible"/> + diff --git a/app/src/main/res/layout/player_popup_notification.xml b/app/src/main/res/layout/player_popup_notification.xml index 4010064d9..ab436a1fa 100644 --- a/app/src/main/res/layout/player_popup_notification.xml +++ b/app/src/main/res/layout/player_popup_notification.xml @@ -7,6 +7,7 @@ android:layout_height="64dp" android:background="@color/background_notification_color" android:clickable="true" + android:focusable="true" android:gravity="center_vertical" android:orientation="horizontal"> @@ -54,6 +55,7 @@ android:layout_height="match_parent" android:background="#00000000" android:clickable="true" + android:focusable="true" android:padding="5dp" android:scaleType="fitCenter" android:src="@drawable/ic_repeat_white" @@ -65,6 +67,7 @@ android:layout_height="match_parent" android:background="#00000000" android:clickable="true" + android:focusable="true" android:src="@drawable/ic_pause_white" tools:ignore="ContentDescription"/> @@ -75,6 +78,7 @@ android:layout_marginLeft="5dp" android:background="#00000000" android:clickable="true" + android:focusable="true" android:padding="5dp" android:scaleType="fitCenter" android:src="@drawable/ic_close_white_24dp"