-Added media session implementation for all players.

-Extracted version numbers in gradle dependencies.
-Updated ExoPlayer to 2.7.1.
-Updated RxJava to 2.1.10, RxAndroid to 2.0.2 and RxBinding to 2.1.1.
-Removed deprecated implementation of media buttons.
This commit is contained in:
John Zhen Mo 2018-03-15 23:42:46 -07:00
parent 5a05cb96be
commit bc7188c8a8
11 changed files with 358 additions and 102 deletions

View File

@ -49,6 +49,11 @@ android {
ext {
supportLibVersion = '27.1.0'
exoPlayerLibVersion = '2.7.1'
roomDbLibVersion = '1.0.0'
leakCanaryVersion = '1.5.4'
okHttpVersion = '1.5.0'
icepickVersion = '3.2.0'
}
dependencies {
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
@ -73,27 +78,28 @@ dependencies {
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
implementation 'com.nononsenseapps:filepicker:4.2.1'
implementation 'com.google.android.exoplayer:exoplayer:2.7.1'
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion"
implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion"
debugImplementation 'com.facebook.stetho:stetho:1.5.0'
debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0'
debugImplementation "com.facebook.stetho:stetho:$okHttpVersion"
debugImplementation "com.facebook.stetho:stetho-urlconnection:$okHttpVersion"
debugImplementation 'com.android.support:multidex:1.0.3'
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
implementation 'io.reactivex.rxjava2:rxjava:2.1.10'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
implementation 'android.arch.persistence.room:runtime:1.0.0'
implementation 'android.arch.persistence.room:rxjava2:1.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
implementation "android.arch.persistence.room:runtime:$roomDbLibVersion"
implementation "android.arch.persistence.room:rxjava2:$roomDbLibVersion"
annotationProcessor "android.arch.persistence.room:compiler:$roomDbLibVersion"
implementation 'frankiesardo:icepick:3.2.0'
annotationProcessor 'frankiesardo:icepick-processor:3.2.0'
implementation "frankiesardo:icepick:$icepickVersion"
annotationProcessor "frankiesardo:icepick-processor:$icepickVersion"
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
betaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpVersion"
}

View File

@ -43,12 +43,6 @@
android:launchMode="singleTask"
android:label="@string/title_activity_background_player"/>
<receiver android:name="org.schabi.newpipe.player.BackgroundPlayer$MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<activity
android:name=".player.PopupVideoPlayerActivity"
android:launchMode="singleTask"

View File

@ -22,8 +22,6 @@ package org.schabi.newpipe.player;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@ -35,7 +33,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.RemoteViews;
@ -81,8 +78,6 @@ public final class BackgroundPlayer extends Service {
private BasePlayerImpl basePlayerImpl;
private LockManager lockManager;
private ComponentName mReceiverComponent;
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
//////////////////////////////////////////////////////////////////////////*/
@ -119,9 +114,6 @@ public final class BackgroundPlayer extends Service {
mBinder = new PlayerServiceBinder(basePlayerImpl);
shouldUpdateOnProgress = true;
mReceiverComponent = new ComponentName(this, MediaButtonReceiver.class);
basePlayerImpl.getAudioReactor().registerMediaButtonEventReceiver(mReceiverComponent);
}
@Override
@ -152,7 +144,6 @@ public final class BackgroundPlayer extends Service {
lockManager.releaseWifiAndCpu();
}
if (basePlayerImpl != null) {
basePlayerImpl.getAudioReactor().unregisterMediaButtonEventReceiver(mReceiverComponent);
basePlayerImpl.stopActivityBinding();
basePlayerImpl.destroy();
}
@ -573,49 +564,4 @@ public final class BackgroundPlayer extends Service {
lockManager.releaseWifiAndCpu();
}
}
public static class MediaButtonReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) return;
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event.getAction() != KeyEvent.ACTION_UP) return;
final int keycode = event.getKeyCode();
final PendingIntent pendingIntent;
switch (keycode) {
case KeyEvent.KEYCODE_MEDIA_NEXT:
pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID,
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT);
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT);
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY:
pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
break;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID,
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT);
break;
case KeyEvent.KEYCODE_MEDIA_REWIND:
pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID,
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT);
break;
default:
pendingIntent = null;
}
if (pendingIntent == null) return;
try {
pendingIntent.send();
} catch (Exception e) {
Log.e(TAG, "Error Sending intent MediaButtonReceiver", e);
}
}
}
}

View File

