Merge branch 'feature-backplayer' of https://github.com/mauriciocolli/NewPipe into mark

This commit is contained in:
Christian Schabesberger 2017-04-18 22:06:47 +02:00
commit 8e7d2e91e9
16 changed files with 1609 additions and 1227 deletions

View File

@ -40,7 +40,7 @@
android:label="@string/background_player_name" /> android:label="@string/background_player_name" />
<activity <activity
android:name=".player.ExoPlayerActivity" android:name=".player.MainVideoPlayer"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTask" android:launchMode="singleTask"

View File

@ -20,10 +20,6 @@ package org.schabi.newpipe;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
import android.graphics.Bitmap;
import java.util.List;
/** /**
* Singleton: * Singleton:
* Used to send data between certain Activity/Services within the same process. * Used to send data between certain Activity/Services within the same process.
@ -39,8 +35,5 @@ public class ActivityCommunicator {
return activityCommunicator; return activityCommunicator;
} }
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
public volatile Bitmap backgroundPlayerThumbnail;
public volatile Class returnActivity; public volatile Class returnActivity;
} }

View File

@ -45,7 +45,7 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener { public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
private static final String TAG = MainActivity.class.toString(); //private static final String TAG = "MainActivity";
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle // Activity's LifeCycle
@ -57,12 +57,23 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC); setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (savedInstanceState == null) initFragments();
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack
String action = intent.getAction();
if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return;
}
super.onNewIntent(intent); super.onNewIntent(intent);
setIntent(intent);
handleIntent(intent); handleIntent(intent);
} }

View File

@ -7,7 +7,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -43,7 +42,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.ImageErrorLoadingListener; import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.Localization; import org.schabi.newpipe.Localization;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -57,9 +55,7 @@ import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream; import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.fragments.OnItemSelectedListener; import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.AbstractPlayer; import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.PlayVideoActivity; import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
@ -111,7 +107,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
private static final ImageLoader imageLoader = ImageLoader.getInstance(); private static final ImageLoader imageLoader = ImageLoader.getInstance();
private static final DisplayImageOptions displayImageOptions = private static final DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build(); new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
private Bitmap streamThumbnail = null;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
@ -409,25 +404,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) { if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, imageLoader.displayImage(info.thumbnail_url, thumbnailImageView,
displayImageOptions, new SimpleImageLoadingListener() { displayImageOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
streamThumbnail = loadedImage;
if (streamThumbnail != null) {
// TODO: Change the thumbnail implementation
// When the thumbnail is not loaded yet, it not passes to the service in time
// so, I can notify the service through a broadcast, but the problem is
// when I click in another video, another thumbnail will be load, and will
// notify again, so I send the videoUrl and compare with the service's url
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB);
intent.putExtra(AbstractPlayer.VIDEO_URL, currentStreamInfo.webpage_url);
activity.sendBroadcast(intent);
}
}
@Override @Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) { public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(activity, ErrorActivity.reportError(activity,
@ -529,11 +505,9 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show(); Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return; return;
} }
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
Intent mIntent = NavigationHelper.getOpenPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId); Intent mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId);
if (info.start_position > 0) mIntent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
activity.startService(mIntent); activity.startService(mIntent);
} }
}); });
@ -624,24 +598,10 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity) boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false); .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
Intent intent; Intent intent;
AudioStream audioStream = AudioStream audioStream = info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams)); if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) { activity.startService(NavigationHelper.getOpenBackgroundPlayerIntent(activity, info, audioStream));
//internal music player: explicit intent Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
ActivityCommunicator.getCommunicator()
.backgroundPlayerThumbnail = streamThumbnail;
intent = new Intent(activity, BackgroundPlayer.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url),
MediaFormat.getMimeById(audioStream.format));
intent.putExtra(BackgroundPlayer.TITLE, info.title);
intent.putExtra(BackgroundPlayer.WEB_URL, info.webpage_url);
intent.putExtra(BackgroundPlayer.SERVICE_ID, serviceId);
intent.putExtra(BackgroundPlayer.CHANNEL_NAME, info.uploader);
activity.startService(intent);
}
} else { } else {
intent = new Intent(); intent = new Intent();
try { try {
@ -829,9 +789,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|| (Build.VERSION.SDK_INT < 16); || (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) { if (!useOldPlayer) {
// ExoPlayer // ExoPlayer
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail; mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, MainVideoPlayer.class, info, actionBarHandler.getSelectedVideoStream());
mIntent = NavigationHelper.getOpenPlayerIntent(activity, ExoPlayerActivity.class, info, actionBarHandler.getSelectedVideoStream());
if (info.start_position > 0) mIntent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
} else { } else {
// Internal Player // Internal Player
mIntent = new Intent(activity, PlayVideoActivity.class) mIntent = new Intent(activity, PlayVideoActivity.class)

View File

@ -4,584 +4,452 @@ import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.v7.app.NotificationCompat; import android.support.annotation.IntRange;
import android.support.v4.app.NotificationCompat;
import android.util.Log; import android.util.Log;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import android.widget.Toast;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.io.IOException;
import java.util.Arrays;
/** /**
* Created by Adam Howard on 08/11/15. * Base players joining the common properties
* Copyright (c) Adam Howard <achdisposable1@gmail.com> 2015
* *
* BackgroundPlayer.java is part of NewPipe. * @author mauriciocolli
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
public class BackgroundPlayer extends Service {
/**Plays the audio stream of videos in the background.*/
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
private static final String TAG = "BackgroundPlayer"; private static final String TAG = "BackgroundPlayer";
private static final String CLASSNAME = "org.schabi.newpipe.player.BackgroundPlayer"; private static final boolean DEBUG = BasePlayer.DEBUG;
private static final String ACTION_STOP = CLASSNAME + ".STOP";
private static final String ACTION_PLAYPAUSE = CLASSNAME + ".PLAYPAUSE";
private static final String ACTION_REWIND = CLASSNAME + ".REWIND";
private static final String ACTION_PLAYBACK_STATE = CLASSNAME + ".PLAYBACK_STATE";
private static final String EXTRA_PLAYBACK_STATE = CLASSNAME + ".extras.EXTRA_PLAYBACK_STATE";
// Extra intent arguments public static final String ACTION_CLOSE = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
public static final String TITLE = "title"; public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
public static final String WEB_URL = "web_url"; public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.BackgroundPlayer.OPEN_DETAIL";
public static final String SERVICE_ID = "service_id"; public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
public static final String CHANNEL_NAME = "channel_name"; public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
private volatile String webUrl = ""; public static final String AUDIO_STREAM = "video_only_audio_stream";
private volatile int serviceId = -1; private AudioStream audioStream;
private volatile String channelName = "";
// Determines if the service is already running. private BasePlayerImpl basePlayerImpl;
// Prevents launching the service twice. private PowerManager powerManager;
public static volatile boolean isRunning; private WifiManager wifiManager;
public BackgroundPlayer() { private PowerManager.WakeLock wakeLock;
super(); private WifiManager.WifiLock wifiLock;
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private static final int NOTIFICATION_ID = 123789;
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private RemoteViews bigNotRemoteView;
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onCreate() { public void onCreate() {
/*PendingIntent pi = PendingIntent.getActivity(this, 0, if (DEBUG) Log.d(TAG, "onCreate() called");
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);*/ notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
super.onCreate(); powerManager = ((PowerManager) getSystemService(POWER_SERVICE));
wifiManager = ((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE));
ThemeHelper.setTheme(this, false);
basePlayerImpl = new BasePlayerImpl(this);
basePlayerImpl.setup();
} }
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, R.string.background_player_playing_toast, if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
Toast.LENGTH_SHORT).show(); basePlayerImpl.handleIntent(intent);
String source = intent.getDataString();
//Log.i(TAG, "backgroundPLayer source:"+source);
String videoTitle = intent.getStringExtra(TITLE);
webUrl = intent.getStringExtra(WEB_URL);
serviceId = intent.getIntExtra(SERVICE_ID, -1);
channelName = intent.getStringExtra(CHANNEL_NAME);
//do nearly everything in a separate thread
PlayerThread player = new PlayerThread(source, videoTitle, this);
player.start();
isRunning = true;
// If we get killed after returning here, don't restart
return START_NOT_STICKY; return START_NOT_STICKY;
} }
@Override @Override
public IBinder onBind(Intent intent) { public void onDestroy() {
// We don't provide binding (yet?), so return null if (DEBUG) Log.d(TAG, "destroy() called");
return null; releaseWifiAndCpu();
stopForeground(true);
if (basePlayerImpl != null) basePlayerImpl.destroy();
} }
@Override @Override
public void onDestroy() { public IBinder onBind(Intent intent) {
//Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); return null;
isRunning = false;
} }
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
private class PlayerThread extends Thread { public void onOpenDetail(Context context, String videoUrl, String videoTitle) {
MediaPlayer mediaPlayer; if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
private String source; Intent i = new Intent(context, MainActivity.class);
private String title; i.putExtra(Constants.KEY_SERVICE_ID, 0);
private int noteID = TAG.hashCode(); i.putExtra(Constants.KEY_URL, videoUrl);
private BackgroundPlayer owner; i.putExtra(Constants.KEY_TITLE, videoTitle);
private NotificationManager noteMgr; i.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
private WifiManager.WifiLock wifiLock; i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
private Bitmap videoThumbnail; context.startActivity(i);
private NoteBuilder noteBuilder; context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
private volatile boolean donePlaying = false; }
public PlayerThread(String src, String title, BackgroundPlayer owner) { private void onClose() {
this.source = src; if (basePlayerImpl != null) basePlayerImpl.destroyPlayer();
this.title = title; stopForeground(true);
this.owner = owner; releaseWifiAndCpu();
mediaPlayer = new MediaPlayer(); stopSelf();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); }
}
public boolean isDonePlaying() { private void onScreenOnOff(boolean on) {
return donePlaying; if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
} if (on) {
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning.get()) basePlayerImpl.startProgressLoop();
} else basePlayerImpl.stopProgressLoop();
private boolean isPlaying() { }
try {
return mediaPlayer.isPlaying();
} catch (IllegalStateException e) {
Log.w(TAG, "Unable to retrieve playing state", e);
return false;
}
}
private void setDonePlaying() { /*//////////////////////////////////////////////////////////////////////////
donePlaying = true; // Notification
synchronized (PlayerThread.this) { //////////////////////////////////////////////////////////////////////////*/
PlayerThread.this.notifyAll();
}
}
private synchronized PlaybackState getPlaybackState() { private NotificationCompat.Builder createNotification() {
try { notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
return new PlaybackState(mediaPlayer.getDuration(), mediaPlayer.getCurrentPosition(), isPlaying()); bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
} catch (IllegalStateException e) {
// This isn't that nice way to handle this.
// maybe there is a better way
Log.w(TAG, this + ": Got illegal state exception while creating playback state", e);
return PlaybackState.UNPREPARED;
}
}
private void broadcastState() { setupNotification(notRemoteView);
PlaybackState state = getPlaybackState(); setupNotification(bigNotRemoteView);
if(state == null) return;
Intent intent = new Intent(ACTION_PLAYBACK_STATE);
intent.putExtra(EXTRA_PLAYBACK_STATE, state);
sendBroadcast(intent);
}
@Override NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
public void run() { .setOngoing(true)
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock .setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
try { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
mediaPlayer.setDataSource(source); .setCustomContentView(notRemoteView)
//We are already in a separate worker thread, .setCustomBigContentView(bigNotRemoteView);
//so calling the blocking prepare() method should be ok if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) builder.setPriority(Notification.PRIORITY_MAX);
mediaPlayer.prepare(); return builder;
}
} catch (IOException ioe) { private void setupNotification(RemoteViews remoteViews) {
ioe.printStackTrace(); //if (videoThumbnail != null) remoteViews.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
Log.e(TAG, "video source:" + source); ///else remoteViews.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
Log.e(TAG, "video title:" + title); remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
//can't do anything useful without a file to play; exit early remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getChannelName());
return;
}
try { remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail; PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
} catch (Exception e) { remoteViews.setOnClickPendingIntent(R.id.notificationStop,
Log.e(TAG, "Could not get video thumbnail from ActivityCommunicator"); PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
e.printStackTrace(); remoteViews.setOnClickPendingIntent(R.id.notificationContent,
} PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
WifiManager wifiMgr = (WifiManager)getSystemService(Context.WIFI_SERVICE); remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
//listen for end of video PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
mediaPlayer.setOnCompletionListener(new EndListener(wifiLock));
//get audio focus
/*
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
}*/
wifiLock.acquire();
mediaPlayer.start();
IntentFilter filter = new IntentFilter();
filter.setPriority(Integer.MAX_VALUE);
filter.addAction(ACTION_PLAYPAUSE);
filter.addAction(ACTION_STOP);
filter.addAction(ACTION_REWIND);
filter.addAction(ACTION_PLAYBACK_STATE);
registerReceiver(broadcastReceiver, filter);
initNotificationBuilder();
startForeground(noteID, noteBuilder.build());
//currently decommissioned progressbar looping update code - works, but doesn't fit inside
//Notification.MediaStyle Notification layout.
noteMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
//update every 2s or 4 times in the video, whichever is shorter
int vidLength = mediaPlayer.getDuration();
int sleepTime = Math.min(2000, (int)(vidLength / 4));
while(!isDonePlaying()) {
broadcastState();
try {
synchronized (this) {
wait(sleepTime);
}
} catch (InterruptedException e) {
Log.e(TAG, "sleep failure", e);
}
}
}
/**Handles button presses from the notification. */
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//Log.i(TAG, "received broadcast action:"+action);
switch (action) {
case ACTION_PLAYPAUSE: {
boolean isPlaying = mediaPlayer.isPlaying();
if(isPlaying) {
mediaPlayer.pause();
} else {
//reacquire CPU lock after auto-releasing it on pause
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
}
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
break;
}
case ACTION_REWIND:
mediaPlayer.seekTo(0);
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
break;
case ACTION_STOP:
//this auto-releases CPU lock
mediaPlayer.stop();
afterPlayCleanup();
break;
case ACTION_PLAYBACK_STATE: {
PlaybackState playbackState = intent.getParcelableExtra(EXTRA_PLAYBACK_STATE);
if(!playbackState.equals(PlaybackState.UNPREPARED)) {
noteBuilder.setProgress(playbackState.getDuration(), playbackState.getPlayedTime(), false);
noteBuilder.setIsPlaying(playbackState.isPlaying());
} else {
noteBuilder.setProgress(0, 0, true);
}
noteMgr.notify(noteID, noteBuilder.build());
break;
}
}
}
};
private void afterPlayCleanup() {
// Notify thread to stop
setDonePlaying();
//remove progress bar
//noteBuilder.setProgress(0, 0, false);
//remove notification
noteMgr.cancel(noteID);
unregisterReceiver(broadcastReceiver);
//release mediaPlayer's system resources
mediaPlayer.release();
//release wifilock
wifiLock.release();
//remove foreground status of service; make BackgroundPlayer killable
stopForeground(true);
try {
// Wait for thread to stop
PlayerThread.this.join();
} catch (InterruptedException e) {
Log.e(TAG, "unable to join player thread", e);
}
stopSelf();
}
private class EndListener implements MediaPlayer.OnCompletionListener {
private WifiManager.WifiLock wl;
public EndListener(WifiManager.WifiLock wifiLock) {
this.wl = wifiLock;
}
@Override
public void onCompletion(MediaPlayer mp) {
afterPlayCleanup();
}
}
private void initNotificationBuilder() {
Notification note;
Resources res = getApplicationContext().getResources();
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
(R.drawable.ic_pause_white, "Pause", playPI).build();
*/
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent stopPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent rewindPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT);
//build intent to return to video, on tapping notification
Intent openDetailViewIntent = new Intent(getApplicationContext(), MainActivity.class);
openDetailViewIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
openDetailViewIntent.putExtra(Constants.KEY_URL, webUrl);
openDetailViewIntent.putExtra(Constants.KEY_TITLE, title);
openDetailViewIntent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID,
openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT);
noteBuilder = new NoteBuilder(owner, playPI, stopPI, rewindPI, openDetailView);
noteBuilder
.setTitle(title)
.setArtist(channelName)
.setOngoing(true)
.setDeleteIntent(stopPI)
//doesn't fit with Notification.MediaStyle
//.setProgress(vidLength, 0, false)
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(),
noteID, openDetailViewIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.setContentIntent(openDetailView)
.setCategory(Notification.CATEGORY_TRANSPORT)
//Make notification appear on lockscreen
.setVisibility(Notification.VISIBILITY_PUBLIC);
}
/**
* Notification builder which works like the real builder but uses a custom view.
*/
class NoteBuilder extends NotificationCompat.Builder {
/**
* @param context
* @inheritDoc
*/
public NoteBuilder(Context context, PendingIntent playPI, PendingIntent stopPI,
PendingIntent rewindPI, PendingIntent openDetailView) {
super(context);
setCustomContentView(createCustomContentView(playPI, stopPI, rewindPI, openDetailView));
setCustomBigContentView(createCustomBigContentView(playPI, stopPI, rewindPI, openDetailView));
}
private RemoteViews createCustomBigContentView(PendingIntent playPI,
PendingIntent stopPI,
PendingIntent rewindPI,
PendingIntent openDetailView) {
//possibly found the expandedView problem,
//but can't test it as I don't have a 5.0 device. -medavox
RemoteViews expandedView =
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI);
expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
return expandedView;
}
private RemoteViews createCustomContentView(PendingIntent playPI, PendingIntent stopPI,
PendingIntent rewindPI,
PendingIntent openDetailView) {
RemoteViews view = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
view.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
view.setOnClickPendingIntent(R.id.notificationStop, stopPI);
view.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
view.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
view.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
return view;
}
/**
* Set the title of the stream
* @param title the title of the stream
* @return this builder for chaining
*/
NoteBuilder setTitle(String title) {
setContentTitle(title);
getContentView().setTextViewText(R.id.notificationSongName, title);
getBigContentView().setTextViewText(R.id.notificationSongName, title);
setTicker(String.format(getBaseContext().getString(
R.string.background_player_time_text), title));
return this;
}
/**
* Set the artist of the stream
* @param artist the artist of the stream
* @return this builder for chaining
*/
NoteBuilder setArtist(String artist) {
setSubText(artist);
getContentView().setTextViewText(R.id.notificationArtist, artist);
getBigContentView().setTextViewText(R.id.notificationArtist, artist);
return this;
}
@Override
public android.support.v4.app.NotificationCompat.Builder setProgress(int max, int progress, boolean indeterminate) {
super.setProgress(max, progress, indeterminate);
getBigContentView().setProgressBar(R.id.playbackProgress, max, progress, indeterminate);
return this;
}
/**
* Set the isPlaying state
* @param isPlaying the is playing state
*/
public void setIsPlaying(boolean isPlaying) {
RemoteViews views = getContentView(), bigViews = getBigContentView();
int imageSrc;
if(isPlaying) {
imageSrc = R.drawable.ic_pause_white;
} else {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
}
views.setImageViewResource(R.id.notificationPlayPause, imageSrc);
bigViews.setImageViewResource(R.id.notificationPlayPause, imageSrc);
}
switch (basePlayerImpl.getCurrentRepeatMode()) {
case REPEAT_DISABLED:
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
break;
case REPEAT_ONE:
remoteViews.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
break;
case REPEAT_ALL:
// Waiting :)
break;
} }
} }
/** /**
* Represents the state of the player. * Updates the notification, and the play/pause button in it.
* Used for changes on the remoteView
*
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/ */
public static class PlaybackState implements Parcelable { private void updateNotification(int drawableId) {
if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
private static final int INDEX_IS_PLAYING = 0; if (notBuilder == null) return;
private static final int INDEX_IS_PREPARED= 1; if (drawableId != -1) {
private static final int INDEX_HAS_ERROR = 2; if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
private final int duration; if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
private final int played;
private final boolean[] booleanValues = new boolean[3];
static final PlaybackState UNPREPARED = new PlaybackState(false, false, false);
static final PlaybackState FAILED = new PlaybackState(false, false, true);
PlaybackState(Parcel in) {
duration = in.readInt();
played = in.readInt();
in.readBooleanArray(booleanValues);
} }
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
PlaybackState(int duration, int played, boolean isPlaying) { private void setControlsOpacity(@IntRange(from = 0, to = 255) int opacity) {
this.played = played; if (notRemoteView != null) notRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
this.duration = duration; if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
this.booleanValues[INDEX_IS_PLAYING] = isPlaying; if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
this.booleanValues[INDEX_IS_PREPARED] = true; if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
this.booleanValues[INDEX_HAS_ERROR] = false; if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
} if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
}
private PlaybackState(boolean isPlaying, boolean isPrepared, boolean hasErrors) { /*//////////////////////////////////////////////////////////////////////////
this.played = 0; // Utils
this.duration = 0; //////////////////////////////////////////////////////////////////////////*/
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = isPrepared;
this.booleanValues[INDEX_HAS_ERROR] = hasErrors;
}
int getDuration() { private void lockWifiAndCpu() {
return duration; if (DEBUG) Log.d(TAG, "lockWifiAndCpu() called");
} if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return;
int getPlayedTime() { wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
return played; wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
}
boolean isPlaying() { if (wakeLock != null) wakeLock.acquire();
return booleanValues[INDEX_IS_PLAYING]; if (wifiLock != null) wifiLock.acquire();
} }
boolean isPrepared() { private void releaseWifiAndCpu() {
return booleanValues[INDEX_IS_PREPARED]; if (DEBUG) Log.d(TAG, "releaseWifiAndCpu() called");
} if (wakeLock != null && wakeLock.isHeld()) wakeLock.release();
if (wifiLock != null && wifiLock.isHeld()) wifiLock.release();
boolean hasErrors() { wakeLock = null;
return booleanValues[INDEX_HAS_ERROR]; wifiLock = null;
} }
//////////////////////////////////////////////////////////////////////////
@Override private class BasePlayerImpl extends BasePlayer {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(duration); BasePlayerImpl(Context context) {
dest.writeInt(played); super(context);
dest.writeBooleanArray(booleanValues);
} }
@Override @Override
public int describeContents() { public void handleIntent(Intent intent) {
return 0; super.handleIntent(intent);
Serializable serializable = intent.getSerializableExtra(BackgroundPlayer.AUDIO_STREAM);
if (serializable instanceof AudioStream) audioStream = (AudioStream) serializable;
playUrl(audioStream.url, MediaFormat.getSuffixById(audioStream.format), true);
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
} }
public static final Creator<PlaybackState> CREATOR = new Creator<PlaybackState>() { @Override
@Override public void initThumbnail() {
public PlaybackState createFromParcel(Parcel in) { if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
return new PlaybackState(in); if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
updateNotification(-1);
super.initThumbnail();
}
@Override
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
if (thumbnail != null) {
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
updateNotification(-1);
} }
@Override
public PlaybackState[] newArray(int size) {
return new PlaybackState[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlaybackState that = (PlaybackState) o;
if (duration != that.duration) return false;
if (played != that.played) return false;
return Arrays.equals(booleanValues, that.booleanValues);
} }
@Override @Override
public int hashCode() { public void playUrl(String url, String format, boolean autoPlay) {
if(this == UNPREPARED) return 1; super.playUrl(url, format, autoPlay);
if(this == FAILED) return 2;
int result = duration; notBuilder = createNotification();
result = 31 * result + played; startForeground(NOTIFICATION_ID, notBuilder.build());
result = 31 * result + Arrays.hashCode(booleanValues); }
return result + 2;
@Override
public void onPrepared(boolean playWhenReady) {
super.onPrepared(playWhenReady);
if (simpleExoPlayer.getDuration() < 15000) {
PROGRESS_LOOP_INTERVAL = 1000;
FAST_FORWARD_REWIND_AMOUNT = 2000;
} else if (simpleExoPlayer.getDuration() > 60 * 60 * 1000) {
PROGRESS_LOOP_INTERVAL = 2000;
FAST_FORWARD_REWIND_AMOUNT = 60000;
} else {
PROGRESS_LOOP_INTERVAL = 2000;
FAST_FORWARD_REWIND_AMOUNT = 10000;
}
basePlayerImpl.getPlayer().setVolume(1f);
}
@Override
public void onRepeatClicked() {
super.onRepeatClicked();
int opacity = 255;
switch (currentRepeatMode) {
case REPEAT_DISABLED:
opacity = 77;
break;
case REPEAT_ONE:
opacity = 255;
break;
case REPEAT_ALL:
// Waiting :)
break;
}
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, opacity);
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, opacity);
updateNotification(-1);
}
@Override
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
updateNotification(-1);
}
@Override
public void onFastRewind() {
super.onFastRewind();
triggerProgressUpdate();
}
@Override
public void onFastForward() {
super.onFastForward();
triggerProgressUpdate();
}
@Override
public void onLoadingChanged(boolean isLoading) {
// Disable default behavior
}
@Override
public void destroy() {
super.destroy();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
@Override
public void onError(Exception exception) {
exception.printStackTrace();
stopSelf();
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void setupBroadcastReceiver(IntentFilter intentFilter) {
super.setupBroadcastReceiver(intentFilter);
intentFilter.addAction(ACTION_CLOSE);
intentFilter.addAction(ACTION_PLAY_PAUSE);
intentFilter.addAction(ACTION_OPEN_DETAIL);
intentFilter.addAction(ACTION_REPEAT);
intentFilter.addAction(ACTION_FAST_FORWARD);
intentFilter.addAction(ACTION_FAST_REWIND);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
}
@Override
public void onBroadcastReceived(Intent intent) {
super.onBroadcastReceived(intent);
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
switch (intent.getAction()) {
case ACTION_CLOSE:
onClose();
break;
case ACTION_PLAY_PAUSE:
onVideoPlayPause();
break;
case ACTION_OPEN_DETAIL:
onOpenDetail(BackgroundPlayer.this, basePlayerImpl.getVideoUrl(), basePlayerImpl.getVideoTitle());
break;
case ACTION_REPEAT:
onRepeatClicked();
break;
case ACTION_FAST_REWIND:
onFastRewind();
break;
case ACTION_FAST_FORWARD:
onFastForward();
break;
case Intent.ACTION_SCREEN_ON:
onScreenOnOff(true);
break;
case Intent.ACTION_SCREEN_OFF:
onScreenOnOff(false);
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// States
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onLoading() {
super.onLoading();
setControlsOpacity(77);
updateNotification(-1);
}
@Override
public void onPlaying() {
super.onPlaying();
setControlsOpacity(255);
updateNotification(R.drawable.ic_pause_white);
lockWifiAndCpu();
}
@Override
public void onPaused() {
super.onPaused();
updateNotification(R.drawable.ic_play_arrow_white);
if (isProgressLoopRunning.get()) stopProgressLoop();
releaseWifiAndCpu();
}
@Override
public void onCompleted() {
super.onCompleted();
setControlsOpacity(255);
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
updateNotification(R.drawable.ic_replay_white);
releaseWifiAndCpu();
} }
} }
} }

