- Improved play queue adapter for selection.
- Fixed media source resolution on background player for streams without an audio only stream. - Fixed background player not updating when screen turns back on. - Fixed background player notification switching to wrong repeat mode icon opacity on click.
This commit is contained in:
parent
bd9ee18e56
commit
a9aee21e58
|
@ -38,6 +38,11 @@
|
||||||
android:name=".player.BackgroundPlayer"
|
android:name=".player.BackgroundPlayer"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".player.BackgroundPlayerActivity"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:label="@string/title_activity_background_player"/>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".player.PopupVideoPlayer"
|
android:name=".player.PopupVideoPlayer"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
@ -36,9 +37,9 @@ import android.support.v4.app.NotificationCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
@ -51,9 +52,6 @@ import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base players joining the common properties
|
* Base players joining the common properties
|
||||||
|
@ -78,13 +76,30 @@ public final class BackgroundPlayer extends Service {
|
||||||
private PowerManager.WakeLock wakeLock;
|
private PowerManager.WakeLock wakeLock;
|
||||||
private WifiManager.WifiLock wifiLock;
|
private WifiManager.WifiLock wifiLock;
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Service-Activity Binder
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public interface PlayerEventListener {
|
||||||
|
void onPlaybackUpdate(int state, int repeatMode, PlaybackParameters parameters);
|
||||||
|
void onProgressUpdate(int currentProgress, int duration, int bufferPercent);
|
||||||
|
void onMetadataUpdate(StreamInfo info);
|
||||||
|
void onServiceStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerEventListener activityListener;
|
||||||
|
private IBinder mBinder;
|
||||||
|
|
||||||
|
class LocalBinder extends Binder {
|
||||||
|
BasePlayerImpl getBackgroundPlayerInstance() {
|
||||||
|
return BackgroundPlayer.this.basePlayerImpl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Notification
|
// Notification
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
private static final int NOTIFICATION_ID = 123789;
|
private static final int NOTIFICATION_ID = 123789;
|
||||||
|
|
||||||
private boolean shouldUpdateNotification;
|
|
||||||
|
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
private NotificationCompat.Builder notBuilder;
|
private NotificationCompat.Builder notBuilder;
|
||||||
private RemoteViews notRemoteView;
|
private RemoteViews notRemoteView;
|
||||||
|
@ -105,6 +120,8 @@ public final class BackgroundPlayer extends Service {
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
basePlayerImpl = new BasePlayerImpl(this);
|
basePlayerImpl = new BasePlayerImpl(this);
|
||||||
basePlayerImpl.setup();
|
basePlayerImpl.setup();
|
||||||
|
|
||||||
|
mBinder = new LocalBinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -124,13 +141,19 @@ public final class BackgroundPlayer extends Service {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return null;
|
return mBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Actions
|
// Actions
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public void openControl(final Context context) {
|
||||||
|
final Intent intent = new Intent(context, BackgroundPlayerActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||||
|
}
|
||||||
|
|
||||||
public void onOpenDetail(Context context, String videoUrl, String videoTitle) {
|
public void onOpenDetail(Context context, String videoUrl, String videoTitle) {
|
||||||
if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
|
if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
|
||||||
Intent i = new Intent(context, MainActivity.class);
|
Intent i = new Intent(context, MainActivity.class);
|
||||||
|
@ -144,7 +167,11 @@ public final class BackgroundPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClose() {
|
private void onClose() {
|
||||||
if (basePlayerImpl != null) basePlayerImpl.destroyPlayer();
|
if (basePlayerImpl != null) {
|
||||||
|
basePlayerImpl.stopActivityBinding();
|
||||||
|
basePlayerImpl.destroyPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
releaseWifiAndCpu();
|
releaseWifiAndCpu();
|
||||||
stopSelf();
|
stopSelf();
|
||||||
|
@ -152,8 +179,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
|
|
||||||
private void onScreenOnOff(boolean on) {
|
private void onScreenOnOff(boolean on) {
|
||||||
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||||
shouldUpdateNotification = on;
|
|
||||||
|
|
||||||
if (on) {
|
if (on) {
|
||||||
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) {
|
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) {
|
||||||
basePlayerImpl.startProgressLoop();
|
basePlayerImpl.startProgressLoop();
|
||||||
|
@ -168,10 +193,8 @@ public final class BackgroundPlayer extends Service {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void resetNotification() {
|
private void resetNotification() {
|
||||||
if (shouldUpdateNotification) {
|
|
||||||
notBuilder = createNotification();
|
notBuilder = createNotification();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private NotificationCompat.Builder createNotification() {
|
private NotificationCompat.Builder createNotification() {
|
||||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
|
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
|
||||||
|
@ -211,7 +234,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
break;
|
break;
|
||||||
case Player.REPEAT_MODE_ONE:
|
case Player.REPEAT_MODE_ONE:
|
||||||
// todo change image
|
// todo change image
|
||||||
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
|
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 168);
|
||||||
break;
|
break;
|
||||||
case Player.REPEAT_MODE_ALL:
|
case Player.REPEAT_MODE_ALL:
|
||||||
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
|
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
|
||||||
|
@ -227,7 +250,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
*/
|
*/
|
||||||
private synchronized void updateNotification(int drawableId) {
|
private synchronized void updateNotification(int drawableId) {
|
||||||
//if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
//if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||||
if (notBuilder == null || !shouldUpdateNotification) return;
|
if (notBuilder == null) return;
|
||||||
if (drawableId != -1) {
|
if (drawableId != -1) {
|
||||||
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||||
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
|
||||||
|
@ -270,7 +293,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private class BasePlayerImpl extends BasePlayer {
|
protected class BasePlayerImpl extends BasePlayer {
|
||||||
|
|
||||||
BasePlayerImpl(Context context) {
|
BasePlayerImpl(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -280,8 +303,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
public void handleIntent(Intent intent) {
|
public void handleIntent(Intent intent) {
|
||||||
super.handleIntent(intent);
|
super.handleIntent(intent);
|
||||||
|
|
||||||
shouldUpdateNotification = true;
|
resetNotification();
|
||||||
notBuilder = createNotification();
|
|
||||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||||
|
|
||||||
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
||||||
|
@ -329,23 +351,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
@Override
|
@Override
|
||||||
public void onRepeatClicked() {
|
public void onRepeatClicked() {
|
||||||
super.onRepeatClicked();
|
super.onRepeatClicked();
|
||||||
|
|
||||||
int opacity = 255;
|
|
||||||
switch (simpleExoPlayer.getRepeatMode()) {
|
|
||||||
case Player.REPEAT_MODE_OFF:
|
|
||||||
opacity = 77;
|
|
||||||
break;
|
|
||||||
case Player.REPEAT_MODE_ONE:
|
|
||||||
// todo change image
|
|
||||||
opacity = 168;
|
|
||||||
break;
|
|
||||||
case Player.REPEAT_MODE_ALL:
|
|
||||||
opacity = 255;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, opacity);
|
|
||||||
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, opacity);
|
|
||||||
updateNotification(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -368,6 +373,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNotification(-1);
|
updateNotification(-1);
|
||||||
|
updateProgress(currentProgress, duration, bufferPercent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -386,16 +392,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
triggerProgressUpdate();
|
triggerProgressUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingChanged(boolean isLoading) {
|
|
||||||
// Disable default behavior
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRepeatModeChanged(int i) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
@ -408,6 +404,42 @@ public final class BackgroundPlayer extends Service {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// ExoPlayer Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
|
super.onPlaybackParametersChanged(playbackParameters);
|
||||||
|
updatePlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingChanged(boolean isLoading) {
|
||||||
|
// Disable default behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRepeatModeChanged(int i) {
|
||||||
|
int opacity = 255;
|
||||||
|
switch (simpleExoPlayer.getRepeatMode()) {
|
||||||
|
case Player.REPEAT_MODE_OFF:
|
||||||
|
opacity = 77;
|
||||||
|
break;
|
||||||
|
case Player.REPEAT_MODE_ONE:
|
||||||
|
// todo change image
|
||||||
|
opacity = 168;
|
||||||
|
break;
|
||||||
|
case Player.REPEAT_MODE_ALL:
|
||||||
|
opacity = 255;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, opacity);
|
||||||
|
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, opacity);
|
||||||
|
updateNotification(-1);
|
||||||
|
updatePlayback();
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Playback Listener
|
// Playback Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -422,11 +454,14 @@ public final class BackgroundPlayer extends Service {
|
||||||
bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
|
bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
|
||||||
bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
|
bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
|
||||||
updateNotification(-1);
|
updateNotification(-1);
|
||||||
|
updateMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaSource sourceOf(final StreamInfo info) {
|
public MediaSource sourceOf(final StreamInfo info) {
|
||||||
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
|
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
|
||||||
|
if (index < 0) return null;
|
||||||
|
|
||||||
final AudioStream audio = info.audio_streams.get(index);
|
final AudioStream audio = info.audio_streams.get(index);
|
||||||
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||||
}
|
}
|
||||||
|
@ -435,6 +470,43 @@ public final class BackgroundPlayer extends Service {
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
super.shutdown();
|
super.shutdown();
|
||||||
stopSelf();
|
stopSelf();
|
||||||
|
stopActivityBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Activity Event Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public void setActivityListener(PlayerEventListener listener) {
|
||||||
|
activityListener = listener;
|
||||||
|
updateMetadata();
|
||||||
|
updatePlayback();
|
||||||
|
triggerProgressUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMetadata() {
|
||||||
|
if (activityListener != null && currentInfo != null) {
|
||||||
|
activityListener.onMetadataUpdate(currentInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePlayback() {
|
||||||
|
if (activityListener != null) {
|
||||||
|
activityListener.onPlaybackUpdate(currentState, simpleExoPlayer.getRepeatMode(), simpleExoPlayer.getPlaybackParameters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||||
|
if (activityListener != null) {
|
||||||
|
activityListener.onProgressUpdate(currentProgress, duration, bufferPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopActivityBinding() {
|
||||||
|
if (activityListener != null) {
|
||||||
|
activityListener.onServiceStopped();
|
||||||
|
activityListener = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -469,7 +541,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
onVideoPlayPause();
|
onVideoPlayPause();
|
||||||
break;
|
break;
|
||||||
case ACTION_OPEN_DETAIL:
|
case ACTION_OPEN_DETAIL:
|
||||||
onOpenDetail(BackgroundPlayer.this, getVideoUrl(), getVideoTitle());
|
openControl(BackgroundPlayer.this);
|
||||||
break;
|
break;
|
||||||
case ACTION_REPEAT:
|
case ACTION_REPEAT:
|
||||||
onRepeatClicked();
|
onRepeatClicked();
|
||||||
|
@ -493,6 +565,12 @@ public final class BackgroundPlayer extends Service {
|
||||||
// States
|
// States
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeState(int state) {
|
||||||
|
super.changeState(state);
|
||||||
|
updatePlayback();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlocked() {
|
public void onBlocked() {
|
||||||
super.onBlocked();
|
super.onBlocked();
|
||||||
|
|
|
@ -0,0 +1,305 @@
|
||||||
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
|
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||||
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
public class BackgroundPlayerActivity extends AppCompatActivity
|
||||||
|
implements BackgroundPlayer.PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener {
|
||||||
|
|
||||||
|
private static final String TAG = "BGPlayerActivity";
|
||||||
|
|
||||||
|
private boolean isServiceBound;
|
||||||
|
private ServiceConnection serviceConnection;
|
||||||
|
|
||||||
|
private BackgroundPlayer.BasePlayerImpl player;
|
||||||
|
|
||||||
|
private boolean isSeeking;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Views
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private View rootView;
|
||||||
|
|
||||||
|
private RecyclerView itemsList;
|
||||||
|
|
||||||
|
private TextView metadataTitle;
|
||||||
|
private TextView metadataArtist;
|
||||||
|
|
||||||
|
private SeekBar progressSeekBar;
|
||||||
|
private TextView progressCurrentTime;
|
||||||
|
private TextView progressEndTime;
|
||||||
|
|
||||||
|
private ImageButton repeatButton;
|
||||||
|
private ImageButton backwardButton;
|
||||||
|
private ImageButton playPauseButton;
|
||||||
|
private ImageButton forwardButton;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Activity Lifecycle
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ThemeHelper.setTheme(this);
|
||||||
|
setContentView(R.layout.activity_background_player);
|
||||||
|
rootView = findViewById(R.id.main_content);
|
||||||
|
|
||||||
|
final Toolbar toolbar = rootView.findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setTitle(R.string.title_activity_background_player);
|
||||||
|
|
||||||
|
serviceConnection = backgroundPlayerConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
final Intent mIntent = new Intent(this, BackgroundPlayer.class);
|
||||||
|
final boolean success = bindService(mIntent, serviceConnection, BIND_AUTO_CREATE);
|
||||||
|
if (!success) unbindService(serviceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
case R.id.action_settings:
|
||||||
|
Intent intent = new Intent(this, SettingsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if(isServiceBound) {
|
||||||
|
unbindService(serviceConnection);
|
||||||
|
isServiceBound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Service Connection
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private ServiceConnection backgroundPlayerConnection() {
|
||||||
|
return new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
Log.d(TAG, "Background player service is disconnected");
|
||||||
|
isServiceBound = false;
|
||||||
|
player = null;
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
Log.d(TAG, "Background player service is connected");
|
||||||
|
final BackgroundPlayer.LocalBinder mLocalBinder = (BackgroundPlayer.LocalBinder) service;
|
||||||
|
player = mLocalBinder.getBackgroundPlayerInstance();
|
||||||
|
if (player == null) {
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
isServiceBound = true;
|
||||||
|
buildComponents();
|
||||||
|
|
||||||
|
player.setActivityListener(BackgroundPlayerActivity.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Component Building
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void buildComponents() {
|
||||||
|
buildQueue();
|
||||||
|
buildMetadata();
|
||||||
|
buildSeekBar();
|
||||||
|
buildControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildQueue() {
|
||||||
|
itemsList = findViewById(R.id.play_queue);
|
||||||
|
itemsList.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
itemsList.setAdapter(player.playQueueAdapter);
|
||||||
|
itemsList.setClickable(true);
|
||||||
|
|
||||||
|
player.playQueueAdapter.setSelectedListener(new PlayQueueItemBuilder.OnSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void selected(PlayQueueItem item) {
|
||||||
|
final int index = player.playQueue.indexOf(item);
|
||||||
|
if (index != -1) player.playQueue.setIndex(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildMetadata() {
|
||||||
|
metadataTitle = rootView.findViewById(R.id.song_name);
|
||||||
|
metadataArtist = rootView.findViewById(R.id.artist_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildSeekBar() {
|
||||||
|
progressCurrentTime = rootView.findViewById(R.id.current_time);
|
||||||
|
progressSeekBar = rootView.findViewById(R.id.seek_bar);
|
||||||
|
progressEndTime = rootView.findViewById(R.id.end_time);
|
||||||
|
|
||||||
|
progressSeekBar.setOnSeekBarChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildControls() {
|
||||||
|
repeatButton = rootView.findViewById(R.id.control_repeat);
|
||||||
|
backwardButton = rootView.findViewById(R.id.control_backward);
|
||||||
|
playPauseButton = rootView.findViewById(R.id.control_play_pause);
|
||||||
|
forwardButton = rootView.findViewById(R.id.control_forward);
|
||||||
|
|
||||||
|
repeatButton.setOnClickListener(this);
|
||||||
|
backwardButton.setOnClickListener(this);
|
||||||
|
playPauseButton.setOnClickListener(this);
|
||||||
|
forwardButton.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Component On-Click Listener
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (view.getId() == repeatButton.getId()) {
|
||||||
|
player.onRepeatClicked();
|
||||||
|
} else if (view.getId() == backwardButton.getId()) {
|
||||||
|
player.onPlayPrevious();
|
||||||
|
} else if (view.getId() == playPauseButton.getId()) {
|
||||||
|
player.onVideoPlayPause();
|
||||||
|
} else if (view.getId() == forwardButton.getId()) {
|
||||||
|
player.onPlayNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Seekbar Listener
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
if (fromUser) progressCurrentTime.setText(Localization.getDurationString(progress / 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
isSeeking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
player.simpleExoPlayer.seekTo(seekBar.getProgress());
|
||||||
|
isSeeking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Binding Service Listener
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackUpdate(int state, int repeatMode, PlaybackParameters parameters) {
|
||||||
|
switch (state) {
|
||||||
|
case BasePlayer.STATE_PAUSED:
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
||||||
|
break;
|
||||||
|
case BasePlayer.STATE_PLAYING:
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||||
|
break;
|
||||||
|
case BasePlayer.STATE_COMPLETED:
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_replay_white);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int alpha = 255;
|
||||||
|
switch (repeatMode) {
|
||||||
|
case Player.REPEAT_MODE_OFF:
|
||||||
|
alpha = 77;
|
||||||
|
break;
|
||||||
|
case Player.REPEAT_MODE_ONE:
|
||||||
|
// todo change image
|
||||||
|
alpha = 168;
|
||||||
|
break;
|
||||||
|
case Player.REPEAT_MODE_ALL:
|
||||||
|
alpha = 255;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
repeatButton.setImageAlpha(alpha);
|
||||||
|
} else {
|
||||||
|
repeatButton.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters != null) {
|
||||||
|
final float speed = parameters.speed;
|
||||||
|
final float pitch = parameters.pitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressUpdate(int currentProgress, int duration, int bufferPercent) {
|
||||||
|
// Set buffer progress
|
||||||
|
progressSeekBar.setSecondaryProgress((int) (progressSeekBar.getMax() * ((float) bufferPercent / 100)));
|
||||||
|
|
||||||
|
// Set Duration
|
||||||
|
progressSeekBar.setMax(duration);
|
||||||
|
progressEndTime.setText(Localization.getDurationString(duration / 1000));
|
||||||
|
|
||||||
|
// Set current time if not seeking
|
||||||
|
if (!isSeeking) {
|
||||||
|
progressSeekBar.setProgress(currentProgress);
|
||||||
|
progressCurrentTime.setText(Localization.getDurationString(currentProgress / 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetadataUpdate(StreamInfo info) {
|
||||||
|
if (info != null) {
|
||||||
|
metadataTitle.setText(info.name);
|
||||||
|
metadataArtist.setText(info.uploader_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceStopped() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,6 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.audiofx.AudioEffect;
|
import android.media.audiofx.AudioEffect;
|
||||||
|
@ -35,7 +34,6 @@ import android.net.Uri;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
@ -72,28 +70,21 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto
|
||||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.assist.ImageSize;
|
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.Downloader;
|
import org.schabi.newpipe.Downloader;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|
||||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||||
import org.schabi.newpipe.playlist.ExternalPlayQueue;
|
|
||||||
import org.schabi.newpipe.playlist.PlayQueue;
|
import org.schabi.newpipe.playlist.PlayQueue;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
import org.schabi.newpipe.playlist.PlayQueueAdapter;
|
||||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Formatter;
|
import java.util.Formatter;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -124,6 +115,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
protected BroadcastReceiver broadcastReceiver;
|
protected BroadcastReceiver broadcastReceiver;
|
||||||
protected IntentFilter intentFilter;
|
protected IntentFilter intentFilter;
|
||||||
|
|
||||||
|
protected PlayQueueAdapter playQueueAdapter;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Intent
|
// Intent
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -285,6 +278,9 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
playQueue = queue;
|
playQueue = queue;
|
||||||
playQueue.init();
|
playQueue.init();
|
||||||
playbackManager = new MediaSourceManager(this, playQueue);
|
playbackManager = new MediaSourceManager(this, playQueue);
|
||||||
|
|
||||||
|
if (playQueueAdapter != null) playQueueAdapter.dispose();
|
||||||
|
playQueueAdapter = new PlayQueueAdapter(playQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initThumbnail(final String url) {
|
public void initThumbnail(final String url) {
|
||||||
|
@ -816,6 +812,7 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||||
private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
|
private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
|
||||||
private final NumberFormat speedFormatter = new DecimalFormat("0.##x");
|
private final NumberFormat speedFormatter = new DecimalFormat("0.##x");
|
||||||
|
|
||||||
|
// todo: merge this into Localization
|
||||||
public String getTimeString(int milliSeconds) {
|
public String getTimeString(int milliSeconds) {
|
||||||
long seconds = (milliSeconds % 60000L) / 1000L;
|
long seconds = (milliSeconds % 60000L) / 1000L;
|
||||||
long minutes = (milliSeconds % 3600000L) / 60000L;
|
long minutes = (milliSeconds % 3600000L) / 60000L;
|
||||||
|
|
|
@ -64,13 +64,9 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.playlist.PlayQueue;
|
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
|
||||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -111,6 +107,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
private List<TrackGroupInfo> trackGroupInfos;
|
private List<TrackGroupInfo> trackGroupInfos;
|
||||||
private int videoRendererIndex = -1;
|
private int videoRendererIndex = -1;
|
||||||
private TrackGroupArray videoTrackGroups;
|
private TrackGroupArray videoTrackGroups;
|
||||||
|
private TrackGroup selectedVideoTrackGroup;
|
||||||
|
|
||||||
private boolean startedFromNewPipe = true;
|
private boolean startedFromNewPipe = true;
|
||||||
protected boolean wasPlaying = false;
|
protected boolean wasPlaying = false;
|
||||||
|
@ -211,7 +208,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
public void initPlayer() {
|
public void initPlayer() {
|
||||||
super.initPlayer();
|
super.initPlayer();
|
||||||
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
||||||
simpleExoPlayer.setVideoListener(this);
|
simpleExoPlayer.addVideoListener(this);
|
||||||
|
|
||||||
trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context));
|
trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context));
|
||||||
}
|
}
|
||||||
|
@ -229,6 +226,79 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// UI Builders
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private final class TrackGroupInfo {
|
||||||
|
final int track;
|
||||||
|
final int group;
|
||||||
|
final Format format;
|
||||||
|
|
||||||
|
TrackGroupInfo(final int track, final int group, final Format format) {
|
||||||
|
this.track = track;
|
||||||
|
this.group = group;
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildQualityMenu() {
|
||||||
|
if (qualityPopupMenu == null || videoTrackGroups == null || selectedVideoTrackGroup == null || videoTrackGroups.length != availableStreams.size()) return;
|
||||||
|
|
||||||
|
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||||
|
trackGroupInfos = new ArrayList<>();
|
||||||
|
int acc = 0;
|
||||||
|
|
||||||
|
// Each group represent a source in sorted order of how the media source was built
|
||||||
|
for (int groupIndex = 0; groupIndex < videoTrackGroups.length; groupIndex++) {
|
||||||
|
final TrackGroup group = videoTrackGroups.get(groupIndex);
|
||||||
|
final VideoStream stream = availableStreams.get(groupIndex);
|
||||||
|
|
||||||
|
// For each source, there may be one or multiple tracks depending on the source type
|
||||||
|
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||||
|
final Format format = group.getFormat(trackIndex);
|
||||||
|
final boolean isSetCurrent = selectedVideoTrackGroup.indexOf(format) != -1;
|
||||||
|
|
||||||
|
if (group.length == 1 && videoTrackGroups.length == availableStreams.size()) {
|
||||||
|
// If the source is non-adaptive (extractor source), then we use the resolution contained in the stream
|
||||||
|
if (isSetCurrent) qualityTextView.setText(stream.resolution);
|
||||||
|
|
||||||
|
final String menuItem = MediaFormat.getNameById(stream.format) + " " +
|
||||||
|
stream.resolution + " (" + format.width + "x" + format.height + ")";
|
||||||
|
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
||||||
|
} else {
|
||||||
|
// Otherwise, we have an adaptive source, which contains multiple formats and
|
||||||
|
// thus have no inherent quality format
|
||||||
|
if (isSetCurrent) qualityTextView.setText(resolutionStringOf(format));
|
||||||
|
|
||||||
|
final MediaFormat mediaFormat = MediaFormat.getFromMimeType(format.sampleMimeType);
|
||||||
|
final String mediaName = mediaFormat == null ? format.sampleMimeType : mediaFormat.name;
|
||||||
|
|
||||||
|
final String menuItem = mediaName + " " + format.width + "x" + format.height;
|
||||||
|
qualityPopupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackGroupInfos.add(new TrackGroupInfo(trackIndex, groupIndex, format));
|
||||||
|
acc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qualityPopupMenu.setOnMenuItemClickListener(this);
|
||||||
|
qualityPopupMenu.setOnDismissListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildPlaybackSpeedMenu() {
|
||||||
|
if (playbackSpeedPopupMenu == null) return;
|
||||||
|
|
||||||
|
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
|
||||||
|
for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
|
||||||
|
playbackSpeedPopupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i]));
|
||||||
|
}
|
||||||
|
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
|
||||||
|
playbackSpeedPopupMenu.setOnMenuItemClickListener(this);
|
||||||
|
playbackSpeedPopupMenu.setOnDismissListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Playback Listener
|
// Playback Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -243,8 +313,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, videos);
|
selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, videos);
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
|
buildPlaybackSpeedMenu();
|
||||||
buildPlaybackSpeedMenu(playbackSpeedPopupMenu);
|
buildQualityMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaSource sourceOf(final StreamInfo info) {
|
public MediaSource sourceOf(final StreamInfo info) {
|
||||||
|
@ -259,15 +329,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
return new MergingMediaSource(sources.toArray(new MediaSource[sources.size()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildPlaybackSpeedMenu(PopupMenu popupMenu) {
|
|
||||||
for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) {
|
|
||||||
popupMenu.getMenu().add(playbackSpeedPopupMenuGroupId, i, Menu.NONE, formatSpeed(PLAYBACK_SPEEDS[i]));
|
|
||||||
}
|
|
||||||
playbackSpeed.setText(formatSpeed(getPlaybackSpeed()));
|
|
||||||
popupMenu.setOnMenuItemClickListener(this);
|
|
||||||
popupMenu.setOnDismissListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// States Implementation
|
// States Implementation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -343,22 +404,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
// ExoPlayer Video Listener
|
// ExoPlayer Video Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private class TrackGroupInfo {
|
|
||||||
final int track;
|
|
||||||
final int group;
|
|
||||||
final String label;
|
|
||||||
final String resolution;
|
|
||||||
final Format format;
|
|
||||||
|
|
||||||
TrackGroupInfo(final int track, final int group, final String label, final String resolution, final Format format) {
|
|
||||||
this.track = track;
|
|
||||||
this.group = group;
|
|
||||||
this.label = label;
|
|
||||||
this.resolution = resolution;
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||||
super.onTracksChanged(trackGroups, trackSelections);
|
super.onTracksChanged(trackGroups, trackSelections);
|
||||||
|
@ -376,52 +421,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
videoTrackGroups = trackSelector.getCurrentMappedTrackInfo().getTrackGroups(videoRendererIndex);
|
videoTrackGroups = trackSelector.getCurrentMappedTrackInfo().getTrackGroups(videoRendererIndex);
|
||||||
final TrackGroup selectedTrackGroup = trackSelections.get(videoRendererIndex).getTrackGroup();
|
selectedVideoTrackGroup = trackSelections.get(videoRendererIndex).getTrackGroup();
|
||||||
|
|
||||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
buildQualityMenu();
|
||||||
buildQualityMenu(qualityPopupMenu, videoTrackGroups, selectedTrackGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildQualityMenu(PopupMenu popupMenu, TrackGroupArray videoTrackGroups, TrackGroup selectedTrackGroup) {
|
|
||||||
trackGroupInfos = new ArrayList<>();
|
|
||||||
int acc = 0;
|
|
||||||
|
|
||||||
// Each group represent a source in sorted order of how the media source was built
|
|
||||||
for (int groupIndex = 0; groupIndex < videoTrackGroups.length; groupIndex++) {
|
|
||||||
final TrackGroup group = videoTrackGroups.get(groupIndex);
|
|
||||||
final VideoStream stream = availableStreams.get(groupIndex);
|
|
||||||
|
|
||||||
// For each source, there may be one or multiple tracks depending on the source type
|
|
||||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
|
||||||
final Format format = group.getFormat(trackIndex);
|
|
||||||
final boolean isSetCurrent = selectedTrackGroup.indexOf(format) != -1;
|
|
||||||
|
|
||||||
if (group.length == 1 && videoTrackGroups.length == availableStreams.size()) {
|
|
||||||
// If the source is non-adaptive (extractor source), then we use the resolution contained in the stream
|
|
||||||
if (isSetCurrent) qualityTextView.setText(stream.resolution);
|
|
||||||
|
|
||||||
final String menuItem = MediaFormat.getNameById(stream.format) + " " +
|
|
||||||
stream.resolution + " (" + format.width + "x" + format.height + ")";
|
|
||||||
popupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
|
||||||
} else {
|
|
||||||
// Otherwise, we have an adaptive source, which contains multiple formats and
|
|
||||||
// thus have no inherent quality format
|
|
||||||
if (isSetCurrent) qualityTextView.setText(resolutionStringOf(format));
|
|
||||||
|
|
||||||
final MediaFormat mediaFormat = MediaFormat.getFromMimeType(format.sampleMimeType);
|
|
||||||
final String mediaName = mediaFormat == null ? format.sampleMimeType : mediaFormat.name;
|
|
||||||
|
|
||||||
final String menuItem = mediaName + " " + format.width + "x" + format.height;
|
|
||||||
popupMenu.getMenu().add(qualityPopupMenuGroupId, acc, Menu.NONE, menuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackGroupInfos.add(new TrackGroupInfo(trackIndex, groupIndex, MediaFormat.getNameById(stream.format), stream.resolution, format));
|
|
||||||
acc++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
popupMenu.setOnMenuItemClickListener(this);
|
|
||||||
popupMenu.setOnDismissListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -13,7 +13,6 @@ import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import io.reactivex.SingleObserver;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.functions.Consumer;
|
||||||
|
@ -86,7 +85,7 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
*
|
*
|
||||||
* If loading fails here, an error will be propagated out and result in a
|
* If loading fails here, an error will be propagated out and result in a
|
||||||
* {@link com.google.android.exoplayer2.ExoPlaybackException}, which is delegated
|
* {@link com.google.android.exoplayer2.ExoPlaybackException}, which is delegated
|
||||||
* out to the player.
|
* to the player.
|
||||||
* */
|
* */
|
||||||
public synchronized void load() {
|
public synchronized void load() {
|
||||||
if (state != STATE_PREPARED || stream == null || loader != null) return;
|
if (state != STATE_PREPARED || stream == null || loader != null) return;
|
||||||
|
@ -95,15 +94,23 @@ public final class DeferredMediaSource implements MediaSource {
|
||||||
final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() {
|
final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(StreamInfo streamInfo) throws Exception {
|
public void accept(StreamInfo streamInfo) throws Exception {
|
||||||
if (exoPlayer == null && listener == null) {
|
|
||||||
error = new Throwable("Stream info loading failed. URL: " + stream.getUrl());
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, " Loaded: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
Log.d(TAG, " Loaded: [" + stream.getTitle() + "] with url: " + stream.getUrl());
|
||||||
|
state = STATE_LOADED;
|
||||||
|
|
||||||
|
if (exoPlayer == null || listener == null || streamInfo == null) {
|
||||||
|
error = new Throwable("Stream info loading failed. URL: " + stream.getUrl());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mediaSource = callback.sourceOf(streamInfo);
|
mediaSource = callback.sourceOf(streamInfo);
|
||||||
mediaSource.prepareSource(exoPlayer, false, listener);
|
if (mediaSource == null) {
|
||||||
state = STATE_LOADED;
|
error = new Throwable("Unable to resolve source from stream info. URL: " + stream.getUrl() +
|
||||||
|
", audio count: " + streamInfo.audio_streams.size() +
|
||||||
|
", video count: " + streamInfo.video_only_streams.size() + streamInfo.video_streams.size());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mediaSource.prepareSource(exoPlayer, false, listener);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||||
playQueue.append(data);
|
playQueue.append(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(final PlayQueueItem data) {
|
public void add(final PlayQueueItem... data) {
|
||||||
playQueue.append(data);
|
playQueue.append(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't ask why we have to do that this way... it's android accept it -.-
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
if(header != null && position == 0) {
|
if(header != null && position == 0) {
|
||||||
|
@ -167,15 +166,17 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
if(holder instanceof PlayQueueItemHolder) {
|
if(holder instanceof PlayQueueItemHolder) {
|
||||||
if(header != null) {
|
// Ensure header does not interfere with list building
|
||||||
i--;
|
if (header != null) position--;
|
||||||
}
|
// Build the list item
|
||||||
playQueueItemBuilder.buildStreamInfoItem((PlayQueueItemHolder) holder, playQueue.getStreams().get(i));
|
playQueueItemBuilder.buildStreamInfoItem((PlayQueueItemHolder) holder, playQueue.getStreams().get(position));
|
||||||
} else if(holder instanceof HFHolder && i == 0 && header != null) {
|
// Check if the current item should be selected/highlighted
|
||||||
|
holder.itemView.setSelected(playQueue.getIndex() == position);
|
||||||
|
} else if(holder instanceof HFHolder && position == 0 && header != null) {
|
||||||
((HFHolder) holder).view = header;
|
((HFHolder) holder).view = header;
|
||||||
} else if(holder instanceof HFHolder && i == playQueue.getStreams().size() && footer != null && showFooter) {
|
} else if(holder instanceof HFHolder && position == playQueue.getStreams().size() && footer != null && showFooter) {
|
||||||
((HFHolder) holder).view = footer;
|
((HFHolder) holder).view = footer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ public class PlayQueueItem implements Serializable {
|
||||||
final private String url;
|
final private String url;
|
||||||
final private int serviceId;
|
final private int serviceId;
|
||||||
final private long duration;
|
final private long duration;
|
||||||
|
final private String thumbnailUrl;
|
||||||
|
final private String uploader;
|
||||||
|
|
||||||
private Throwable error;
|
private Throwable error;
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ public class PlayQueueItem implements Serializable {
|
||||||
this.url = streamInfo.url;
|
this.url = streamInfo.url;
|
||||||
this.serviceId = streamInfo.service_id;
|
this.serviceId = streamInfo.service_id;
|
||||||
this.duration = streamInfo.duration;
|
this.duration = streamInfo.duration;
|
||||||
|
this.thumbnailUrl = streamInfo.thumbnail_url;
|
||||||
|
this.uploader = streamInfo.uploader_name;
|
||||||
|
|
||||||
this.stream = Single.just(streamInfo);
|
this.stream = Single.just(streamInfo);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +43,8 @@ public class PlayQueueItem implements Serializable {
|
||||||
this.url = streamInfoItem.url;
|
this.url = streamInfoItem.url;
|
||||||
this.serviceId = streamInfoItem.service_id;
|
this.serviceId = streamInfoItem.service_id;
|
||||||
this.duration = streamInfoItem.duration;
|
this.duration = streamInfoItem.duration;
|
||||||
|
this.thumbnailUrl = streamInfoItem.thumbnail_url;
|
||||||
|
this.uploader = streamInfoItem.uploader_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -59,6 +65,14 @@ public class PlayQueueItem implements Serializable {
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUploader() {
|
||||||
|
return uploader;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Throwable getError() {
|
public Throwable getError() {
|
||||||
return error;
|
return error;
|
||||||
|
|
|
@ -5,9 +5,11 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
import java.util.Locale;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
|
||||||
|
|
||||||
public class PlayQueueItemBuilder {
|
public class PlayQueueItemBuilder {
|
||||||
|
@ -15,68 +17,44 @@ public class PlayQueueItemBuilder {
|
||||||
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
||||||
|
|
||||||
public interface OnSelectedListener {
|
public interface OnSelectedListener {
|
||||||
void selected(int serviceId, String url, String title);
|
void selected(PlayQueueItem item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnSelectedListener onStreamInfoItemSelectedListener;
|
private OnSelectedListener onItemClickListener;
|
||||||
|
|
||||||
public PlayQueueItemBuilder() {}
|
public PlayQueueItemBuilder() {}
|
||||||
|
|
||||||
public void setOnSelectedListener(OnSelectedListener listener) {
|
public void setOnSelectedListener(OnSelectedListener listener) {
|
||||||
this.onStreamInfoItemSelectedListener = listener;
|
this.onItemClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public View buildView(ViewGroup parent, final PlayQueueItem item) {
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
|
||||||
final View itemView = inflater.inflate(R.layout.play_queue_item, parent, false);
|
|
||||||
final PlayQueueItemHolder holder = new PlayQueueItemHolder(itemView);
|
|
||||||
|
|
||||||
buildStreamInfoItem(holder, item);
|
|
||||||
|
|
||||||
return itemView;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void buildStreamInfoItem(PlayQueueItemHolder holder, final PlayQueueItem item) {
|
public void buildStreamInfoItem(PlayQueueItemHolder holder, final PlayQueueItem item) {
|
||||||
if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle());
|
if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle());
|
||||||
|
if (!TextUtils.isEmpty(item.getUploader())) holder.itemAdditionalDetailsView.setText(item.getUploader());
|
||||||
|
|
||||||
if (item.getDuration() > 0) {
|
if (item.getDuration() > 0) {
|
||||||
holder.itemDurationView.setText(getDurationString(item.getDuration()));
|
holder.itemDurationView.setText(Localization.getDurationString(item.getDuration()));
|
||||||
} else {
|
} else {
|
||||||
holder.itemDurationView.setVisibility(View.GONE);
|
holder.itemDurationView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, IMAGE_OPTIONS);
|
||||||
|
|
||||||
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
if(onStreamInfoItemSelectedListener != null) {
|
if (onItemClickListener != null) {
|
||||||
onStreamInfoItemSelectedListener.selected(item.getServiceId(), item.getUrl(), item.getTitle());
|
onItemClickListener.selected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final DisplayImageOptions IMAGE_OPTIONS =
|
||||||
public static String getDurationString(long duration) {
|
new DisplayImageOptions.Builder()
|
||||||
if(duration < 0) {
|
.cacheInMemory(true)
|
||||||
duration = 0;
|
.showImageOnFail(R.drawable.dummy_thumbnail)
|
||||||
}
|
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
|
||||||
String output;
|
.showImageOnLoading(R.drawable.dummy_thumbnail)
|
||||||
long days = duration / (24 * 60 * 60); /* greater than a day */
|
.build();
|
||||||
duration %= (24 * 60 * 60);
|
|
||||||
long hours = duration / (60 * 60); /* greater than an hour */
|
|
||||||
duration %= (60 * 60);
|
|
||||||
long minutes = duration / 60;
|
|
||||||
long seconds = duration % 60;
|
|
||||||
|
|
||||||
//handle days
|
|
||||||
if (days > 0) {
|
|
||||||
output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds);
|
|
||||||
} else if(hours > 0) {
|
|
||||||
output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
|
|
||||||
} else {
|
|
||||||
output = String.format(Locale.US, "%d:%02d", minutes, seconds);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,17 @@ import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||||
|
|
||||||
public class PlayQueueItemHolder extends RecyclerView.ViewHolder {
|
public class PlayQueueItemHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
public final TextView itemVideoTitleView, itemDurationView;
|
public final TextView itemVideoTitleView, itemDurationView, itemAdditionalDetailsView;
|
||||||
|
public final ImageView itemThumbnailView;
|
||||||
|
|
||||||
public final View itemRoot;
|
public final View itemRoot;
|
||||||
|
|
||||||
public PlayQueueItemHolder(View v) {
|
public PlayQueueItemHolder(View v) {
|
||||||
super(v);
|
super(v);
|
||||||
itemRoot = v.findViewById(R.id.itemRoot);
|
itemRoot = v.findViewById(R.id.itemRoot);
|
||||||
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
|
itemVideoTitleView = v.findViewById(R.id.itemVideoTitleView);
|
||||||
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
|
itemDurationView = v.findViewById(R.id.itemDurationView);
|
||||||
|
itemAdditionalDetailsView = v.findViewById(R.id.itemAdditionalDetails);
|
||||||
|
itemThumbnailView = v.findViewById(R.id.itemThumbnailView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true" android:color="@color/dark_youtube_primary_color"/>
|
||||||
|
<item android:color="@color/dark_youtube_accent_color"/>
|
||||||
|
</selector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true" android:color="@color/light_youtube_primary_color"/>
|
||||||
|
<item android:color="@color/light_youtube_accent_color"/>
|
||||||
|
</selector>
|
|
@ -0,0 +1,186 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/main_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context="org.schabi.newpipe.player.BackgroundPlayerActivity">
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="@dimen/appbar_padding_top"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
app:title="@string/app_name"/>
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
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:scrollbars="vertical"
|
||||||
|
app:layoutManager="LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/play_queue_item"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/metadata"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<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="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>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_above="@+id/playback_controls"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/current_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:text="-:--:--"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="1:06:29"/>
|
||||||
|
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatSeekBar
|
||||||
|
android:id="@+id/seek_bar"
|
||||||
|
style="@style/Widget.AppCompat.SeekBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
tools:progress="25"
|
||||||
|
tools:secondaryProgress="50"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/end_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="-:--:--"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="1:23:49"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/playback_controls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/player_controls_bg"
|
||||||
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/control_repeat"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_repeat_white"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/control_backward"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginRight="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/ic_action_av_fast_rewind"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/control_play_pause"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginRight="5dp"
|
||||||
|
android:layout_toLeftOf="@+id/control_forward"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_pause_white"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/control_forward"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_action_av_fast_forward"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -7,6 +7,7 @@
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
android:padding="6dp">
|
android:padding="6dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:textSize="@dimen/video_item_search_title_text_size"
|
android:textSize="@dimen/video_item_search_title_text_size"
|
||||||
|
android:textColor="?attr/selector_color"
|
||||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -66,5 +68,6 @@
|
||||||
android:lines="1"
|
android:lines="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||||
tools:text="Uploader • 2 years ago • 10M views"/>
|
android:textColor="?attr/selector_color"
|
||||||
|
tools:text="Uploader"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -1,51 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/itemRoot"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/video_item_search_height"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:padding="@dimen/video_item_search_padding">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/itemThumbnailView"
|
|
||||||
android:layout_width="@dimen/video_item_search_thumbnail_image_width"
|
|
||||||
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
|
|
||||||
android:contentDescription="@string/list_thumbnail_view_description"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:src="@drawable/dummy_thumbnail"
|
|
||||||
tools:ignore="RtlHardcoded"/>
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itemPlaylistTitleView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_toRightOf="@id/itemThumbnailView"
|
|
||||||
android:layout_toEndOf="@id/itemThumbnailView"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:lines="3"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:textSize="@dimen/video_item_search_title_text_size"
|
|
||||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itemAdditionalDetails"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_toRightOf="@id/itemThumbnailView"
|
|
||||||
android:layout_toEndOf="@id/itemThumbnailView"
|
|
||||||
android:lines="1"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
|
||||||
android:text="@string/playlist"/>
|
|
||||||
</RelativeLayout>
|
|
|
@ -22,6 +22,7 @@
|
||||||
<!-- Can't refer to colors directly into drawable's xml-->
|
<!-- Can't refer to colors directly into drawable's xml-->
|
||||||
<attr name="toolbar_shadow_drawable" format="reference"/>
|
<attr name="toolbar_shadow_drawable" format="reference"/>
|
||||||
|
|
||||||
|
<attr name="selector_color" format="color"/>
|
||||||
<attr name="separator_color" format="color"/>
|
<attr name="separator_color" format="color"/>
|
||||||
<attr name="contrast_background_color" format="color"/>
|
<attr name="contrast_background_color" format="color"/>
|
||||||
</resources>
|
</resources>
|
|
@ -292,4 +292,7 @@
|
||||||
<string name="top_50">Top 50</string>
|
<string name="top_50">Top 50</string>
|
||||||
<string name="new_and_hot">New & hot</string>
|
<string name="new_and_hot">New & hot</string>
|
||||||
<string name="service_kiosk_string" translatable="false">%1$s/%2$s</string>
|
<string name="service_kiosk_string" translatable="false">%1$s/%2$s</string>
|
||||||
|
|
||||||
|
<!-- Player -->
|
||||||
|
<string name="title_activity_background_player">Background Player</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<item name="language">@drawable/ic_language_black_24dp</item>
|
<item name="language">@drawable/ic_language_black_24dp</item>
|
||||||
<item name="history">@drawable/ic_history_black_24dp</item>
|
<item name="history">@drawable/ic_history_black_24dp</item>
|
||||||
|
|
||||||
|
<item name="selector_color">@color/light_selector</item>
|
||||||
<item name="separator_color">@color/light_separator_color</item>
|
<item name="separator_color">@color/light_separator_color</item>
|
||||||
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
||||||
<item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_light</item>
|
<item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_light</item>
|
||||||
|
@ -60,6 +61,7 @@
|
||||||
<item name="language">@drawable/ic_language_white_24dp</item>
|
<item name="language">@drawable/ic_language_white_24dp</item>
|
||||||
<item name="history">@drawable/ic_history_white_24dp</item>
|
<item name="history">@drawable/ic_history_white_24dp</item>
|
||||||
|
|
||||||
|
<item name="selector_color">@color/dark_selector</item>
|
||||||
<item name="separator_color">@color/dark_separator_color</item>
|
<item name="separator_color">@color/dark_separator_color</item>
|
||||||
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
||||||
<item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_dark</item>
|
<item name="toolbar_shadow_drawable">@drawable/toolbar_shadow_dark</item>
|
||||||
|
|
Loading…
Reference in New Issue