@ -61,8 +61,10 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.history.HistoryRecordManager;
import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.LoadController;
import org.schabi.newpipe.player.helper.MediaSessionManager;
import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
import org.schabi.newpipe.player.playback.CustomTrackSelector;
import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener;
@ -148,6 +150,7 @@ public abstract class BasePlayer implements
protected SimpleExoPlayer simpleExoPlayer;
protected AudioReactor audioReactor;
protected MediaSessionManager mediaSessionManager;
private boolean isPrepared = false;
private boolean isSynchronizing = false;
@ -195,11 +198,13 @@ public abstract class BasePlayer implements
final LoadControl loadControl = new LoadController(context);
final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl);
audioReactor = new AudioReactor(context, simpleExoPlayer);
simpleExoPlayer.addListener(this);
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
audioReactor = new AudioReactor(context, simpleExoPlayer);
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
new BasePlayerMediaSession(this));
}
public void initListeners() {}
@ -262,8 +267,8 @@ public abstract class BasePlayer implements
}
if (isProgressLoopRunning()) stopProgressLoop();
if (playQueue != null) playQueue.dispose();
if (audioReactor != null) audioReactor.dispose();
if (playbackManager != null) playbackManager.dispose();
if (audioReactor != null) audioReactor.abandonAudioFocus();
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
if (playQueueAdapter != null) {
@ -279,6 +284,7 @@ public abstract class BasePlayer implements
trackSelector = null;
simpleExoPlayer = null;
mediaSessionManager = null;
}
/*//////////////////////////////////////////////////////////////////////////

View File

@ -3,7 +3,6 @@ package org.schabi.newpipe.player.helper;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFocusRequest;
@ -18,18 +17,14 @@ import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AudioRendererEventListener {
public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
AudioRendererEventListener {
private static final String TAG = "AudioFocusReactor";
private static final boolean SHOULD_BUILD_FOCUS_REQUEST =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static final boolean CAN_USE_MEDIA_BUTTONS =
Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1;
private static final String MEDIA_BUTTON_DEPRECATED_ERROR =
"registerMediaButtonEventReceiver has been deprecated and maybe not supported anymore.";
private static final int DUCK_DURATION = 1500;
private static final float DUCK_AUDIO_TO = .2f;
@ -42,7 +37,8 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
private final AudioFocusRequest request;
public AudioReactor(@NonNull final Context context, @NonNull final SimpleExoPlayer player) {
public AudioReactor(@NonNull final Context context,
@NonNull final SimpleExoPlayer player) {
this.player = player;
this.context = context;
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@ -59,6 +55,11 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
}
}
public void dispose() {
abandonAudioFocus();
player.removeAudioDebugListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
// Audio Manager
//////////////////////////////////////////////////////////////////////////*/
@ -91,22 +92,6 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au
audioManager.setStreamVolume(STREAM_TYPE, volume, 0);
}
public void registerMediaButtonEventReceiver(ComponentName componentName) {
if (CAN_USE_MEDIA_BUTTONS) {
audioManager.registerMediaButtonEventReceiver(componentName);
} else {
Log.e(TAG, MEDIA_BUTTON_DEPRECATED_ERROR);
}
}
public void unregisterMediaButtonEventReceiver(ComponentName componentName) {
if (CAN_USE_MEDIA_BUTTONS) {
audioManager.unregisterMediaButtonEventReceiver(componentName);
} else {
Log.e(TAG, MEDIA_BUTTON_DEPRECATED_ERROR);
}
}
/*//////////////////////////////////////////////////////////////////////////
// AudioFocus
//////////////////////////////////////////////////////////////////////////*/

View File

@ -0,0 +1,38 @@
package org.schabi.newpipe.player.helper;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.media.session.MediaSessionCompat;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import org.schabi.newpipe.player.mediasession.DummyPlaybackPreparer;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.mediasession.PlayQueueNavigator;
import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController;
public class MediaSessionManager {
private static final String TAG = "MediaSessionManager";
private final MediaSessionCompat mediaSession;
private final MediaSessionConnector sessionConnector;
public MediaSessionManager(@NonNull final Context context,
@NonNull final Player player,
@NonNull final MediaSessionCallback callback) {
this.mediaSession = new MediaSessionCompat(context, TAG);
this.sessionConnector = new MediaSessionConnector(mediaSession,
new PlayQueuePlaybackController(callback));
this.sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback));
this.sessionConnector.setPlayer(player, new DummyPlaybackPreparer());
}
public MediaSessionCompat getMediaSession() {
return mediaSession;
}
public MediaSessionConnector getSessionConnector() {
return sessionConnector;
}
}

View File

@ -0,0 +1,45 @@
package org.schabi.newpipe.player.mediasession;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
public class DummyPlaybackPreparer implements MediaSessionConnector.PlaybackPreparer {
@Override
public long getSupportedPrepareActions() {
return 0;
}
@Override
public void onPrepare() {
}
@Override
public void onPrepareFromMediaId(String mediaId, Bundle extras) {
}
@Override
public void onPrepareFromSearch(String query, Bundle extras) {
}
@Override
public void onPrepareFromUri(Uri uri, Bundle extras) {
}
@Override
public String[] getCommands() {
return new String[0];
}
@Override
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
}
}

View File