View File

@ -0,0 +1,717 @@
package org.schabi.newpipe.player;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import java.io.File;
import java.util.Formatter;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Base for the players, joining the common properties
*
* @author mauriciocolli
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManager.OnAudioFocusChangeListener {
public static final boolean DEBUG = false;
public static final String TAG = "BasePlayer";
protected Context context;
protected SharedPreferences sharedPreferences;
protected AudioManager audioManager;
protected BroadcastReceiver broadcastReceiver;
protected IntentFilter intentFilter;
/*//////////////////////////////////////////////////////////////////////////
// Intent
//////////////////////////////////////////////////////////////////////////*/
public static final String VIDEO_URL = "video_url";
public static final String VIDEO_TITLE = "video_title";
public static final String VIDEO_THUMBNAIL_URL = "video_thumbnail_url";
public static final String START_POSITION = "start_position";
public static final String CHANNEL_NAME = "channel_name";
protected Bitmap videoThumbnail = null;
protected String videoUrl = "";
protected String videoTitle = "";
protected String videoThumbnailUrl = "";
protected int videoStartPos = -1;
protected String channelName = "";
/*//////////////////////////////////////////////////////////////////////////
// Player
//////////////////////////////////////////////////////////////////////////*/
public int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
public static final String CACHE_FOLDER_NAME = "exoplayer";
protected SimpleExoPlayer simpleExoPlayer;
protected boolean isPrepared = false;
protected MediaSource mediaSource;
protected CacheDataSourceFactory cacheDataSourceFactory;
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
protected int PROGRESS_LOOP_INTERVAL = 100;
protected AtomicBoolean isProgressLoopRunning = new AtomicBoolean();
protected Handler progressLoop;
protected Runnable progressUpdate;
//////////////////////////////////////////////////////////////////////////*/
public BasePlayer(Context context) {
this.context = context;
this.progressLoop = new Handler();
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
this.broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
onBroadcastReceived(intent);
}
};
this.intentFilter = new IntentFilter();
setupBroadcastReceiver(intentFilter);
context.registerReceiver(broadcastReceiver, intentFilter);
}
public void setup() {
if (simpleExoPlayer == null) initPlayer();
initListeners();
}
private void initExoPlayerCache() {
if (cacheDataSourceFactory == null) {
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getPackageName()), bandwidthMeter);
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (!cacheDir.exists()) {
//noinspection ResultOfMethodCallIgnored
cacheDir.mkdir();
}
if (DEBUG) Log.d(TAG, "initExoPlayerCache: cacheDir = " + cacheDir.getAbsolutePath());
SimpleCache simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(64 * 1024 * 1024L));
cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE, 512 * 1024);
}
}
public void initPlayer() {
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
initExoPlayerCache();
AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
DefaultLoadControl loadControl = new DefaultLoadControl();
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, defaultTrackSelector, loadControl);
simpleExoPlayer.addListener(this);
}
public void initListeners() {
progressUpdate = new Runnable() {
@Override
public void run() {
//if(DEBUG) Log.d(TAG, "progressUpdate run() called");
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, PROGRESS_LOOP_INTERVAL);
}
};
}
public void handleIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return;
videoUrl = intent.getStringExtra(VIDEO_URL);
videoTitle = intent.getStringExtra(VIDEO_TITLE);
videoThumbnailUrl = intent.getStringExtra(VIDEO_THUMBNAIL_URL);
videoStartPos = intent.getIntExtra(START_POSITION, -1);
channelName = intent.getStringExtra(CHANNEL_NAME);
initThumbnail();
//play(getSelectedVideoStream(), true);
}
public void initThumbnail() {
if (DEBUG) Log.d(TAG, "initThumbnail() called");
videoThumbnail = null;
if (videoThumbnailUrl == null || videoThumbnailUrl.isEmpty()) return;
ImageLoader.getInstance().resume();
ImageLoader.getInstance().loadImage(videoThumbnailUrl, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (DEBUG) Log.d(TAG, "onLoadingComplete() called with: imageUri = [" + imageUri + "], view = [" + view + "], loadedImage = [" + loadedImage + "]");
videoThumbnail = loadedImage;
onThumbnailReceived(loadedImage);
}
});
}
public void playUrl(String url, String format, boolean autoPlay) {
if (DEBUG) {
Log.d(TAG, "play() called with: url = [" + url + "], autoPlay = [" + autoPlay + "]");
}
if (url == null || simpleExoPlayer == null) {
RuntimeException runtimeException = new RuntimeException((url == null ? "Url " : "Player ") + " null");
onError(runtimeException);
throw runtimeException;
}
changeState(STATE_LOADING);
isPrepared = false;
mediaSource = buildMediaSource(url, format);
if (simpleExoPlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) simpleExoPlayer.stop();
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.setPlayWhenReady(autoPlay);
}
public void destroyPlayer() {
if (DEBUG) Log.d(TAG, "destroyPlayer() called");
if (simpleExoPlayer != null) {
simpleExoPlayer.stop();
simpleExoPlayer.release();
}
if (progressLoop != null && isProgressLoopRunning.get()) stopProgressLoop();
}
public void destroy() {
if (DEBUG) Log.d(TAG, "destroy() called");
destroyPlayer();
unregisterBroadcastReceiver();
videoThumbnail = null;
simpleExoPlayer = null;
}
public MediaSource buildMediaSource(String url, String overrideExtension) {
if (DEBUG) {
Log.d(TAG, "buildMediaSource() called with: url = [" + url + "], overrideExtension = [" + overrideExtension + "]");
}
Uri uri = Uri.parse(url);
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
MediaSource mediaSource;
switch (type) {
case C.TYPE_SS:
mediaSource = new SsMediaSource(uri, cacheDataSourceFactory, new DefaultSsChunkSource.Factory(cacheDataSourceFactory), null, null);
break;
case C.TYPE_DASH:
mediaSource = new DashMediaSource(uri, cacheDataSourceFactory, new DefaultDashChunkSource.Factory(cacheDataSourceFactory), null, null);
break;
case C.TYPE_HLS:
mediaSource = new HlsMediaSource(uri, cacheDataSourceFactory, null, null);
break;
case C.TYPE_OTHER:
mediaSource = new ExtractorMediaSource(uri, cacheDataSourceFactory, extractorsFactory, null, null);
break;
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
}
return mediaSource;
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
/**
* Add your action in the intentFilter
*
* @param intentFilter intent filter that will be used for register the receiver
*/
protected void setupBroadcastReceiver(IntentFilter intentFilter) {
intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
}
public void onBroadcastReceived(Intent intent) {
switch (intent.getAction()) {
case AudioManager.ACTION_AUDIO_BECOMING_NOISY:
if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
break;
}
}
public void unregisterBroadcastReceiver() {
if (broadcastReceiver != null && context != null) {
context.unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
}
/*//////////////////////////////////////////////////////////////////////////
// AudioFocus
//////////////////////////////////////////////////////////////////////////*/
private static final int DUCK_DURATION = 1500;
private static final float DUCK_AUDIO_TO = .2f;
@Override
public void onAudioFocusChange(int focusChange) {
if (DEBUG) Log.d(TAG, "onAudioFocusChange() called with: focusChange = [" + focusChange + "]");
if (simpleExoPlayer == null) return;
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
onAudioFocusGain();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
onAudioFocusLossCanDuck();
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
onAudioFocusLoss();
break;
}
}
protected void onAudioFocusGain() {
if (DEBUG) Log.d(TAG, "onAudioFocusGain() called");
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(DUCK_AUDIO_TO);
animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION);
simpleExoPlayer.setPlayWhenReady(true);
}
protected void onAudioFocusLoss() {
if (DEBUG) Log.d(TAG, "onAudioFocusLoss() called");
simpleExoPlayer.setPlayWhenReady(false);
}
protected void onAudioFocusLossCanDuck() {
if (DEBUG) Log.d(TAG, "onAudioFocusLossCanDuck() called");
// Set the volume to 1/10 on ducking
animateAudio(simpleExoPlayer.getVolume(), DUCK_AUDIO_TO, DUCK_DURATION);
}
/*//////////////////////////////////////////////////////////////////////////
// States Implementation
//////////////////////////////////////////////////////////////////////////*/
public static final int STATE_LOADING = 123;
public static final int STATE_PLAYING = 124;
public static final int STATE_BUFFERING = 125;
public static final int STATE_PAUSED = 126;
public static final int STATE_PAUSED_SEEK = 127;
public static final int STATE_COMPLETED = 128;
protected int currentState = -1;
public void changeState(int state) {
if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
currentState = state;
switch (state) {
case STATE_LOADING:
onLoading();
break;
case STATE_PLAYING:
onPlaying();
break;
case STATE_BUFFERING:
onBuffering();
break;
case STATE_PAUSED:
onPaused();
break;
case STATE_PAUSED_SEEK:
onPausedSeek();
break;
case STATE_COMPLETED:
onCompleted();
break;
}
}
public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called");
if (!isProgressLoopRunning.get()) startProgressLoop();
}
public void onPlaying() {
if (DEBUG) Log.d(TAG, "onPlaying() called");
if (!isProgressLoopRunning.get()) startProgressLoop();
}
public void onBuffering() {
}
public void onPaused() {
if (isProgressLoopRunning.get()) stopProgressLoop();
}
public void onPausedSeek() {
}
public void onCompleted() {
if (DEBUG) Log.d(TAG, "onCompleted() called");
if (isProgressLoopRunning.get()) stopProgressLoop();
if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
changeState(STATE_LOADING);
simpleExoPlayer.seekTo(0);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Repeat
//////////////////////////////////////////////////////////////////////////*/
protected RepeatMode currentRepeatMode = RepeatMode.REPEAT_DISABLED;
public enum RepeatMode {
REPEAT_DISABLED,
REPEAT_ONE,
REPEAT_ALL
}
public void onRepeatClicked() {
if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
// TODO: implement repeat all when playlist is implemented
// Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
setCurrentRepeatMode(getCurrentRepeatMode() == RepeatMode.REPEAT_DISABLED ?
RepeatMode.REPEAT_ONE :
RepeatMode.REPEAT_DISABLED);
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getCurrentRepeatMode().name());
}
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
@Override
public void onLoadingChanged(boolean isLoading) {
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop();
else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop();
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
if (getCurrentState() == STATE_PAUSED_SEEK) {
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek");
return;
}
switch (playbackState) {
case ExoPlayer.STATE_IDLE: // 1
isPrepared = false;
break;
case ExoPlayer.STATE_BUFFERING: // 2
if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING);
break;
case ExoPlayer.STATE_READY: //3
if (!isPrepared) {
isPrepared = true;
onPrepared(playWhenReady);
break;
}
if (currentState == STATE_PAUSED_SEEK) break;
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
break;
case ExoPlayer.STATE_ENDED: // 4
changeState(STATE_COMPLETED);
isPrepared = false;
break;
}
}
@Override
public void onPlayerError(ExoPlaybackException error) {
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
onError(error);
}
@Override
public void onPositionDiscontinuity() {
}
/*//////////////////////////////////////////////////////////////////////////
// General Player
//////////////////////////////////////////////////////////////////////////*/
public abstract void onError(Exception exception);
public void onPrepared(boolean playWhenReady) {
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
if (playWhenReady) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
}
public abstract void onUpdateProgress(int currentProgress, int duration, int bufferPercent);
public void onVideoPlayPause() {
if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
if (currentState == STATE_COMPLETED) {
onVideoPlayPauseRepeat();
return;
}
if (!isPlaying()) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
else audioManager.abandonAudioFocus(null);
simpleExoPlayer.setPlayWhenReady(!isPlaying());
}
public void onVideoPlayPauseRepeat() {
if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
changeState(STATE_LOADING);
setVideoStartPos(0);
simpleExoPlayer.seekTo(0);
simpleExoPlayer.setPlayWhenReady(true);
audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
public void onFastRewind() {
if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
}
public void onFastForward() {
if (DEBUG) Log.d(TAG, "onFastForward() called");
seekBy(FAST_FORWARD_REWIND_AMOUNT);
}
public void onThumbnailReceived(Bitmap thumbnail) {
if (DEBUG) Log.d(TAG, "onThumbnailReceived() called with: thumbnail = [" + thumbnail + "]");
}
public void seekBy(int milliSeconds) {
if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
if (simpleExoPlayer == null || (isCompleted() && milliSeconds > 0) || ((milliSeconds < 0 && simpleExoPlayer.getCurrentPosition() == 0))) return;
int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds);
if (progress < 0) progress = 0;
simpleExoPlayer.seekTo(progress);
}
public boolean isPlaying() {
return simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY && simpleExoPlayer.getPlayWhenReady();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private final StringBuilder stringBuilder = new StringBuilder();
private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
public String getTimeString(int milliSeconds) {
long seconds = (milliSeconds % 60000L) / 1000L;
long minutes = (milliSeconds % 3600000L) / 60000L;
long hours = (milliSeconds % 86400000L) / 3600000L;
long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
stringBuilder.setLength(0);
return days > 0 ? formatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
: hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
: formatter.format("%02d:%02d", minutes, seconds).toString();
}
protected void startProgressLoop() {
progressLoop.removeCallbacksAndMessages(null);
isProgressLoopRunning.set(true);
progressLoop.post(progressUpdate);
}
protected void stopProgressLoop() {
isProgressLoopRunning.set(false);
progressLoop.removeCallbacksAndMessages(null);
}
protected void tryDeleteCacheFiles(Context context) {
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (cacheDir.exists()) {
try {
if (cacheDir.isDirectory()) {
for (File file : cacheDir.listFiles()) {
try {
if (DEBUG) Log.d(TAG, "tryDeleteCacheFiles: " + file.getAbsolutePath() + " deleted = " + file.delete());
} catch (Exception ignored) {
}
}
}
} catch (Exception ignored) {
}
}
}
public void triggerProgressUpdate() {
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
}
public void animateAudio(final float from, final float to, int duration) {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setFloatValues(from, to);
valueAnimator.setDuration(duration);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(from);
}
@Override
public void onAnimationCancel(Animator animation) {
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(to);
}
@Override
public void onAnimationEnd(Animator animation) {
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(to);
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(((float) animation.getAnimatedValue()));
}
});
valueAnimator.start();
}
/*//////////////////////////////////////////////////////////////////////////
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/
public SimpleExoPlayer getPlayer() {
return simpleExoPlayer;
}
public SharedPreferences getSharedPreferences() {
return sharedPreferences;
}
public RepeatMode getCurrentRepeatMode() {
return currentRepeatMode;
}
public void setCurrentRepeatMode(RepeatMode mode) {
currentRepeatMode = mode;
}
public int getCurrentState() {
return currentState;
}
public String getVideoUrl() {
return videoUrl;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public int getVideoStartPos() {
return videoStartPos;
}
public void setVideoStartPos(int videoStartPos) {
this.videoStartPos = videoStartPos;
}
public String getVideoTitle() {
return videoTitle;
}
public void setVideoTitle(String videoTitle) {
this.videoTitle = videoTitle;
}
public String getChannelName() {
return channelName;
}
public void setChannelName(String channelName) {
this.channelName = channelName;
}
public boolean isCompleted() {
return simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_ENDED;
}
public boolean isPrepared() {
return isPrepared;
}
public void setPrepared(boolean prepared) {
isPrepared = prepared;
}
public Bitmap getVideoThumbnail() {
return videoThumbnail;
}
public void setVideoThumbnail(Bitmap videoThumbnail) {
this.videoThumbnail = videoThumbnail;
}
public String getVideoThumbnailUrl() {
return videoThumbnailUrl;
}
public void setVideoThumbnailUrl(String videoThumbnailUrl) {
this.videoThumbnailUrl = videoThumbnailUrl;
}
}

View File

@ -1,10 +1,8 @@
package org.schabi.newpipe.player; package org.schabi.newpipe.player;
import android.app.Activity; import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.graphics.Color; import android.graphics.Color;
import android.media.AudioManager; import android.media.AudioManager;
@ -23,22 +21,20 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
/** /**
* Activity Player implementing AbstractPlayer * Activity Player implementing VideoPlayer
* *
* @author mauriciocolli * @author mauriciocolli
*/ */
public class ExoPlayerActivity extends Activity { public class MainVideoPlayer extends Activity {
private static final String TAG = ".ExoPlayerActivity"; private static final String TAG = ".MainVideoPlayer";
private static final boolean DEBUG = AbstractPlayer.DEBUG; private static final boolean DEBUG = BasePlayer.DEBUG;
private AudioManager audioManager; private AudioManager audioManager;
private BroadcastReceiver broadcastReceiver;
private GestureDetector gestureDetector; private GestureDetector gestureDetector;
private final Runnable hideUiRunnable = new Runnable() { private final Runnable hideUiRunnable = new Runnable() {
@ -49,7 +45,7 @@ public class ExoPlayerActivity extends Activity {
}; };
private boolean activityPaused; private boolean activityPaused;
private AbstractPlayerImpl playerImpl; private VideoPlayerImpl playerImpl;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Activity LifeCycle // Activity LifeCycle
@ -72,9 +68,8 @@ public class ExoPlayerActivity extends Activity {
showSystemUi(); showSystemUi();
setContentView(R.layout.activity_exo_player); setContentView(R.layout.activity_exo_player);
playerImpl = new AbstractPlayerImpl(); playerImpl = new VideoPlayerImpl();
playerImpl.setup(findViewById(android.R.id.content)); playerImpl.setup(findViewById(android.R.id.content));
initReceiver();
playerImpl.handleIntent(getIntent()); playerImpl.handleIntent(getIntent());
} }
@ -97,8 +92,10 @@ public class ExoPlayerActivity extends Activity {
super.onStop(); super.onStop();
if (DEBUG) Log.d(TAG, "onStop() called"); if (DEBUG) Log.d(TAG, "onStop() called");
activityPaused = true; activityPaused = true;
playerImpl.destroy(); if (playerImpl.getPlayer() != null) {
playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition()); playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
playerImpl.destroyPlayer();
}
} }
@Override @Override
@ -106,9 +103,9 @@ public class ExoPlayerActivity extends Activity {
super.onResume(); super.onResume();
if (DEBUG) Log.d(TAG, "onResume() called"); if (DEBUG) Log.d(TAG, "onResume() called");
if (activityPaused) { if (activityPaused) {
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
playerImpl.initPlayer(); playerImpl.initPlayer();
playerImpl.playVideo(playerImpl.getSelectedVideoStream(), false); playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
playerImpl.play(false);
activityPaused = false; activityPaused = false;
} }
} }
@ -118,29 +115,6 @@ public class ExoPlayerActivity extends Activity {
super.onDestroy(); super.onDestroy();
if (DEBUG) Log.d(TAG, "onDestroy() called"); if (DEBUG) Log.d(TAG, "onDestroy() called");
if (playerImpl != null) playerImpl.destroy(); if (playerImpl != null) playerImpl.destroy();
if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
private void initReceiver() {
if (DEBUG) Log.d(TAG, "initReceiver() called");
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
switch (intent.getAction()) {
case AbstractPlayer.ACTION_UPDATE_THUMB:
playerImpl.onUpdateThumbnail(intent);
break;
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
registerReceiver(broadcastReceiver, intentFilter);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -182,7 +156,7 @@ public class ExoPlayerActivity extends Activity {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@SuppressWarnings({"unused", "WeakerAccess"}) @SuppressWarnings({"unused", "WeakerAccess"})
private class AbstractPlayerImpl extends AbstractPlayer { private class VideoPlayerImpl extends VideoPlayer {
private TextView titleTextView; private TextView titleTextView;
private TextView channelTextView; private TextView channelTextView;
private TextView volumeTextView; private TextView volumeTextView;
@ -192,8 +166,8 @@ public class ExoPlayerActivity extends Activity {
private ImageButton screenRotationButton; private ImageButton screenRotationButton;
private ImageButton playPauseButton; private ImageButton playPauseButton;
AbstractPlayerImpl() { VideoPlayerImpl() {
super("AbstractPlayerImpl" + ExoPlayerActivity.TAG, ExoPlayerActivity.this); super("VideoPlayerImpl" + MainVideoPlayer.TAG, MainVideoPlayer.this);
} }
@Override @Override
@ -238,8 +212,8 @@ public class ExoPlayerActivity extends Activity {
} }
@Override @Override
public void playVideo(VideoStream videoStream, boolean autoPlay) { public void playUrl(String url, String format, boolean autoPlay) {
super.playVideo(videoStream, autoPlay); super.playUrl(url, format, autoPlay);
playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white); playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white);
} }
@ -249,16 +223,16 @@ public class ExoPlayerActivity extends Activity {
if (playerImpl.getPlayer() == null) return; if (playerImpl.getPlayer() == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(ExoPlayerActivity.this)) { && !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) {
Toast.makeText(ExoPlayerActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show(); Toast.makeText(MainVideoPlayer.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return; return;
} }
if (playerImpl != null) playerImpl.destroy(); context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, playerImpl));
context.startService(NavigationHelper.getOpenPlayerIntent(context, PopupVideoPlayer.class, playerImpl)); if (playerImpl != null) playerImpl.destroyPlayer();
((View) getControlAnimationView().getParent()).setVisibility(View.GONE); ((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
ExoPlayerActivity.this.finish(); MainVideoPlayer.this.finish();
} }
@Override @Override
@ -307,28 +281,6 @@ public class ExoPlayerActivity extends Activity {
toggleOrientation(); toggleOrientation();
} }
@Override
public void onVideoPlayPause() {
super.onVideoPlayPause();
if (getPlayer().getPlayWhenReady()) {
animateView(playPauseButton, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, true, 200, 0);
}
});
} else {
animateView(playPauseButton, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animateView(playPauseButton, true, 200, 0);
}
});
}
}
@Override @Override
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar); super.onStopTrackingTouch(seekBar);
@ -344,7 +296,8 @@ public class ExoPlayerActivity extends Activity {
} }
@Override @Override
public void onError() { public void onError(Exception exception) {
exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -366,10 +319,37 @@ public class ExoPlayerActivity extends Activity {
animateView(playPauseButton, false, 100, 0); animateView(playPauseButton, false, 100, 0);
} }
@Override
public void onPlaying() {
super.onPlaying();
//playPauseButton.setImageResource(R.drawable.ic_pause_white);
//animateView(playPauseButton, true, 500, 0);
animateView(playPauseButton, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, true, 200, 0);
}
});
showSystemUi();
}
@Override @Override
public void onPaused() { public void onPaused() {
super.onPaused(); super.onPaused();
animateView(playPauseButton, true, 100, 0); //playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
//animateView(playPauseButton, true, 100, 0);
animateView(playPauseButton, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animateView(playPauseButton, true, 200, 0);
}
});
showSystemUi(); showSystemUi();
} }
@ -379,12 +359,6 @@ public class ExoPlayerActivity extends Activity {
animateView(playPauseButton, false, 100, 0); animateView(playPauseButton, false, 100, 0);
} }
@Override
public void onPlaying() {
super.onPlaying();
animateView(playPauseButton, true, 500, 0);
showSystemUi();
}
@Override @Override
public void onCompleted() { public void onCompleted() {
@ -467,14 +441,14 @@ public class ExoPlayerActivity extends Activity {
@Override @Override
public boolean onSingleTapConfirmed(MotionEvent e) { public boolean onSingleTapConfirmed(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
if (playerImpl.getCurrentState() != StateInterface.STATE_PLAYING) return true; if (playerImpl.getCurrentState() != BasePlayer.STATE_PLAYING) return true;
if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true); if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true);
else { else {
playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() { playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
@Override @Override
public void run() { public void run() {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME, true); playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
} }
}); });
showSystemUi(); showSystemUi();
@ -482,25 +456,25 @@ public class ExoPlayerActivity extends Activity {
return true; return true;
} }
private final float stepsBrightness = 21, stepBrightness = (1f / stepsBrightness), minBrightness = .01f; private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
private float currentBrightness = .5f; private float currentBrightness = .5f;
private int currentVolume, maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); private int currentVolume, maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0;
private final String brightnessUnicode = new String(Character.toChars(0x2600)); private final String brightnessUnicode = new String(Character.toChars(0x2600));
// private final String volumeUnicode = new String(Character.toChars(0x1F50A));
private final String volumeUnicode = new String(Character.toChars(0x1F508)); private final String volumeUnicode = new String(Character.toChars(0x1F508));
private final int MOVEMENT_THRESHOLD = 40; private final int MOVEMENT_THRESHOLD = 40;
private final int eventsThreshold = 3; private final int eventsThreshold = 8;
private boolean triggered = false; private boolean triggered = false;
private int eventsNum; private int eventsNum;
// TODO: Improve video gesture controls
@Override @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//noinspection PointlessBooleanExpression //noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "ExoPlayerActivity.onScroll = " + if (DEBUG && true) Log.d(TAG, "MainVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" + ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" + ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]"); ", distanceXy = [" + distanceX + ", " + distanceY + "]");
@ -510,16 +484,17 @@ public class ExoPlayerActivity extends Activity {
return false; return false;
} }
if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == StateInterface.STATE_COMPLETED) return false; if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false;
isMoving = true; isMoving = true;
// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top // boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
boolean up = distanceY > 0; boolean up = distanceY > 0;
if (e1.getX() > playerImpl.getRootView().getWidth() / 2) { if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + (up ? 1 : -1); double floor = Math.floor(up ? stepVolume : -stepVolume);
currentVolume = (int) (audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + floor);
if (currentVolume >= maxVolume) currentVolume = maxVolume; if (currentVolume >= maxVolume) currentVolume = maxVolume;
if (currentVolume <= 0) currentVolume = 0; if (currentVolume <= minVolume) currentVolume = (int) minVolume;
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0); audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
@ -555,8 +530,8 @@ public class ExoPlayerActivity extends Activity {
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200); if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200); if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) { if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME); playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
} }
} }

