mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-10-24 11:57:38 +00:00
- 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:
committed by
John Zhen Mo
parent
bd9ee18e56
commit
a9aee21e58
@@ -27,6 +27,7 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
@@ -36,9 +37,9 @@ import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
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.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Base players joining the common properties
|
||||
@@ -78,13 +76,30 @@ public final class BackgroundPlayer extends Service {
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private static final int NOTIFICATION_ID = 123789;
|
||||
|
||||
private boolean shouldUpdateNotification;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private NotificationCompat.Builder notBuilder;
|
||||
private RemoteViews notRemoteView;
|
||||
@@ -105,6 +120,8 @@ public final class BackgroundPlayer extends Service {
|
||||
ThemeHelper.setTheme(this);
|
||||
basePlayerImpl = new BasePlayerImpl(this);
|
||||
basePlayerImpl.setup();
|
||||
|
||||
mBinder = new LocalBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,13 +141,19 @@ public final class BackgroundPlayer extends Service {
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// 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) {
|
||||
if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
|
||||
Intent i = new Intent(context, MainActivity.class);
|
||||
@@ -144,7 +167,11 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
if (basePlayerImpl != null) basePlayerImpl.destroyPlayer();
|
||||
if (basePlayerImpl != null) {
|
||||
basePlayerImpl.stopActivityBinding();
|
||||
basePlayerImpl.destroyPlayer();
|
||||
}
|
||||
|
||||
stopForeground(true);
|
||||
releaseWifiAndCpu();
|
||||
stopSelf();
|
||||
@@ -152,8 +179,6 @@ public final class BackgroundPlayer extends Service {
|
||||
|
||||
private void onScreenOnOff(boolean on) {
|
||||
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||
shouldUpdateNotification = on;
|
||||
|
||||
if (on) {
|
||||
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning()) {
|
||||
basePlayerImpl.startProgressLoop();
|
||||
@@ -168,9 +193,7 @@ public final class BackgroundPlayer extends Service {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void resetNotification() {
|
||||
if (shouldUpdateNotification) {
|
||||
notBuilder = createNotification();
|
||||
}
|
||||
notBuilder = createNotification();
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder createNotification() {
|
||||
@@ -211,7 +234,7 @@ public final class BackgroundPlayer extends Service {
|
||||
break;
|
||||
case Player.REPEAT_MODE_ONE:
|
||||
// todo change image
|
||||
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
|
||||
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 168);
|
||||
break;
|
||||
case Player.REPEAT_MODE_ALL:
|
||||
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
|
||||
@@ -227,7 +250,7 @@ public final class BackgroundPlayer extends Service {
|
||||
*/
|
||||
private synchronized void updateNotification(int drawableId) {
|
||||
//if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
|
||||
if (notBuilder == null || !shouldUpdateNotification) return;
|
||||
if (notBuilder == null) return;
|
||||
if (drawableId != -1) {
|
||||
if (notRemoteView != null) notRemoteView.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) {
|
||||
super(context);
|
||||
@@ -280,8 +303,7 @@ public final class BackgroundPlayer extends Service {
|
||||
public void handleIntent(Intent intent) {
|
||||
super.handleIntent(intent);
|
||||
|
||||
shouldUpdateNotification = true;
|
||||
notBuilder = createNotification();
|
||||
resetNotification();
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
|
||||
@@ -329,23 +351,6 @@ public final class BackgroundPlayer extends Service {
|
||||
@Override
|
||||
public void 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
|
||||
@@ -368,6 +373,7 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
|
||||
updateNotification(-1);
|
||||
updateProgress(currentProgress, duration, bufferPercent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -386,16 +392,6 @@ public final class BackgroundPlayer extends Service {
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingChanged(boolean isLoading) {
|
||||
// Disable default behavior
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
@@ -408,6 +404,42 @@ public final class BackgroundPlayer extends Service {
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -422,11 +454,14 @@ public final class BackgroundPlayer extends Service {
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationSongName, getVideoTitle());
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationArtist, getUploaderName());
|
||||
updateNotification(-1);
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(final StreamInfo info) {
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams);
|
||||
if (index < 0) return null;
|
||||
|
||||
final AudioStream audio = info.audio_streams.get(index);
|
||||
return buildMediaSource(audio.url, MediaFormat.getSuffixById(audio.format));
|
||||
}
|
||||
@@ -435,6 +470,43 @@ public final class BackgroundPlayer extends Service {
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
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();
|
||||
break;
|
||||
case ACTION_OPEN_DETAIL:
|
||||
onOpenDetail(BackgroundPlayer.this, getVideoUrl(), getVideoTitle());
|
||||
openControl(BackgroundPlayer.this);
|
||||
break;
|
||||
case ACTION_REPEAT:
|
||||
onRepeatClicked();
|
||||
@@ -493,6 +565,12 @@ public final class BackgroundPlayer extends Service {
|
||||
// States
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void changeState(int state) {
|
||||
super.changeState(state);
|
||||
updatePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void 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.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
@@ -35,7 +34,6 @@ import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
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.util.Util;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.ImageSize;
|
||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.Downloader;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
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.PlaybackListener;
|
||||
import org.schabi.newpipe.playlist.ExternalPlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueAdapter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -124,6 +115,8 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
protected BroadcastReceiver broadcastReceiver;
|
||||
protected IntentFilter intentFilter;
|
||||
|
||||
protected PlayQueueAdapter playQueueAdapter;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Intent
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -285,6 +278,9 @@ public abstract class BasePlayer implements Player.EventListener,
|
||||
playQueue = queue;
|
||||
playQueue.init();
|
||||
playbackManager = new MediaSourceManager(this, playQueue);
|
||||
|
||||
if (playQueueAdapter != null) playQueueAdapter.dispose();
|
||||
playQueueAdapter = new PlayQueueAdapter(playQueue);
|
||||
}
|
||||
|
||||
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 NumberFormat speedFormatter = new DecimalFormat("0.##x");
|
||||
|
||||
// todo: merge this into Localization
|
||||
public String getTimeString(int milliSeconds) {
|
||||
long seconds = (milliSeconds % 60000L) / 1000L;
|
||||
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.stream.StreamInfo;
|
||||
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.ListHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -111,6 +107,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
private List<TrackGroupInfo> trackGroupInfos;
|
||||
private int videoRendererIndex = -1;
|
||||
private TrackGroupArray videoTrackGroups;
|
||||
private TrackGroup selectedVideoTrackGroup;
|
||||
|
||||
private boolean startedFromNewPipe = true;
|
||||
protected boolean wasPlaying = false;
|
||||
@@ -211,7 +208,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
public void initPlayer() {
|
||||
super.initPlayer();
|
||||
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
||||
simpleExoPlayer.setVideoListener(this);
|
||||
simpleExoPlayer.addVideoListener(this);
|
||||
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -243,8 +313,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, videos);
|
||||
}
|
||||
|
||||
playbackSpeedPopupMenu.getMenu().removeGroup(playbackSpeedPopupMenuGroupId);
|
||||
buildPlaybackSpeedMenu(playbackSpeedPopupMenu);
|
||||
buildPlaybackSpeedMenu();
|
||||
buildQualityMenu();
|
||||
}
|
||||
|
||||
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()]));
|
||||
}
|
||||
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -343,22 +404,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
// 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
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
super.onTracksChanged(trackGroups, trackSelections);
|
||||
@@ -376,52 +421,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
||||
}
|
||||
}
|
||||
videoTrackGroups = trackSelector.getCurrentMappedTrackInfo().getTrackGroups(videoRendererIndex);
|
||||
final TrackGroup selectedTrackGroup = trackSelections.get(videoRendererIndex).getTrackGroup();
|
||||
selectedVideoTrackGroup = trackSelections.get(videoRendererIndex).getTrackGroup();
|
||||
|
||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||
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);
|
||||
buildQualityMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
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
|
||||
* {@link com.google.android.exoplayer2.ExoPlaybackException}, which is delegated
|
||||
* out to the player.
|
||||
* to the player.
|
||||
* */
|
||||
public synchronized void load() {
|
||||
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>() {
|
||||
@Override
|
||||
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;
|
||||
|
||||
mediaSource = callback.sourceOf(streamInfo);
|
||||
mediaSource.prepareSource(exoPlayer, false, listener);
|
||||
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);
|
||||
if (mediaSource == null) {
|
||||
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);
|
||||
}
|
||||
|
||||
public void add(final PlayQueueItem data) {
|
||||
public void add(final PlayQueueItem... data) {
|
||||
playQueue.append(data);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
return count;
|
||||
}
|
||||
|
||||
// don't ask why we have to do that this way... it's android accept it -.-
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if(header != null && position == 0) {
|
||||
@@ -167,15 +166,17 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
if(holder instanceof PlayQueueItemHolder) {
|
||||
if(header != null) {
|
||||
i--;
|
||||
}
|
||||
playQueueItemBuilder.buildStreamInfoItem((PlayQueueItemHolder) holder, playQueue.getStreams().get(i));
|
||||
} else if(holder instanceof HFHolder && i == 0 && header != null) {
|
||||
// Ensure header does not interfere with list building
|
||||
if (header != null) position--;
|
||||
// Build the list item
|
||||
playQueueItemBuilder.buildStreamInfoItem((PlayQueueItemHolder) holder, playQueue.getStreams().get(position));
|
||||
// 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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public class PlayQueueItem implements Serializable {
|
||||
final private String url;
|
||||
final private int serviceId;
|
||||
final private long duration;
|
||||
final private String thumbnailUrl;
|
||||
final private String uploader;
|
||||
|
||||
private Throwable error;
|
||||
|
||||
@@ -30,6 +32,8 @@ public class PlayQueueItem implements Serializable {
|
||||
this.url = streamInfo.url;
|
||||
this.serviceId = streamInfo.service_id;
|
||||
this.duration = streamInfo.duration;
|
||||
this.thumbnailUrl = streamInfo.thumbnail_url;
|
||||
this.uploader = streamInfo.uploader_name;
|
||||
|
||||
this.stream = Single.just(streamInfo);
|
||||
}
|
||||
@@ -39,6 +43,8 @@ public class PlayQueueItem implements Serializable {
|
||||
this.url = streamInfoItem.url;
|
||||
this.serviceId = streamInfoItem.service_id;
|
||||
this.duration = streamInfoItem.duration;
|
||||
this.thumbnailUrl = streamInfoItem.thumbnail_url;
|
||||
this.uploader = streamInfoItem.uploader_name;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -59,6 +65,14 @@ public class PlayQueueItem implements Serializable {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
|
||||
@@ -5,9 +5,11 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
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 {
|
||||
@@ -15,68 +17,44 @@ public class PlayQueueItemBuilder {
|
||||
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
||||
|
||||
public interface OnSelectedListener {
|
||||
void selected(int serviceId, String url, String title);
|
||||
void selected(PlayQueueItem item);
|
||||
}
|
||||
|
||||
private OnSelectedListener onStreamInfoItemSelectedListener;
|
||||
private OnSelectedListener onItemClickListener;
|
||||
|
||||
public PlayQueueItemBuilder() {}
|
||||
|
||||
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) {
|
||||
if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle());
|
||||
if (!TextUtils.isEmpty(item.getUploader())) holder.itemAdditionalDetailsView.setText(item.getUploader());
|
||||
|
||||
if (item.getDuration() > 0) {
|
||||
holder.itemDurationView.setText(getDurationString(item.getDuration()));
|
||||
holder.itemDurationView.setText(Localization.getDurationString(item.getDuration()));
|
||||
} else {
|
||||
holder.itemDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, IMAGE_OPTIONS);
|
||||
|
||||
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(onStreamInfoItemSelectedListener != null) {
|
||||
onStreamInfoItemSelectedListener.selected(item.getServiceId(), item.getUrl(), item.getTitle());
|
||||
if (onItemClickListener != null) {
|
||||
onItemClickListener.selected(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static String getDurationString(long duration) {
|
||||
if(duration < 0) {
|
||||
duration = 0;
|
||||
}
|
||||
String output;
|
||||
long days = duration / (24 * 60 * 60); /* greater than a day */
|
||||
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;
|
||||
}
|
||||
private static final DisplayImageOptions IMAGE_OPTIONS =
|
||||
new DisplayImageOptions.Builder()
|
||||
.cacheInMemory(true)
|
||||
.showImageOnFail(R.drawable.dummy_thumbnail)
|
||||
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
|
||||
.showImageOnLoading(R.drawable.dummy_thumbnail)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -31,13 +31,17 @@ import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
|
||||
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 PlayQueueItemHolder(View v) {
|
||||
super(v);
|
||||
itemRoot = v.findViewById(R.id.itemRoot);
|
||||
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
|
||||
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
|
||||
itemVideoTitleView = v.findViewById(R.id.itemVideoTitleView);
|
||||
itemDurationView = v.findViewById(R.id.itemDurationView);
|
||||
itemAdditionalDetailsView = v.findViewById(R.id.itemAdditionalDetails);
|
||||
itemThumbnailView = v.findViewById(R.id.itemThumbnailView);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user