@ -0,0 +1,17 @@
package org.schabi.newpipe.player.mediasession;
import android.support.v4.media.MediaDescriptionCompat;
public interface MediaSessionCallback {
void onSkipToPrevious();
void onSkipToNext();
void onSkipToIndex(final int index);
int getCurrentPlayingIndex();
int getQueueSize();
MediaDescriptionCompat getQueueMetadata(final int index);
void onPlay();
void onPause();
void onSetShuffle(final boolean isShuffled);
}

View File

@ -0,0 +1,111 @@
package org.schabi.newpipe.player.mediasession;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.session.MediaSessionCompat;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator {
public static final int DEFAULT_MAX_QUEUE_SIZE = 10;
private final MediaSessionCompat mediaSession;
private final MediaSessionCallback callback;
private final int maxQueueSize;
private long activeQueueItemId;
public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession,
@NonNull final MediaSessionCallback callback) {
this.mediaSession = mediaSession;
this.callback = callback;
this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
this.activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
}
@Override
public long getSupportedQueueNavigatorActions(@Nullable Player player) {
return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM;
}
@Override
public void onTimelineChanged(Player player) {
publishFloatingQueueWindow();
}
@Override
public void onCurrentWindowIndexChanged(Player player) {
if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID
|| player.getCurrentTimeline().getWindowCount() > maxQueueSize) {
publishFloatingQueueWindow();
} else if (!player.getCurrentTimeline().isEmpty()) {
activeQueueItemId = player.getCurrentWindowIndex();
}
}
@Override
public long getActiveQueueItemId(@Nullable Player player) {
return callback.getCurrentPlayingIndex();
}
@Override
public void onSkipToPrevious(Player player) {
callback.onSkipToPrevious();
}
@Override
public void onSkipToQueueItem(Player player, long id) {
callback.onSkipToIndex((int) id);
}
@Override
public void onSkipToNext(Player player) {
callback.onSkipToNext();
}
private void publishFloatingQueueWindow() {
if (callback.getQueueSize() == 0) {
mediaSession.setQueue(Collections.<MediaSessionCompat.QueueItem>emptyList());
activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
return;
}
// Yes this is almost a copypasta, got a problem with that? =\
int windowCount = callback.getQueueSize();
int currentWindowIndex = callback.getCurrentPlayingIndex();
int queueSize = Math.min(maxQueueSize, windowCount);
int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
windowCount - queueSize);
List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
for (int i = startIndex; i < startIndex + queueSize; i++) {
queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i));
}
mediaSession.setQueue(queue);
activeQueueItemId = currentWindowIndex;
}
@Override
public String[] getCommands() {
return new String[0];
}
@Override
public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
}
}

View File

@ -0,0 +1,31 @@
package org.schabi.newpipe.player.mediasession;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.DefaultPlaybackController;
public class PlayQueuePlaybackController extends DefaultPlaybackController {
private final MediaSessionCallback callback;
public PlayQueuePlaybackController(final MediaSessionCallback callback) {
super();
this.callback = callback;
}
@Override
public void onPlay(Player player) {
callback.onPlay();
}
@Override
public void onPause(Player player) {
callback.onPause();
}
@Override
public void onSetShuffleMode(Player player, int shuffleMode) {
callback.onSetShuffle(shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL
|| shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP);
}
}

View File

@ -0,0 +1,77 @@
package org.schabi.newpipe.player.playback;
import android.net.Uri;
import android.support.v4.media.MediaDescriptionCompat;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.playlist.PlayQueueItem;
public class BasePlayerMediaSession implements MediaSessionCallback {
private BasePlayer player;
public BasePlayerMediaSession(final BasePlayer player) {
this.player = player;
}
@Override
public void onSkipToPrevious() {
player.onPlayPrevious();
}
@Override
public void onSkipToNext() {
player.onPlayNext();
}
@Override
public void onSkipToIndex(int index) {
if (player.getPlayQueue() == null) return;
player.onSelected(player.getPlayQueue().getItem(index));
}
@Override
public int getCurrentPlayingIndex() {
if (player.getPlayQueue() == null) return -1;
return player.getPlayQueue().getIndex();
}
@Override
public int getQueueSize() {
if (player.getPlayQueue() == null) return -1;
return player.getPlayQueue().size();
}
@Override
public MediaDescriptionCompat getQueueMetadata(int index) {
if (player.getPlayQueue() == null || player.getPlayQueue().getItem(index) == null) {
return null;
}
final PlayQueueItem item = player.getPlayQueue().getItem(index);
MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder()
.setMediaId(String.valueOf(index))
.setTitle(item.getTitle())
.setSubtitle(item.getUploader());
final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl());
if (thumbnailUri != null) descriptionBuilder.setIconUri(thumbnailUri);
return descriptionBuilder.build();
}
@Override
public void onPlay() {
if (!player.isPlaying()) player.onVideoPlayPause();
}
@Override
public void onPause() {
if (player.isPlaying()) player.onVideoPlayPause();
}
@Override
public void onSetShuffle(boolean isShuffled) {
player.onShuffleModeEnabledChanged(isShuffled);
}
}