View File

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
@ -31,7 +30,6 @@ import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -39,7 +37,6 @@ import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamInfo; import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -47,13 +44,13 @@ import org.schabi.newpipe.util.Utils;
import org.schabi.newpipe.workers.StreamExtractorWorker; import org.schabi.newpipe.workers.StreamExtractorWorker;
/** /**
* Service Popup Player implementing AbstractPlayer * Service Popup Player implementing VideoPlayer
* *
* @author mauriciocolli * @author mauriciocolli
*/ */
public class PopupVideoPlayer extends Service { public class PopupVideoPlayer extends Service {
private static final String TAG = ".PopupVideoPlayer"; private static final String TAG = ".PopupVideoPlayer";
private static final boolean DEBUG = AbstractPlayer.DEBUG; private static final boolean DEBUG = BasePlayer.DEBUG;
private static final int NOTIFICATION_ID = 40028922; private static final int NOTIFICATION_ID = 40028922;
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE"; public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
@ -61,8 +58,6 @@ public class PopupVideoPlayer extends Service {
public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL"; public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT"; public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
private BroadcastReceiver broadcastReceiver;
private WindowManager windowManager; private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams; private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector; private GestureDetector gestureDetector;
@ -81,7 +76,7 @@ public class PopupVideoPlayer extends Service {
private ImageLoader imageLoader = ImageLoader.getInstance(); private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build(); private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
private AbstractPlayerImpl playerImpl; private VideoPlayerImpl playerImpl;
private StreamExtractorWorker currentExtractorWorker; private StreamExtractorWorker currentExtractorWorker;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -92,9 +87,8 @@ public class PopupVideoPlayer extends Service {
public void onCreate() { public void onCreate() {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
initReceiver();
playerImpl = new AbstractPlayerImpl(); playerImpl = new VideoPlayerImpl();
ThemeHelper.setTheme(this, false); ThemeHelper.setTheme(this, false);
} }
@ -132,7 +126,6 @@ public class PopupVideoPlayer extends Service {
} }
if (imageLoader != null) imageLoader.clearMemoryCache(); if (imageLoader != null) imageLoader.clearMemoryCache();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
if (currentExtractorWorker != null) { if (currentExtractorWorker != null) {
currentExtractorWorker.cancel(); currentExtractorWorker.cancel();
currentExtractorWorker = null; currentExtractorWorker = null;
@ -148,39 +141,6 @@ public class PopupVideoPlayer extends Service {
// Init // Init
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void initReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
switch (intent.getAction()) {
case ACTION_CLOSE:
onVideoClose();
break;
case ACTION_PLAY_PAUSE:
playerImpl.onVideoPlayPause();
break;
case ACTION_OPEN_DETAIL:
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
break;
case ACTION_REPEAT:
playerImpl.onRepeatClicked();
break;
case AbstractPlayer.ACTION_UPDATE_THUMB:
playerImpl.onUpdateThumbnail(intent);
break;
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_CLOSE);
intentFilter.addAction(ACTION_PLAY_PAUSE);
intentFilter.addAction(ACTION_OPEN_DETAIL);
intentFilter.addAction(ACTION_REPEAT);
intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
registerReceiver(broadcastReceiver, intentFilter);
}
@SuppressLint("RtlHardcoded") @SuppressLint("RtlHardcoded")
private void initPopup() { private void initPopup() {
if (DEBUG) Log.d(TAG, "initPopup() called"); if (DEBUG) Log.d(TAG, "initPopup() called");
@ -213,8 +173,9 @@ public class PopupVideoPlayer extends Service {
private NotificationCompat.Builder createNotification() { private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification); notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
if (playerImpl.getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail()); if (playerImpl.getVideoThumbnail() == null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
else notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail); else notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle()); notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getChannelName()); notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getChannelName());
@ -302,21 +263,35 @@ public class PopupVideoPlayer extends Service {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
private class AbstractPlayerImpl extends AbstractPlayer { private class VideoPlayerImpl extends VideoPlayer {
AbstractPlayerImpl() { VideoPlayerImpl() {
super("AbstractPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this); super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
} }
@Override @Override
public void playVideo(VideoStream videoStream, boolean autoPlay) { public void playUrl(String url, String format, boolean autoPlay) {
super.playVideo(videoStream, autoPlay); super.playUrl(url, format, autoPlay);
windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight); windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
windowManager.updateViewLayout(getRootView(), windowLayoutParams); windowManager.updateViewLayout(getRootView(), windowLayoutParams);
notBuilder = createNotification(); notBuilder = createNotification();
startForeground(NOTIFICATION_ID, notBuilder.build()); startForeground(NOTIFICATION_ID, notBuilder.build());
notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); }
@Override
public void destroy() {
super.destroy();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
}
@Override
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
if (thumbnail != null) {
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
updateNotification(-1);
}
} }
@Override @Override
@ -324,8 +299,8 @@ public class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
Intent intent; Intent intent;
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) { if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
intent = NavigationHelper.getOpenPlayerIntent(context, ExoPlayerActivity.class, playerImpl); intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, playerImpl);
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(AbstractPlayer.STARTED_FROM_NEWPIPE, false); if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else { } else {
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class) intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
@ -335,9 +310,9 @@ public class PopupVideoPlayer extends Service {
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f)); .putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} }
stopSelf();
if (playerImpl != null) playerImpl.destroy();
context.startActivity(intent); context.startActivity(intent);
if (playerImpl != null) playerImpl.destroyPlayer();
stopSelf();
} }
@Override @Override
@ -360,13 +335,6 @@ public class PopupVideoPlayer extends Service {
updateNotification(-1); updateNotification(-1);
} }
@Override
public void onUpdateThumbnail(Intent intent) {
super.onUpdateThumbnail(intent);
if (getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, getVideoThumbnail());
updateNotification(-1);
}
@Override @Override
public void onDismiss(PopupMenu menu) { public void onDismiss(PopupMenu menu) {
super.onDismiss(menu); super.onDismiss(menu);
@ -374,11 +342,45 @@ public class PopupVideoPlayer extends Service {
} }
@Override @Override
public void onError() { public void onError(Exception exception) {
exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show(); Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
stopSelf(); stopSelf();
} }
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void setupBroadcastReceiver(IntentFilter intentFilter) {
super.setupBroadcastReceiver(intentFilter);
if (DEBUG) Log.d(TAG, "setupBroadcastReceiver() called with: intentFilter = [" + intentFilter + "]");
intentFilter.addAction(ACTION_CLOSE);
intentFilter.addAction(ACTION_PLAY_PAUSE);
intentFilter.addAction(ACTION_OPEN_DETAIL);
intentFilter.addAction(ACTION_REPEAT);
}
@Override
public void onBroadcastReceived(Intent intent) {
super.onBroadcastReceived(intent);
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
switch (intent.getAction()) {
case ACTION_CLOSE:
onVideoClose();
break;
case ACTION_PLAY_PAUSE:
playerImpl.onVideoPlayPause();
break;
case ACTION_OPEN_DETAIL:
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
break;
case ACTION_REPEAT:
playerImpl.onRepeatClicked();
break;
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// States // States
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -483,8 +485,8 @@ public class PopupVideoPlayer extends Service {
private void onScrollEnd() { private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called"); if (DEBUG) Log.d(TAG, "onScrollEnd() called");
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) { if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME); playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
} }
} }
@ -516,6 +518,7 @@ public class PopupVideoPlayer extends Service {
public void onReceive(StreamInfo info) { public void onReceive(StreamInfo info) {
playerImpl.setVideoTitle(info.title); playerImpl.setVideoTitle(info.title);
playerImpl.setVideoUrl(info.webpage_url); playerImpl.setVideoUrl(info.webpage_url);
playerImpl.setVideoThumbnailUrl(info.thumbnail_url);
playerImpl.setChannelName(info.uploader); playerImpl.setChannelName(info.uploader);
playerImpl.setVideoStreamsList(Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false)); playerImpl.setVideoStreamsList(Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false));
@ -537,7 +540,7 @@ public class PopupVideoPlayer extends Service {
mainHandler.post(new Runnable() { mainHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
playerImpl.playVideo(playerImpl.getSelectedVideoStream(), true); playerImpl.play(true);
} }
}); });
@ -551,7 +554,6 @@ public class PopupVideoPlayer extends Service {
playerImpl.setVideoThumbnail(loadedImage); playerImpl.setVideoThumbnail(loadedImage);
if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage); if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
updateNotification(-1); updateNotification(-1);
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = loadedImage;
} }
}); });
} }
@ -619,6 +621,7 @@ public class PopupVideoPlayer extends Service {
@Override @Override
public void onUnrecoverableError(Exception exception) { public void onUnrecoverableError(Exception exception) {
exception.printStackTrace();
stopSelf(); stopSelf();
} }
} }

