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" />
<activity
android:name=".player.ExoPlayerActivity"
android:name=".player.MainVideoPlayer"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"

View file

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

View file

@ -45,7 +45,7 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
private static final String TAG = MainActivity.class.toString();
//private static final String TAG = "MainActivity";
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
@ -57,12 +57,23 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (savedInstanceState == null) initFragments();
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
}
@Override
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);
setIntent(intent);
handleIntent(intent);
}

View file

@ -7,7 +7,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
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.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.Localization;
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.fragments.OnItemSelectedListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.AbstractPlayer;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.PopupVideoPlayer;
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 DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
private Bitmap streamThumbnail = null;
/*//////////////////////////////////////////////////////////////////////////
// Views
@ -409,25 +404,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView,
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
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
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();
return;
}
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
Intent mIntent = NavigationHelper.getOpenPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId);
if (info.start_position > 0) mIntent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
Intent mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId);
activity.startService(mIntent);
}
});
@ -624,24 +598,10 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
Intent intent;
AudioStream audioStream =
info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent
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);
}
AudioStream audioStream = info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
activity.startService(NavigationHelper.getOpenBackgroundPlayerIntent(activity, info, audioStream));
Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
} else {
intent = new Intent();
try {
@ -829,9 +789,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|| (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) {
// ExoPlayer
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
mIntent = NavigationHelper.getOpenPlayerIntent(activity, ExoPlayerActivity.class, info, actionBarHandler.getSelectedVideoStream());
if (info.start_position > 0) mIntent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, MainVideoPlayer.class, info, actionBarHandler.getSelectedVideoStream());
} else {
// Internal Player
mIntent = new Intent(activity, PlayVideoActivity.class)

View file

@ -4,584 +4,452 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
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.widget.RemoteViews;
import android.widget.Toast;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
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.ThemeHelper;
import java.io.Serializable;
import java.io.IOException;
import java.util.Arrays;
/**
* Created by Adam Howard on 08/11/15.
* Copyright (c) Adam Howard <achdisposable1@gmail.com> 2015
* Base players joining the common properties
*
* BackgroundPlayer.java is part of NewPipe.
*
* 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/>.
* @author mauriciocolli
*/
/**Plays the audio stream of videos in the background.*/
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
public class BackgroundPlayer extends Service {
private static final String TAG = "BackgroundPlayer";
private static final String CLASSNAME = "org.schabi.newpipe.player.BackgroundPlayer";
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";
private static final boolean DEBUG = BasePlayer.DEBUG;
// Extra intent arguments
public static final String TITLE = "title";
public static final String WEB_URL = "web_url";
public static final String SERVICE_ID = "service_id";
public static final String CHANNEL_NAME = "channel_name";
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.BackgroundPlayer.OPEN_DETAIL";
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
private volatile String webUrl = "";
private volatile int serviceId = -1;
private volatile String channelName = "";
public static final String AUDIO_STREAM = "video_only_audio_stream";
private AudioStream audioStream;
// Determines if the service is already running.
// Prevents launching the service twice.
public static volatile boolean isRunning;
private BasePlayerImpl basePlayerImpl;
private PowerManager powerManager;
private WifiManager wifiManager;
public BackgroundPlayer() {
super();
}
private PowerManager.WakeLock wakeLock;
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
public void onCreate() {
/*PendingIntent pi = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);*/
super.onCreate();
if (DEBUG) Log.d(TAG, "onCreate() called");
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
powerManager = ((PowerManager) getSystemService(POWER_SERVICE));
wifiManager = ((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE));
ThemeHelper.setTheme(this, false);
basePlayerImpl = new BasePlayerImpl(this);
basePlayerImpl.setup();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, R.string.background_player_playing_toast,
Toast.LENGTH_SHORT).show();
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
if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
basePlayerImpl.handleIntent(intent);
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding (yet?), so return null
return null;
public void onDestroy() {
if (DEBUG) Log.d(TAG, "destroy() called");
releaseWifiAndCpu();
stopForeground(true);
if (basePlayerImpl != null) basePlayerImpl.destroy();
}
@Override
public void onDestroy() {
//Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
isRunning = false;
public IBinder onBind(Intent intent) {
return null;
}
/*//////////////////////////////////////////////////////////////////////////
// Actions
//////////////////////////////////////////////////////////////////////////*/
private class PlayerThread extends Thread {
MediaPlayer mediaPlayer;
private String source;
private String title;
private int noteID = TAG.hashCode();
private BackgroundPlayer owner;
private NotificationManager noteMgr;
private WifiManager.WifiLock wifiLock;
private Bitmap videoThumbnail;
private NoteBuilder noteBuilder;
private volatile boolean donePlaying = false;
public void onOpenDetail(Context context, String videoUrl, String videoTitle) {
if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
Intent i = new Intent(context, MainActivity.class);
i.putExtra(Constants.KEY_SERVICE_ID, 0);
i.putExtra(Constants.KEY_URL, videoUrl);
i.putExtra(Constants.KEY_TITLE, videoTitle);
i.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
public PlayerThread(String src, String title, BackgroundPlayer owner) {
this.source = src;
this.title = title;
this.owner = owner;
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
private void onClose() {
if (basePlayerImpl != null) basePlayerImpl.destroyPlayer();
stopForeground(true);
releaseWifiAndCpu();
stopSelf();
}
public boolean isDonePlaying() {
return donePlaying;
}
private void onScreenOnOff(boolean on) {
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;
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/
private synchronized PlaybackState getPlaybackState() {
try {
return new PlaybackState(mediaPlayer.getDuration(), mediaPlayer.getCurrentPosition(), isPlaying());
} 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 NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
private void broadcastState() {
PlaybackState state = getPlaybackState();
if(state == null) return;
Intent intent = new Intent(ACTION_PLAYBACK_STATE);
intent.putExtra(EXTRA_PLAYBACK_STATE, state);
sendBroadcast(intent);
}
setupNotification(notRemoteView);
setupNotification(bigNotRemoteView);
@Override
public void run() {
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock
try {
mediaPlayer.setDataSource(source);
//We are already in a separate worker thread,
//so calling the blocking prepare() method should be ok
mediaPlayer.prepare();
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) builder.setPriority(Notification.PRIORITY_MAX);
return builder;
}
} catch (IOException ioe) {
ioe.printStackTrace();
Log.e(TAG, "video source:" + source);
Log.e(TAG, "video title:" + title);
//can't do anything useful without a file to play; exit early
return;
}
private void setupNotification(RemoteViews remoteViews) {
//if (videoThumbnail != null) remoteViews.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
///else remoteViews.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
remoteViews.setTextViewText(R.id.notificationSongName, basePlayerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, basePlayerImpl.getChannelName());
try {
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
} catch (Exception e) {
Log.e(TAG, "Could not get video thumbnail from ActivityCommunicator");
e.printStackTrace();
}
remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
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);
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
//listen for end of video
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);
}
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
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 static final int INDEX_IS_PLAYING = 0;
private static final int INDEX_IS_PREPARED= 1;
private static final int INDEX_HAS_ERROR = 2;
private final int duration;
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);
private void updateNotification(int drawableId) {
if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
if (notBuilder == null) return;
if (drawableId != -1) {
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
PlaybackState(int duration, int played, boolean isPlaying) {
this.played = played;
this.duration = duration;
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = true;
this.booleanValues[INDEX_HAS_ERROR] = false;
}
private void setControlsOpacity(@IntRange(from = 0, to = 255) int opacity) {
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
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;
this.duration = 0;
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = isPrepared;
this.booleanValues[INDEX_HAS_ERROR] = hasErrors;
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
int getDuration() {
return duration;
}
private void lockWifiAndCpu() {
if (DEBUG) Log.d(TAG, "lockWifiAndCpu() called");
if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return;
int getPlayedTime() {
return played;
}
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
boolean isPlaying() {
return booleanValues[INDEX_IS_PLAYING];
}
if (wakeLock != null) wakeLock.acquire();
if (wifiLock != null) wifiLock.acquire();
}
boolean isPrepared() {
return booleanValues[INDEX_IS_PREPARED];
}
private void releaseWifiAndCpu() {
if (DEBUG) Log.d(TAG, "releaseWifiAndCpu() called");
if (wakeLock != null && wakeLock.isHeld()) wakeLock.release();
if (wifiLock != null && wifiLock.isHeld()) wifiLock.release();
boolean hasErrors() {
return booleanValues[INDEX_HAS_ERROR];
}
wakeLock = null;
wifiLock = null;
}
//////////////////////////////////////////////////////////////////////////
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(duration);
dest.writeInt(played);
dest.writeBooleanArray(booleanValues);
private class BasePlayerImpl extends BasePlayer {
BasePlayerImpl(Context context) {
super(context);
}
@Override
public int describeContents() {
return 0;
public void handleIntent(Intent intent) {
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
public PlaybackState createFromParcel(Parcel in) {
return new PlaybackState(in);
@Override
public void initThumbnail() {
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
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
public int hashCode() {
if(this == UNPREPARED) return 1;
if(this == FAILED) return 2;
int result = duration;
result = 31 * result + played;
result = 31 * result + Arrays.hashCode(booleanValues);
return result + 2;
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
notBuilder = createNotification();
startForeground(NOTIFICATION_ID, notBuilder.build());
}
@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;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.media.AudioManager;
@ -23,22 +21,20 @@ import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
/**
* Activity Player implementing AbstractPlayer
* Activity Player implementing VideoPlayer
*
* @author mauriciocolli
*/
public class ExoPlayerActivity extends Activity {
private static final String TAG = ".ExoPlayerActivity";
private static final boolean DEBUG = AbstractPlayer.DEBUG;
public class MainVideoPlayer extends Activity {
private static final String TAG = ".MainVideoPlayer";
private static final boolean DEBUG = BasePlayer.DEBUG;
private AudioManager audioManager;
private BroadcastReceiver broadcastReceiver;
private GestureDetector gestureDetector;
private final Runnable hideUiRunnable = new Runnable() {
@ -49,7 +45,7 @@ public class ExoPlayerActivity extends Activity {
};
private boolean activityPaused;
private AbstractPlayerImpl playerImpl;
private VideoPlayerImpl playerImpl;
/*//////////////////////////////////////////////////////////////////////////
// Activity LifeCycle
@ -72,9 +68,8 @@ public class ExoPlayerActivity extends Activity {
showSystemUi();
setContentView(R.layout.activity_exo_player);
playerImpl = new AbstractPlayerImpl();
playerImpl = new VideoPlayerImpl();
playerImpl.setup(findViewById(android.R.id.content));
initReceiver();
playerImpl.handleIntent(getIntent());
}
@ -97,8 +92,10 @@ public class ExoPlayerActivity extends Activity {
super.onStop();
if (DEBUG) Log.d(TAG, "onStop() called");
activityPaused = true;
playerImpl.destroy();
playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
if (playerImpl.getPlayer() != null) {
playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
playerImpl.destroyPlayer();
}
}
@Override
@ -106,9 +103,9 @@ public class ExoPlayerActivity extends Activity {
super.onResume();
if (DEBUG) Log.d(TAG, "onResume() called");
if (activityPaused) {
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
playerImpl.initPlayer();
playerImpl.playVideo(playerImpl.getSelectedVideoStream(), false);
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
playerImpl.play(false);
activityPaused = false;
}
}
@ -118,29 +115,6 @@ public class ExoPlayerActivity extends Activity {
super.onDestroy();
if (DEBUG) Log.d(TAG, "onDestroy() called");
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"})
private class AbstractPlayerImpl extends AbstractPlayer {
private class VideoPlayerImpl extends VideoPlayer {
private TextView titleTextView;
private TextView channelTextView;
private TextView volumeTextView;
@ -192,8 +166,8 @@ public class ExoPlayerActivity extends Activity {
private ImageButton screenRotationButton;
private ImageButton playPauseButton;
AbstractPlayerImpl() {
super("AbstractPlayerImpl" + ExoPlayerActivity.TAG, ExoPlayerActivity.this);
VideoPlayerImpl() {
super("VideoPlayerImpl" + MainVideoPlayer.TAG, MainVideoPlayer.this);
}
@Override
@ -238,8 +212,8 @@ public class ExoPlayerActivity extends Activity {
}
@Override
public void playVideo(VideoStream videoStream, boolean autoPlay) {
super.playVideo(videoStream, autoPlay);
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(ExoPlayerActivity.this)) {
Toast.makeText(ExoPlayerActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
&& !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) {
Toast.makeText(MainVideoPlayer.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return;
}
if (playerImpl != null) playerImpl.destroy();
context.startService(NavigationHelper.getOpenPlayerIntent(context, PopupVideoPlayer.class, playerImpl));
context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, playerImpl));
if (playerImpl != null) playerImpl.destroyPlayer();
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
ExoPlayerActivity.this.finish();
MainVideoPlayer.this.finish();
}
@Override
@ -307,28 +281,6 @@ public class ExoPlayerActivity extends Activity {
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
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
@ -344,7 +296,8 @@ public class ExoPlayerActivity extends Activity {
}
@Override
public void onError() {
public void onError(Exception exception) {
exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
finish();
}
@ -366,10 +319,37 @@ public class ExoPlayerActivity extends Activity {
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
public void 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();
}
@ -379,12 +359,6 @@ public class ExoPlayerActivity extends Activity {
animateView(playPauseButton, false, 100, 0);
}
@Override
public void onPlaying() {
super.onPlaying();
animateView(playPauseButton, true, 500, 0);
showSystemUi();
}
@Override
public void onCompleted() {
@ -467,14 +441,14 @@ public class ExoPlayerActivity extends Activity {
@Override
public boolean onSingleTapConfirmed(MotionEvent 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);
else {
playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
@Override
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();
@ -482,25 +456,25 @@ public class ExoPlayerActivity extends Activity {
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 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 volumeUnicode = new String(Character.toChars(0x1F50A));
private final String volumeUnicode = new String(Character.toChars(0x1F508));
private final int MOVEMENT_THRESHOLD = 40;
private final int eventsThreshold = 3;
private final int eventsThreshold = 8;
private boolean triggered = false;
private int eventsNum;
// TODO: Improve video gesture controls
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "ExoPlayerActivity.onScroll = " +
if (DEBUG && true) Log.d(TAG, "MainVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");
@ -510,16 +484,17 @@ public class ExoPlayerActivity extends Activity {
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;
// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
boolean up = distanceY > 0;
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 <= 0) currentVolume = 0;
if (currentVolume <= minVolume) currentVolume = (int) minVolume;
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
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.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
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.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
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.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
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.StreamingService;
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.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
@ -47,13 +44,13 @@ import org.schabi.newpipe.util.Utils;
import org.schabi.newpipe.workers.StreamExtractorWorker;
/**
* Service Popup Player implementing AbstractPlayer
* Service Popup Player implementing VideoPlayer
*
* @author mauriciocolli
*/
public class PopupVideoPlayer extends Service {
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;
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_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
private BroadcastReceiver broadcastReceiver;
private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector;
@ -81,7 +76,7 @@ public class PopupVideoPlayer extends Service {
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
private AbstractPlayerImpl playerImpl;
private VideoPlayerImpl playerImpl;
private StreamExtractorWorker currentExtractorWorker;
/*//////////////////////////////////////////////////////////////////////////
@ -92,9 +87,8 @@ public class PopupVideoPlayer extends Service {
public void onCreate() {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
initReceiver();
playerImpl = new AbstractPlayerImpl();
playerImpl = new VideoPlayerImpl();
ThemeHelper.setTheme(this, false);
}
@ -132,7 +126,6 @@ public class PopupVideoPlayer extends Service {
}
if (imageLoader != null) imageLoader.clearMemoryCache();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
if (currentExtractorWorker != null) {
currentExtractorWorker.cancel();
currentExtractorWorker = null;
@ -148,39 +141,6 @@ public class PopupVideoPlayer extends Service {
// 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")
private void initPopup() {
if (DEBUG) Log.d(TAG, "initPopup() called");
@ -213,8 +173,9 @@ public class PopupVideoPlayer extends Service {
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
if (playerImpl.getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
else notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
if (playerImpl.getVideoThumbnail() == null) 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.notificationArtist, playerImpl.getChannelName());
@ -302,21 +263,35 @@ public class PopupVideoPlayer extends Service {
///////////////////////////////////////////////////////////////////////////
private class AbstractPlayerImpl extends AbstractPlayer {
AbstractPlayerImpl() {
super("AbstractPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
private class VideoPlayerImpl extends VideoPlayer {
VideoPlayerImpl() {
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
}
@Override
public void playVideo(VideoStream videoStream, boolean autoPlay) {
super.playVideo(videoStream, autoPlay);
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
windowManager.updateViewLayout(getRootView(), windowLayoutParams);
notBuilder = createNotification();
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
@ -324,8 +299,8 @@ public class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
Intent intent;
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
intent = NavigationHelper.getOpenPlayerIntent(context, ExoPlayerActivity.class, playerImpl);
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(AbstractPlayer.STARTED_FROM_NEWPIPE, false);
intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, playerImpl);
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
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));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
stopSelf();
if (playerImpl != null) playerImpl.destroy();
context.startActivity(intent);
if (playerImpl != null) playerImpl.destroyPlayer();
stopSelf();
}
@Override
@ -360,13 +335,6 @@ public class PopupVideoPlayer extends Service {
updateNotification(-1);
}
@Override
public void onUpdateThumbnail(Intent intent) {
super.onUpdateThumbnail(intent);
if (getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, getVideoThumbnail());
updateNotification(-1);
}
@Override
public void onDismiss(PopupMenu menu) {
super.onDismiss(menu);
@ -374,11 +342,45 @@ public class PopupVideoPlayer extends Service {
}
@Override
public void onError() {
public void onError(Exception exception) {
exception.printStackTrace();
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
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
//////////////////////////////////////////////////////////////////////////*/
@ -483,8 +485,8 @@ public class PopupVideoPlayer extends Service {
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
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) {
playerImpl.setVideoTitle(info.title);
playerImpl.setVideoUrl(info.webpage_url);
playerImpl.setVideoThumbnailUrl(info.thumbnail_url);
playerImpl.setChannelName(info.uploader);
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() {
@Override
public void run() {
playerImpl.playVideo(playerImpl.getSelectedVideoStream(), true);
playerImpl.play(true);
}
});
@ -551,7 +554,6 @@ public class PopupVideoPlayer extends Service {
playerImpl.setVideoThumbnail(loadedImage);
if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
updateNotification(-1);
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = loadedImage;
}
});
}
@ -619,6 +621,7 @@ public class PopupVideoPlayer extends Service {
@Override
public void onUnrecoverableError(Exception exception) {
exception.printStackTrace();
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.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@ -29,84 +25,42 @@ import android.widget.ProgressBar;
import android.widget.SeekBar;
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.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.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.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.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Common properties of the players
* Base for <b>video</b> players
*
* @author mauriciocolli
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener, SimpleExoPlayer.VideoListener {
public static final boolean DEBUG = false;
@SuppressWarnings({"WeakerAccess", "unused"})
public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.VideoListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
public static final boolean DEBUG = BasePlayer.DEBUG;
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
//////////////////////////////////////////////////////////////////////////*/
public static final String VIDEO_URL = "video_url";
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_TITLE = "video_title";
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";
private String videoUrl = "";
private int videoStartPos = -1;
private String videoTitle = "";
private Bitmap videoThumbnail;
private String channelName = "";
private int selectedIndexStream;
private ArrayList<VideoStream> videoStreamsList = new ArrayList<>();
private AudioStream videoOnlyAudioStream;
@ -115,36 +69,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
// 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 String CACHE_FOLDER_NAME = "exoplayer";
private boolean startedFromNewPipe = true;
private boolean isPrepared = 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
@ -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.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) {
initViews(rootView);
initListeners();
if (simpleExoPlayer == null) initPlayer();
else {
simpleExoPlayer.addListener(this);
simpleExoPlayer.setVideoListener(this);
simpleExoPlayer.setVideoSurfaceView(surfaceView);
}
setup();
}
public void initViews(View rootView) {
@ -241,36 +149,24 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
}
@Override
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, 100);
}
};
super.initListeners();
playbackSeekBar.setOnSeekBarChangeListener(this);
fullScreenButton.setOnClickListener(this);
qualityTextView.setOnClickListener(this);
}
@Override
public void initPlayer() {
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
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);
super.initPlayer();
simpleExoPlayer.setVideoSurfaceView(surfaceView);
simpleExoPlayer.setVideoListener(this);
}
@SuppressWarnings("unchecked")
public void handleIntent(Intent intent) {
super.handleIntent(intent);
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent == null) return;
@ -284,79 +180,36 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
Serializable audioStream = intent.getSerializableExtra(VIDEO_ONLY_AUDIO_STREAM);
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);
try {
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
} catch (Exception e) {
e.printStackTrace();
}
playVideo(getSelectedVideoStream(), true);
play(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) {
onError();
return;
}
public void play(boolean autoPlay) {
playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay);
}
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;
if (url == null || simpleExoPlayer == null) {
RuntimeException runtimeException = new RuntimeException((url == null ? "Url " : "Player ") + " null");
onError(runtimeException);
throw runtimeException;
}
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
buildQualityMenu(qualityPopupMenu);
videoSource = buildMediaSource(videoStream, MediaFormat.getSuffixById(getSelectedVideoStream().format));
if (simpleExoPlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) simpleExoPlayer.stop();
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
simpleExoPlayer.prepare(videoSource);
simpleExoPlayer.setPlayWhenReady(autoPlay);
changeState(STATE_LOADING);
super.playUrl(url, format, autoPlay);
}
public void destroy() {
if (DEBUG) Log.d(TAG, "destroy() called");
if (simpleExoPlayer != null) {
simpleExoPlayer.stop();
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;
@Override
public MediaSource buildMediaSource(String url, String overrideExtension) {
MediaSource mediaSource = super.buildMediaSource(url, overrideExtension);
if (!getSelectedVideoStream().isVideoOnly) return mediaSource;
Uri audioUri = Uri.parse(videoOnlyAudioStream.url);
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);
popupMenu.setOnMenuItemClickListener(this);
popupMenu.setOnDismissListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
// 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
public void onLoading() {
if (DEBUG) Log.d(TAG, "onLoading() called");
@ -463,7 +289,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (isProgressLoopRunning.get()) stopProgressLoop();
if (videoThumbnail != null) endScreen.setImageBitmap(videoThumbnail);
animateView(controlsRoot, true, 500, 0);
animateView(endScreen, true, 800, 0);
animateView(currentDisplaySeek, false, 200, 0);
@ -481,74 +306,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
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
//////////////////////////////////////////////////////////////////////////*/
@ -570,8 +331,7 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
// General Player
//////////////////////////////////////////////////////////////////////////*/
public abstract void onError();
@Override
public void onPrepared(boolean 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());
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) {
if (!isPrepared) return;
if (currentState != STATE_PAUSED) {
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
playbackCurrentTime.setText(getTimeString(currentProgress));
@ -601,32 +369,32 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
}
}
public void onUpdateThumbnail(Intent intent) {
if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called with: intent = [" + intent + "]");
if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return;
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
@Override
public void onVideoPlayPauseRepeat() {
if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
if (qualityChanged) {
setVideoStartPos(0);
play(true);
} else super.onVideoPlayPauseRepeat();
}
public void onVideoPlayPause() {
if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
if (currentState == STATE_COMPLETED) {
changeState(STATE_LOADING);
if (qualityChanged) playVideo(getSelectedVideoStream(), true);
simpleExoPlayer.seekTo(0);
return;
}
simpleExoPlayer.setPlayWhenReady(!isPlaying());
@Override
public void onThumbnailReceived(Bitmap thumbnail) {
super.onThumbnailReceived(thumbnail);
if (thumbnail != null) endScreen.setImageBitmap(thumbnail);
}
protected abstract void onFullScreenButtonClicked();
@Override
public void onFastRewind() {
if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
super.onFastRewind();
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
}
@Override
public void onFastForward() {
if (DEBUG) Log.d(TAG, "onFastForward() called");
seekBy(FAST_FORWARD_REWIND_AMOUNT);
super.onFastForward();
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) {
if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
if (selectedIndexStream == menuItem.getItemId()) return true;
setVideoStartPos((int) getPlayer().getCurrentPosition());
setVideoStartPos((int) simpleExoPlayer.getCurrentPosition());
selectedIndexStream = menuItem.getItemId();
if (!(getCurrentState() == STATE_COMPLETED)) playVideo(getSelectedVideoStream(), wasPlaying);
if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying);
else qualityChanged = true;
qualityTextView.setText(menuItem.getTitle());
@ -671,8 +439,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
qualityTextView.setText(getSelectedVideoStream().resolution);
}
public abstract void onFullScreenButtonClicked();
public void onQualitySelectorClicked() {
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
qualityPopupMenu.show();
@ -684,18 +450,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
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
//////////////////////////////////////////////////////////////////////////*/
@ -737,21 +491,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
// 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() {
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() {
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
//////////////////////////////////////////////////////////////////////////*/
public SimpleExoPlayer getPlayer() {
return simpleExoPlayer;
}
public SharedPreferences getSharedPreferences() {
return sharedPreferences;
}
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
return aspectRatioFrameLayout;
}
@ -972,22 +663,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
return surfaceView;
}
public RepeatMode getCurrentRepeatMode() {
return currentRepeatMode;
}
public void setCurrentRepeatMode(RepeatMode mode) {
currentRepeatMode = mode;
}
public boolean wasPlaying() {
return wasPlaying;
}
public int getCurrentState() {
return currentState;
}
public VideoStream getSelectedVideoStream() {
return videoStreamsList.get(selectedIndexStream);
}
@ -1000,46 +679,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
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() {
return selectedIndexStream;
}
@ -1135,4 +774,5 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
public TextView getCurrentDisplaySeek() {
return currentDisplaySeek;
}
}

View file

@ -8,33 +8,57 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
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.fragments.OnItemSelectedListener;
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"})
public class NavigationHelper {
public static Intent getOpenPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
return new Intent(context, targetClazz)
.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false))
.putExtra(AbstractPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams));
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
Intent mIntent = new Intent(context, targetClazz)
.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(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
.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)
.putExtra(AbstractPlayer.VIDEO_TITLE, instance.getVideoTitle())
.putExtra(AbstractPlayer.VIDEO_URL, instance.getVideoUrl())
.putExtra(AbstractPlayer.CHANNEL_NAME, instance.getChannelName())
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
.putExtra(AbstractPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
.putExtra(AbstractPlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()));
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
.putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl())
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
.putExtra(BasePlayer.CHANNEL_NAME, instance.getChannelName())
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
.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;
}
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++) {
if (audioStreams.get(i).format == preferredFormat) {
return i;
}
AudioStream audioStream = audioStreams.get(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);
}
//show_higher_resolutions_key
/**
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
* chosen by the user
*
* @param preferredFormat format to give preference
* @param showHigherResolutions
* @param showHigherResolutions show >1080p resolutions
* @param videoStreams normal videos list
* @param videoOnlyStreams video only stream 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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notificationContent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@color/background_notification_color">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
xmlns:tools="http://schemas.android.com/tools">
<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_height="64dp"
android:background="@color/background_notification_color"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal">
@ -18,65 +19,102 @@
android:id="@+id/notificationCover"
android:layout_width="64dp"
android:layout_height="64dp"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
android:scaleType="centerCrop"/>
tools:ignore="ContentDescription"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:orientation="vertical" >
android:orientation="vertical"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/notificationSongName"
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:ellipsize="marquee"
android:singleLine="true"
android:text="title" />
android:maxLines="1"
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"
android:layout_width="match_parent"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="wrap_content"
android:ellipsize="end"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="artist" />
android:maxLines="1"
android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout>
<ImageButton
android:id="@+id/notificationRewind"
android:id="@+id/notificationRepeat"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp"
android:background="#00ffffff"
android:layout_height="match_parent"
android:background="#00000000"
android:clickable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_action_av_fast_rewind" />
android:padding="5dp"
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
android:id="@+id/notificationPlayPause"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp"
android:background="#00ffffff"
android:layout_width="45dp"
android:layout_height="match_parent"
android:background="#00000000"
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
android:id="@+id/notificationStop"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="5dp"
android:background="#00ffffff"
android:layout_marginLeft="5dp"
android:background="#00000000"
android:clickable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_close_white" />
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_close_white"
tools:ignore="ContentDescription,RtlHardcoded"/>
</LinearLayout>
</RelativeLayout>
<ProgressBar
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"?>
<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"
android:id="@+id/notificationContent"
android:layout_width="fill_parent"
@ -93,4 +242,4 @@
android:layout_alignParentLeft="true" />
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>-->

View file

@ -29,20 +29,23 @@
<TextView
android:id="@+id/notificationSongName"
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:ellipsize="marquee"
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
android:id="@+id/notificationArtist"
android:layout_width="match_parent"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="wrap_content"
android:ellipsize="end"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:maxLines="1"
tools:text="a long, long artist"/>
android:textSize="12sp"
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
</LinearLayout>
<ImageButton