-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.
This commit is contained in:
John Zhen Mo 2017-10-26 19:59:25 -07:00
parent c6e759a94c
commit f284a799ef
16 changed files with 747 additions and 336 deletions

View File

@ -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();
}
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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));
}
}
}

View File

@ -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;
/**

View File

@ -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;

View File

@ -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;

View File

@ -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) {}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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">
<TextView
@ -63,10 +66,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:maxLines="4"
android:textColor="?attr/colorAccent"
android:textSize="14sp"
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." />
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."
/>
<TextView
android:id="@+id/artist_name"
@ -74,11 +78,30 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:maxLines="2"
android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis." />
</LinearLayout>
<TextView
android:id="@+id/seek_display"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#c0000000"
android:paddingBottom="5dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingTop="5dp"
android:layout_above="@+id/playback_controls_top"
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"/>
<RelativeLayout
android:id="@+id/playback_controls_top"
android:layout_width="match_parent"
@ -95,12 +118,12 @@
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="@+id/control_play_pause"
android:background="#00000000"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/exo_controls_previous"
android:background="?attr/selectableItemBackgroundBorderless"
tools:ignore="ContentDescription"/>
<ImageButton
@ -112,7 +135,7 @@
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="2dp"
android:clickable="true"
android:focusable="true"
@ -144,7 +167,7 @@
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/control_play_pause"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
@ -175,6 +198,7 @@
android:text="1x"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>
<ImageButton
@ -184,7 +208,7 @@
android:layout_toLeftOf="@+id/anchor"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
@ -203,7 +227,7 @@
android:layout_toRightOf="@+id/anchor"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
@ -223,6 +247,7 @@
android:text="100%"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>
</RelativeLayout>
</RelativeLayout>
@ -249,7 +274,6 @@
tools:ignore="HardcodedText"
tools:text="1:06:29"/>
<android.support.v7.widget.AppCompatSeekBar
android:id="@+id/seek_bar"
style="@style/Widget.AppCompat.SeekBar"

View File

@ -31,42 +31,70 @@
android:id="@+id/play_queue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/appbar"
android:layout_above="@+id/metadata"
android:layout_below="@id/appbar"
android:layout_above="@id/center"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/play_queue_item"/>
<LinearLayout
android:id="@+id/metadata"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/progress_bar"
android:orientation="vertical"
android:padding="8dp"
tools:ignore="RtlHardcoded,RtlSymmetry">
android:id="@+id/center"
android:layout_above="@+id/progress_bar">
<LinearLayout
android:id="@+id/metadata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
tools:ignore="RtlHardcoded,RtlSymmetry">
<TextView
android:id="@+id/song_name"
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:textSize="14sp"
android:textColor="?attr/colorAccent"
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."
/>
<TextView
android:id="@+id/artist_name"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout>
<TextView
android:id="@+id/song_name"
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="match_parent"
android:id="@+id/seek_display"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textSize="14sp"
android:textColor="?attr/colorAccent"
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."/>
<TextView
android:id="@+id/artist_name"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout>
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"/>
</RelativeLayout>
<LinearLayout
android:id="@+id/progress_bar"
@ -135,6 +163,7 @@
android:text="1x"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>
<ImageButton
@ -144,7 +173,7 @@
android:layout_toLeftOf="@+id/control_backward"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
@ -158,7 +187,7 @@
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="@+id/control_play_pause"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
@ -175,7 +204,7 @@
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="2dp"
android:clickable="true"
android:focusable="true"
@ -207,7 +236,7 @@
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toRightOf="@+id/control_play_pause"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="2dp"
@ -222,7 +251,7 @@
android:layout_toRightOf="@+id/control_forward"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:background="#00000000"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:scaleType="fitXY"
@ -242,6 +271,7 @@
android:text="100%"
android:textColor="@android:color/white"
android:textStyle="bold"
android:background="?attr/selectableItemBackground"
tools:ignore="HardcodedText,RtlHardcoded"/>
</RelativeLayout>

View File

@ -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"