View File

@ -1,19 +0,0 @@
package org.schabi.newpipe.player;
public interface StateInterface {
int STATE_LOADING = 123;
int STATE_PLAYING = 124;
int STATE_BUFFERING = 125;
int STATE_PAUSED = 126;
int STATE_PAUSED_SEEK = 127;
int STATE_COMPLETED = 128;
void changeState(int state);
void onLoading();
void onPlaying();
void onBuffering();
void onPaused();
void onPausedSeek();
void onCompleted();
}

View File

@ -7,16 +7,12 @@ import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -29,84 +25,42 @@ import android.widget.ProgressBar;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.AudioStream; import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.VideoStream; import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Formatter;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Common properties of the players * Base for <b>video</b> players
* *
* @author mauriciocolli * @author mauriciocolli
*/ */
@SuppressWarnings({"unused", "WeakerAccess"}) @SuppressWarnings({"WeakerAccess", "unused"})
public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener, SimpleExoPlayer.VideoListener { public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.VideoListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
public static final boolean DEBUG = false; public static final boolean DEBUG = BasePlayer.DEBUG;
public final String TAG; public final String TAG;
protected Context context;
private SharedPreferences sharedPreferences;
private static int currentState = -1;
public static final String ACTION_UPDATE_THUMB = "org.schabi.newpipe.player.AbstractPlayer.UPDATE_THUMBNAIL";
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Intent // Intent
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static final String VIDEO_URL = "video_url";
public static final String VIDEO_STREAMS_LIST = "video_streams_list"; public static final String VIDEO_STREAMS_LIST = "video_streams_list";
public static final String VIDEO_ONLY_AUDIO_STREAM = "video_only_audio_stream"; public static final String VIDEO_ONLY_AUDIO_STREAM = "video_only_audio_stream";
public static final String VIDEO_TITLE = "video_title";
public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream"; public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
public static final String START_POSITION = "start_position";
public static final String CHANNEL_NAME = "channel_name";
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
private String videoUrl = "";
private int videoStartPos = -1;
private String videoTitle = "";
private Bitmap videoThumbnail;
private String channelName = "";
private int selectedIndexStream; private int selectedIndexStream;
private ArrayList<VideoStream> videoStreamsList = new ArrayList<>(); private ArrayList<VideoStream> videoStreamsList = new ArrayList<>();
private AudioStream videoOnlyAudioStream; private AudioStream videoOnlyAudioStream;
@ -115,36 +69,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
// Player // Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static final int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
public static final String CACHE_FOLDER_NAME = "exoplayer";
private boolean startedFromNewPipe = true; private boolean startedFromNewPipe = true;
private boolean isPrepared = false;
private boolean wasPlaying = false; private boolean wasPlaying = false;
private SimpleExoPlayer simpleExoPlayer;
@SuppressWarnings("FieldCanBeLocal")
private MediaSource videoSource;
private static CacheDataSourceFactory cacheDataSourceFactory;
private static final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
private static final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
private AtomicBoolean isProgressLoopRunning = new AtomicBoolean();
private Handler progressLoop;
private Runnable progressUpdate;
/*//////////////////////////////////////////////////////////////////////////
// Repeat
//////////////////////////////////////////////////////////////////////////*/
private RepeatMode currentRepeatMode = RepeatMode.REPEAT_DISABLED;
public enum RepeatMode {
REPEAT_DISABLED,
REPEAT_ONE,
REPEAT_ALL
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
@ -181,35 +109,15 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
public AbstractPlayer(String debugTag, Context context) { public VideoPlayer(String debugTag, Context context) {
super(context);
this.TAG = debugTag; this.TAG = debugTag;
this.context = context; this.context = context;
this.progressLoop = new Handler();
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (cacheDataSourceFactory == null) {
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getPackageName()), bandwidthMeter);
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (!cacheDir.exists()) {
//noinspection ResultOfMethodCallIgnored
cacheDir.mkdir();
}
Log.d(TAG, "buildMediaSource: cacheDir = " + cacheDir.getAbsolutePath());
SimpleCache simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(64 * 1024 * 1024L));
cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE, 512 * 1024);
}
} }
public void setup(View rootView) { public void setup(View rootView) {
initViews(rootView); initViews(rootView);
initListeners(); setup();
if (simpleExoPlayer == null) initPlayer();
else {
simpleExoPlayer.addListener(this);
simpleExoPlayer.setVideoListener(this);
simpleExoPlayer.setVideoSurfaceView(surfaceView);
}
} }
public void initViews(View rootView) { public void initViews(View rootView) {
@ -241,36 +149,24 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
} }
@Override
public void initListeners() { public void initListeners() {
progressUpdate = new Runnable() { super.initListeners();
@Override
public void run() {
//if(DEBUG) Log.d(TAG, "progressUpdate run() called");
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, 100);
}
};
playbackSeekBar.setOnSeekBarChangeListener(this); playbackSeekBar.setOnSeekBarChangeListener(this);
fullScreenButton.setOnClickListener(this); fullScreenButton.setOnClickListener(this);
qualityTextView.setOnClickListener(this); qualityTextView.setOnClickListener(this);
} }
@Override
public void initPlayer() { public void initPlayer() {
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]"); super.initPlayer();
AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
DefaultLoadControl loadControl = new DefaultLoadControl();
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, defaultTrackSelector, loadControl);
simpleExoPlayer.addListener(this);
simpleExoPlayer.setVideoListener(this);
simpleExoPlayer.setVideoSurfaceView(surfaceView); simpleExoPlayer.setVideoSurfaceView(surfaceView);
simpleExoPlayer.setVideoListener(this);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void handleIntent(Intent intent) { public void handleIntent(Intent intent) {
super.handleIntent(intent);
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return; if (intent == null) return;
@ -284,79 +180,36 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
Serializable audioStream = intent.getSerializableExtra(VIDEO_ONLY_AUDIO_STREAM); Serializable audioStream = intent.getSerializableExtra(VIDEO_ONLY_AUDIO_STREAM);
if (audioStream != null) videoOnlyAudioStream = (AudioStream) audioStream; if (audioStream != null) videoOnlyAudioStream = (AudioStream) audioStream;
videoUrl = intent.getStringExtra(VIDEO_URL);
videoTitle = intent.getStringExtra(VIDEO_TITLE);
videoStartPos = intent.getIntExtra(START_POSITION, -1);
channelName = intent.getStringExtra(CHANNEL_NAME);
startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true); startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true);
try { play(true);
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
} catch (Exception e) {
e.printStackTrace();
}
playVideo(getSelectedVideoStream(), true);
} }
public void playVideo(VideoStream videoStream, boolean autoPlay) {
if (DEBUG) {
Log.d(TAG, "playVideo() called with: videoStream = [" + videoStream + ", " + videoStream.url + ", isVideoOnly = " + videoStream.isVideoOnly + "], autoPlay = [" + autoPlay + "]");
}
if (videoStream == null || videoStream.url == null || simpleExoPlayer == null) { public void play(boolean autoPlay) {
onError(); playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay);
return; }
}
isPrepared = false; @Override
public void playUrl(String url, String format, boolean autoPlay) {
if (DEBUG) Log.d(TAG, "play() called with: url = [" + url + "], autoPlay = [" + autoPlay + "]");
qualityChanged = false; qualityChanged = false;
if (url == null || simpleExoPlayer == null) {
RuntimeException runtimeException = new RuntimeException((url == null ? "Url " : "Player ") + " null");
onError(runtimeException);
throw runtimeException;
}
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId); qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
buildQualityMenu(qualityPopupMenu); buildQualityMenu(qualityPopupMenu);
videoSource = buildMediaSource(videoStream, MediaFormat.getSuffixById(getSelectedVideoStream().format)); super.playUrl(url, format, autoPlay);
if (simpleExoPlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) simpleExoPlayer.stop();
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
simpleExoPlayer.prepare(videoSource);
simpleExoPlayer.setPlayWhenReady(autoPlay);
changeState(STATE_LOADING);
} }
public void destroy() { @Override
if (DEBUG) Log.d(TAG, "destroy() called"); public MediaSource buildMediaSource(String url, String overrideExtension) {
if (simpleExoPlayer != null) { MediaSource mediaSource = super.buildMediaSource(url, overrideExtension);
simpleExoPlayer.stop(); if (!getSelectedVideoStream().isVideoOnly) return mediaSource;
simpleExoPlayer.release();
}
if (progressLoop != null) stopProgressLoop();
}
private MediaSource buildMediaSource(VideoStream videoStream, String overrideExtension) {
if (DEBUG) {
Log.d(TAG, "buildMediaSource() called with: videoStream = [" + videoStream + ", " + videoStream.url + "isVideoOnly = " + videoStream.isVideoOnly + "], overrideExtension = [" + overrideExtension + "]");
}
Uri uri = Uri.parse(videoStream.url);
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
MediaSource mediaSource;
switch (type) {
case C.TYPE_SS:
mediaSource = new SsMediaSource(uri, cacheDataSourceFactory, new DefaultSsChunkSource.Factory(cacheDataSourceFactory), null, null);
break;
case C.TYPE_DASH:
mediaSource = new DashMediaSource(uri, cacheDataSourceFactory, new DefaultDashChunkSource.Factory(cacheDataSourceFactory), null, null);
break;
case C.TYPE_HLS:
mediaSource = new HlsMediaSource(uri, cacheDataSourceFactory, null, null);
break;
case C.TYPE_OTHER:
mediaSource = new ExtractorMediaSource(uri, cacheDataSourceFactory, extractorsFactory, null, null);
break;
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
}
if (!videoStream.isVideoOnly) return mediaSource;
Uri audioUri = Uri.parse(videoOnlyAudioStream.url); Uri audioUri = Uri.parse(videoOnlyAudioStream.url);
return new MergingMediaSource(mediaSource, new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null)); return new MergingMediaSource(mediaSource, new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null));
@ -370,39 +223,12 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
qualityTextView.setText(getSelectedVideoStream().resolution); qualityTextView.setText(getSelectedVideoStream().resolution);
popupMenu.setOnMenuItemClickListener(this); popupMenu.setOnMenuItemClickListener(this);
popupMenu.setOnDismissListener(this); popupMenu.setOnDismissListener(this);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// States Implementation // States Implementation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override
public void changeState(int state) {
if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
currentState = state;
switch (state) {
case STATE_LOADING:
onLoading();
break;
case STATE_PLAYING:
onPlaying();
break;
case STATE_BUFFERING:
onBuffering();
break;
case STATE_PAUSED:
onPaused();
break;
case STATE_PAUSED_SEEK:
onPausedSeek();
break;
case STATE_COMPLETED:
onCompleted();
break;
}
}
@Override @Override
public void onLoading() { public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called"); if (DEBUG) Log.d(TAG, "onLoading() called");
@ -463,7 +289,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (isProgressLoopRunning.get()) stopProgressLoop(); if (isProgressLoopRunning.get()) stopProgressLoop();
if (videoThumbnail != null) endScreen.setImageBitmap(videoThumbnail);
animateView(controlsRoot, true, 500, 0); animateView(controlsRoot, true, 500, 0);
animateView(endScreen, true, 800, 0); animateView(endScreen, true, 800, 0);
animateView(currentDisplaySeek, false, 200, 0); animateView(currentDisplaySeek, false, 200, 0);
@ -481,74 +306,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (currentRepeatMode == RepeatMode.REPEAT_ONE) { if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
changeState(STATE_LOADING); changeState(STATE_LOADING);
getPlayer().seekTo(0); simpleExoPlayer.seekTo(0);
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
@Override
public void onLoadingChanged(boolean isLoading) {
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop();
else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop();
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
if (getCurrentState() == STATE_PAUSED_SEEK) {
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek");
return;
}
switch (playbackState) {
case ExoPlayer.STATE_IDLE: // 1
isPrepared = false;
break;
case ExoPlayer.STATE_BUFFERING: // 2
if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING);
break;
case ExoPlayer.STATE_READY: //3
if (!isPrepared) {
isPrepared = true;
onPrepared(playWhenReady);
break;
}
if (currentState == STATE_PAUSED_SEEK) break;
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
break;
case ExoPlayer.STATE_ENDED: // 4
changeState(STATE_COMPLETED);
isPrepared = false;
break;
}
}
@Override
public void onPlayerError(ExoPlaybackException error) {
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
onError();
}
@Override
public void onPositionDiscontinuity() {
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called");
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// ExoPlayer Video Listener // ExoPlayer Video Listener
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -570,8 +331,7 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
// General Player // General Player
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public abstract void onError(); @Override
public void onPrepared(boolean playWhenReady) { public void onPrepared(boolean playWhenReady) {
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
@ -584,11 +344,19 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration()); playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration())); playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); super.onPrepared(playWhenReady);
} }
@Override
public void destroy() {
super.destroy();
if (endScreen != null) endScreen.setImageBitmap(null);
}
@Override
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
if (!isPrepared) return; if (!isPrepared) return;
if (currentState != STATE_PAUSED) { if (currentState != STATE_PAUSED) {
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress); if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
playbackCurrentTime.setText(getTimeString(currentProgress)); playbackCurrentTime.setText(getTimeString(currentProgress));
@ -601,32 +369,32 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
} }
} }
public void onUpdateThumbnail(Intent intent) { @Override
if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called with: intent = [" + intent + "]"); public void onVideoPlayPauseRepeat() {
if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return; if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail; if (qualityChanged) {
setVideoStartPos(0);
play(true);
} else super.onVideoPlayPauseRepeat();
} }
public void onVideoPlayPause() { @Override
if (DEBUG) Log.d(TAG, "onVideoPlayPause() called"); public void onThumbnailReceived(Bitmap thumbnail) {
if (currentState == STATE_COMPLETED) { super.onThumbnailReceived(thumbnail);
changeState(STATE_LOADING); if (thumbnail != null) endScreen.setImageBitmap(thumbnail);
if (qualityChanged) playVideo(getSelectedVideoStream(), true);
simpleExoPlayer.seekTo(0);
return;
}
simpleExoPlayer.setPlayWhenReady(!isPlaying());
} }
protected abstract void onFullScreenButtonClicked();
@Override
public void onFastRewind() { public void onFastRewind() {
if (DEBUG) Log.d(TAG, "onFastRewind() called"); super.onFastRewind();
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true); showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
} }
@Override
public void onFastForward() { public void onFastForward() {
if (DEBUG) Log.d(TAG, "onFastForward() called"); super.onFastForward();
seekBy(FAST_FORWARD_REWIND_AMOUNT);
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true); showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
} }
@ -651,10 +419,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
public boolean onMenuItemClick(MenuItem menuItem) { public boolean onMenuItemClick(MenuItem menuItem) {
if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]"); if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
if (selectedIndexStream == menuItem.getItemId()) return true; if (selectedIndexStream == menuItem.getItemId()) return true;
setVideoStartPos((int) getPlayer().getCurrentPosition()); setVideoStartPos((int) simpleExoPlayer.getCurrentPosition());
selectedIndexStream = menuItem.getItemId(); selectedIndexStream = menuItem.getItemId();
if (!(getCurrentState() == STATE_COMPLETED)) playVideo(getSelectedVideoStream(), wasPlaying); if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying);
else qualityChanged = true; else qualityChanged = true;
qualityTextView.setText(menuItem.getTitle()); qualityTextView.setText(menuItem.getTitle());
@ -671,8 +439,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
qualityTextView.setText(getSelectedVideoStream().resolution); qualityTextView.setText(getSelectedVideoStream().resolution);
} }
public abstract void onFullScreenButtonClicked();
public void onQualitySelectorClicked() { public void onQualitySelectorClicked() {
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called"); if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
qualityPopupMenu.show(); qualityPopupMenu.show();
@ -684,18 +450,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
wasPlaying = isPlaying(); wasPlaying = isPlaying();
} }
public void onRepeatClicked() {
if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
// TODO: implement repeat all when playlist is implemented
// Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
setCurrentRepeatMode(getCurrentRepeatMode() == RepeatMode.REPEAT_DISABLED ?
RepeatMode.REPEAT_ONE :
RepeatMode.REPEAT_DISABLED);
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getCurrentRepeatMode().name());
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// SeekBar Listener // SeekBar Listener
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -737,21 +491,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private static final StringBuilder stringBuilder = new StringBuilder();
private static final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
public String getTimeString(int milliSeconds) {
long seconds = (milliSeconds % 60000L) / 1000L;
long minutes = (milliSeconds % 3600000L) / 60000L;
long hours = (milliSeconds % 86400000L) / 3600000L;
long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
stringBuilder.setLength(0);
return days > 0 ? formatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
: hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
: formatter.format("%02d:%02d", minutes, seconds).toString();
}
public boolean isControlsVisible() { public boolean isControlsVisible() {
return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE; return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
} }
@ -908,62 +647,14 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
} }
} }
private void seekBy(int milliSeconds) {
if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
if (simpleExoPlayer == null) return;
int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds);
simpleExoPlayer.seekTo(progress);
}
public boolean isPlaying() {
return simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY && simpleExoPlayer.getPlayWhenReady();
}
public boolean isQualityMenuVisible() { public boolean isQualityMenuVisible() {
return isQualityPopupMenuVisible; return isQualityPopupMenuVisible;
} }
private void startProgressLoop() {
progressLoop.removeCallbacksAndMessages(null);
isProgressLoopRunning.set(true);
progressLoop.post(progressUpdate);
}
private void stopProgressLoop() {
isProgressLoopRunning.set(false);
progressLoop.removeCallbacksAndMessages(null);
}
public void tryDeleteCacheFiles(Context context) {
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
if (cacheDir.exists()) {
try {
if (cacheDir.isDirectory()) {
for (File file : cacheDir.listFiles()) {
try {
if (DEBUG) Log.d(TAG, "tryDeleteCacheFiles: " + file.getAbsolutePath() + " deleted = " + file.delete());
} catch (Exception ignored) {
}
}
}
} catch (Exception ignored) {
}
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Getters and Setters // Getters and Setters
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public SimpleExoPlayer getPlayer() {
return simpleExoPlayer;
}
public SharedPreferences getSharedPreferences() {
return sharedPreferences;
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() { public AspectRatioFrameLayout getAspectRatioFrameLayout() {
return aspectRatioFrameLayout; return aspectRatioFrameLayout;
} }
@ -972,22 +663,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
return surfaceView; return surfaceView;
} }
public RepeatMode getCurrentRepeatMode() {
return currentRepeatMode;
}
public void setCurrentRepeatMode(RepeatMode mode) {
currentRepeatMode = mode;
}
public boolean wasPlaying() { public boolean wasPlaying() {
return wasPlaying; return wasPlaying;
} }
public int getCurrentState() {
return currentState;
}
public VideoStream getSelectedVideoStream() { public VideoStream getSelectedVideoStream() {
return videoStreamsList.get(selectedIndexStream); return videoStreamsList.get(selectedIndexStream);
} }
@ -1000,46 +679,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
return qualityPopupMenuGroupId; return qualityPopupMenuGroupId;
} }
public String getVideoUrl() {
return videoUrl;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public int getVideoStartPos() {
return videoStartPos;
}
public void setVideoStartPos(int videoStartPos) {
this.videoStartPos = videoStartPos;
}
public String getVideoTitle() {
return videoTitle;
}
public void setVideoTitle(String videoTitle) {
this.videoTitle = videoTitle;
}
public Bitmap getVideoThumbnail() {
return videoThumbnail;
}
public void setVideoThumbnail(Bitmap videoThumbnail) {
this.videoThumbnail = videoThumbnail;
}
public String getChannelName() {
return channelName;
}
public void setChannelName(String channelName) {
this.channelName = channelName;
}
public int getSelectedStreamIndex() { public int getSelectedStreamIndex() {
return selectedIndexStream; return selectedIndexStream;
} }
@ -1135,4 +774,5 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
public TextView getCurrentDisplaySeek() { public TextView getCurrentDisplaySeek() {
return currentDisplaySeek; return currentDisplaySeek;
} }
} }

View File

@ -8,33 +8,57 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo; import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.fragments.OnItemSelectedListener; import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.player.AbstractPlayer; import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.VideoPlayer;
@SuppressWarnings({"unused", "WeakerAccess"}) @SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper { public class NavigationHelper {
public static Intent getOpenPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) { public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
return new Intent(context, targetClazz) Intent mIntent = new Intent(context, targetClazz)
.putExtra(AbstractPlayer.VIDEO_TITLE, info.title) .putExtra(BasePlayer.VIDEO_TITLE, info.title)
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url) .putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader) .putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex) .putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false)) .putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
.putExtra(AbstractPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams)); .putExtra(VideoPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false))
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams));
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
return mIntent;
} }
public static Intent getOpenPlayerIntent(Context context, Class targetClazz, AbstractPlayer instance) {
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, VideoPlayer instance) {
return new Intent(context, targetClazz) return new Intent(context, targetClazz)
.putExtra(AbstractPlayer.VIDEO_TITLE, instance.getVideoTitle()) .putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
.putExtra(AbstractPlayer.VIDEO_URL, instance.getVideoUrl()) .putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl())
.putExtra(AbstractPlayer.CHANNEL_NAME, instance.getChannelName()) .putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex()) .putExtra(BasePlayer.CHANNEL_NAME, instance.getChannelName())
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList()) .putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
.putExtra(AbstractPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream()) .putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
.putExtra(AbstractPlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition())); .putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
.putExtra(BasePlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()));
}
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(Utils.getPreferredAudioFormat(context, info.audio_streams)));
}
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
Intent mIntent = new Intent(context, BackgroundPlayer.class)
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
.putExtra(BackgroundPlayer.AUDIO_STREAM, audioStream);
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
return mIntent;
} }

View File

@ -136,13 +136,19 @@ public class Utils {
break; break;
} }
int highestQualityIndex = 0;
// Try to find a audio stream with the preferred format
for (int i = 0; i < audioStreams.size(); i++) if (audioStreams.get(i).format == preferredFormat) highestQualityIndex = i;
// Try to find a audio stream with the highest bitrate and preferred format
for (int i = 0; i < audioStreams.size(); i++) { for (int i = 0; i < audioStreams.size(); i++) {
if (audioStreams.get(i).format == preferredFormat) { AudioStream audioStream = audioStreams.get(i);
return i; if (audioStream.avgBitrate > audioStreams.get(highestQualityIndex).avgBitrate
} && audioStream.format == preferredFormat) highestQualityIndex = i;
} }
return 0; return highestQualityIndex;
} }
/** /**
@ -191,14 +197,13 @@ public class Utils {
} }
return getSortedStreamVideosList(preferredFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder); return getSortedStreamVideosList(preferredFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
} }
//show_higher_resolutions_key
/** /**
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format * Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
* chosen by the user * chosen by the user
* *
* @param preferredFormat format to give preference * @param preferredFormat format to give preference
* @param showHigherResolutions * @param showHigherResolutions show >1080p resolutions
* @param videoStreams normal videos list * @param videoStreams normal videos list
* @param videoOnlyStreams video only stream list * @param videoOnlyStreams video only stream list
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list * @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/gray"/>
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/middle_gray"/>
</shape>
</clip>
</item>
</layer-list>

View File

@ -1,16 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notificationContent" android:layout_width="match_parent"
android:layout_width="fill_parent" android:layout_height="64dp"
android:layout_height="wrap_content" xmlns:tools="http://schemas.android.com/tools">
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@color/background_notification_color">
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/notificationContent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="64dp"
android:background="@color/background_notification_color"
android:clickable="true"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
@ -18,65 +19,102 @@
android:id="@+id/notificationCover" android:id="@+id/notificationCover"
android:layout_width="64dp" android:layout_width="64dp"
android:layout_height="64dp" android:layout_height="64dp"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail" android:src="@drawable/dummy_thumbnail"
android:scaleType="centerCrop"/> tools:ignore="ContentDescription"/>
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" > android:orientation="vertical"
tools:ignore="RtlHardcoded">
<TextView <TextView
android:id="@+id/notificationSongName" android:id="@+id/notificationSongName"
style="@android:style/TextAppearance.StatusBar.EventContent.Title" style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:ellipsize="end"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="marquee" android:maxLines="1"
android:singleLine="true" android:textSize="14sp"
android:text="title" /> 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 <TextView
android:id="@+id/notificationArtist" android:id="@+id/notificationArtist"
android:layout_width="match_parent"
style="@android:style/TextAppearance.StatusBar.EventContent" style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="wrap_content" android:ellipsize="end"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="marquee" android:maxLines="1"
android:singleLine="true" android:textSize="12sp"
android:text="artist" /> tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout> </LinearLayout>
<ImageButton <ImageButton
android:id="@+id/notificationRewind" android:id="@+id/notificationRepeat"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="match_parent"
android:layout_margin="5dp" android:background="#00000000"
android:background="#00ffffff"
android:clickable="true" android:clickable="true"
android:scaleType="fitXY" android:padding="5dp"
android:src="@drawable/ic_action_av_fast_rewind" /> android:scaleType="fitCenter"
android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationFRewind"
android:layout_width="45dp"
android:layout_height="match_parent"
android:background="#00000000"
android:clickable="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_rewind"
tools:ignore="ContentDescription"/>
<ImageButton <ImageButton
android:id="@+id/notificationPlayPause" android:id="@+id/notificationPlayPause"
android:layout_width="40dp" android:layout_width="45dp"
android:layout_height="40dp" android:layout_height="match_parent"
android:layout_margin="5dp" android:background="#00000000"
android:background="#00ffffff"
android:clickable="true" android:clickable="true"
android:scaleType="fitXY" android:src="@drawable/ic_pause_white"
android:src="@drawable/ic_pause_white" /> tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationFForward"
android:layout_width="45dp"
android:layout_height="match_parent"
android:background="#00000000"
android:clickable="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_forward"
tools:ignore="ContentDescription"/>
<ImageButton <ImageButton
android:id="@+id/notificationStop" android:id="@+id/notificationStop"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_margin="5dp" android:layout_marginLeft="5dp"
android:background="#00ffffff" android:background="#00000000"
android:clickable="true" android:clickable="true"
android:scaleType="fitXY" android:padding="5dp"
android:src="@drawable/ic_close_white" /> android:scaleType="fitCenter"
android:src="@drawable/ic_close_white"
tools:ignore="ContentDescription,RtlHardcoded"/>
</LinearLayout> </LinearLayout>
<ProgressBar
</RelativeLayout> android:id="@+id/notificationProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_gravity="bottom"
android:layout_marginLeft="64dp"
android:progressDrawable="@drawable/custom_progress_bar"
tools:ignore="RtlHardcoded"
tools:progress="52"/>
</FrameLayout>

View File

@ -1,4 +1,153 @@
<?xml version="1.0" encoding="utf-8"?> <?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/notificationContent"
android:layout_width="match_parent"
android:layout_height="128dp"
android:background="@color/background_notification_color"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/notificationCover"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_alignParentLeft="true"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
tools:ignore="ContentDescription,RtlHardcoded"/>
<ImageButton
android:id="@+id/notificationStop"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:background="#00000000"
android:clickable="true"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_close_white"
tools:ignore="ContentDescription,RtlHardcoded"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/notificationStop"
android:layout_toRightOf="@+id/notificationCover"
android:orientation="vertical"
android:padding="8dp"
tools:ignore="RtlHardcoded,RtlSymmetry">
<TextView
android:id="@+id/notificationSongName"
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"
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/notificationArtist"
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>
<ProgressBar
android:id="@+id/notificationProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_alignTop="@+id/notificationControls"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toRightOf="@+id/notificationCover"
android:progressDrawable="@drawable/custom_progress_bar"
tools:ignore="RtlHardcoded"
tools:progress="52"/>
<RelativeLayout
android:id="@+id/notificationControls"
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingTop="10dp"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/notificationCover"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<ImageButton
android:id="@+id/notificationRepeat"
android:layout_width="45dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="#00000000"
android:clickable="true"
android:paddingBottom="4dp"
android:paddingLeft="11dp"
android:paddingRight="11dp"
android:paddingTop="4dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationFRewind"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/notificationPlayPause"
android:background="#00000000"
android:clickable="true"
android:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_rewind"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationPlayPause"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/notificationFForward"
android:background="#00000000"
android:padding="2dp"
android:clickable="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_pause_white"
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/notificationFForward"
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:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_action_av_fast_forward"
tools:ignore="ContentDescription"/>
</RelativeLayout>
</RelativeLayout>
<!--
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notificationContent" android:id="@+id/notificationContent"
android:layout_width="fill_parent" android:layout_width="fill_parent"
@ -93,4 +242,4 @@
android:layout_alignParentLeft="true" /> android:layout_alignParentLeft="true" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>-->

View File

@ -29,20 +29,23 @@
<TextView <TextView
android:id="@+id/notificationSongName" android:id="@+id/notificationSongName"
style="@android:style/TextAppearance.StatusBar.EventContent.Title" style="@android:style/TextAppearance.StatusBar.EventContent.Title"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:ellipsize="end"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="marquee"
android:maxLines="1" android:maxLines="1"
tools:text="a long, long, long, long, long title"/> android:textSize="14sp"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta."/>
<TextView <TextView
android:id="@+id/notificationArtist" android:id="@+id/notificationArtist"
android:layout_width="match_parent"
style="@android:style/TextAppearance.StatusBar.EventContent" style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="wrap_content" android:ellipsize="end"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="marquee"
android:maxLines="1" android:maxLines="1"
tools:text="a long, long artist"/> android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout> </LinearLayout>
<ImageButton <ImageButton