Merge branch 'feature-backplayer' of https://github.com/mauriciocolli/NewPipe into mark
This commit is contained in:
commit
8e7d2e91e9
16 changed files with 1609 additions and 1227 deletions
|
@ -40,7 +40,7 @@
|
||||||
android:label="@string/background_player_name" />
|
android:label="@string/background_player_name" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".player.ExoPlayerActivity"
|
android:name=".player.MainVideoPlayer"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
|
|
|
@ -20,10 +20,6 @@ package org.schabi.newpipe;
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton:
|
* Singleton:
|
||||||
* Used to send data between certain Activity/Services within the same process.
|
* Used to send data between certain Activity/Services within the same process.
|
||||||
|
@ -39,8 +35,5 @@ public class ActivityCommunicator {
|
||||||
return activityCommunicator;
|
return activityCommunicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
|
|
||||||
public volatile Bitmap backgroundPlayerThumbnail;
|
|
||||||
|
|
||||||
public volatile Class returnActivity;
|
public volatile Class returnActivity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ import org.schabi.newpipe.util.PermissionHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
|
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
|
||||||
private static final String TAG = MainActivity.class.toString();
|
//private static final String TAG = "MainActivity";
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Activity's LifeCycle
|
// Activity's LifeCycle
|
||||||
|
@ -57,12 +57,23 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||||
if (savedInstanceState == null) initFragments();
|
|
||||||
|
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||||
|
initFragments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
|
if (intent != null) {
|
||||||
|
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||||
|
// to not destroy the already created backstack
|
||||||
|
String action = intent.getAction();
|
||||||
|
if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return;
|
||||||
|
}
|
||||||
|
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
|
setIntent(intent);
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -43,7 +42,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
|
||||||
import org.schabi.newpipe.ImageErrorLoadingListener;
|
import org.schabi.newpipe.ImageErrorLoadingListener;
|
||||||
import org.schabi.newpipe.Localization;
|
import org.schabi.newpipe.Localization;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -57,9 +55,7 @@ import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||||
import org.schabi.newpipe.fragments.OnItemSelectedListener;
|
import org.schabi.newpipe.fragments.OnItemSelectedListener;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.player.AbstractPlayer;
|
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
|
||||||
import org.schabi.newpipe.player.ExoPlayerActivity;
|
|
||||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
@ -111,7 +107,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|
||||||
private static final ImageLoader imageLoader = ImageLoader.getInstance();
|
private static final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
private static final DisplayImageOptions displayImageOptions =
|
private static final DisplayImageOptions displayImageOptions =
|
||||||
new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
|
new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
|
||||||
private Bitmap streamThumbnail = null;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
|
@ -409,25 +404,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|
||||||
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
||||||
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView,
|
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView,
|
||||||
displayImageOptions, new SimpleImageLoadingListener() {
|
displayImageOptions, new SimpleImageLoadingListener() {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
|
||||||
streamThumbnail = loadedImage;
|
|
||||||
|
|
||||||
if (streamThumbnail != null) {
|
|
||||||
// TODO: Change the thumbnail implementation
|
|
||||||
|
|
||||||
// When the thumbnail is not loaded yet, it not passes to the service in time
|
|
||||||
// so, I can notify the service through a broadcast, but the problem is
|
|
||||||
// when I click in another video, another thumbnail will be load, and will
|
|
||||||
// notify again, so I send the videoUrl and compare with the service's url
|
|
||||||
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
|
|
||||||
Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB);
|
|
||||||
intent.putExtra(AbstractPlayer.VIDEO_URL, currentStreamInfo.webpage_url);
|
|
||||||
activity.sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||||
ErrorActivity.reportError(activity,
|
ErrorActivity.reportError(activity,
|
||||||
|
@ -529,11 +505,9 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|
||||||
Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
|
|
||||||
|
|
||||||
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||||
Intent mIntent = NavigationHelper.getOpenPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId);
|
Intent mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId);
|
||||||
if (info.start_position > 0) mIntent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
|
|
||||||
activity.startService(mIntent);
|
activity.startService(mIntent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -624,24 +598,10 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|
||||||
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
|
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
|
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
|
||||||
Intent intent;
|
Intent intent;
|
||||||
AudioStream audioStream =
|
AudioStream audioStream = info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
|
||||||
info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
|
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
|
||||||
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
|
activity.startService(NavigationHelper.getOpenBackgroundPlayerIntent(activity, info, audioStream));
|
||||||
//internal music player: explicit intent
|
Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
|
||||||
if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
|
|
||||||
ActivityCommunicator.getCommunicator()
|
|
||||||
.backgroundPlayerThumbnail = streamThumbnail;
|
|
||||||
intent = new Intent(activity, BackgroundPlayer.class);
|
|
||||||
|
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
|
||||||
intent.setDataAndType(Uri.parse(audioStream.url),
|
|
||||||
MediaFormat.getMimeById(audioStream.format));
|
|
||||||
intent.putExtra(BackgroundPlayer.TITLE, info.title);
|
|
||||||
intent.putExtra(BackgroundPlayer.WEB_URL, info.webpage_url);
|
|
||||||
intent.putExtra(BackgroundPlayer.SERVICE_ID, serviceId);
|
|
||||||
intent.putExtra(BackgroundPlayer.CHANNEL_NAME, info.uploader);
|
|
||||||
activity.startService(intent);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
intent = new Intent();
|
intent = new Intent();
|
||||||
try {
|
try {
|
||||||
|
@ -829,9 +789,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
|
||||||
|| (Build.VERSION.SDK_INT < 16);
|
|| (Build.VERSION.SDK_INT < 16);
|
||||||
if (!useOldPlayer) {
|
if (!useOldPlayer) {
|
||||||
// ExoPlayer
|
// ExoPlayer
|
||||||
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
|
mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, MainVideoPlayer.class, info, actionBarHandler.getSelectedVideoStream());
|
||||||
mIntent = NavigationHelper.getOpenPlayerIntent(activity, ExoPlayerActivity.class, info, actionBarHandler.getSelectedVideoStream());
|
|
||||||
if (info.start_position > 0) mIntent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
|
|
||||||
} else {
|
} else {
|
||||||
// Internal Player
|
// Internal Player
|
||||||
mIntent = new Intent(activity, PlayVideoActivity.class)
|
mIntent = new Intent(activity, PlayVideoActivity.class)
|
||||||
|
|
|
@ -4,584 +4,452 @@ import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaPlayer;
|
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.support.v7.app.NotificationCompat;
|
import android.support.annotation.IntRange;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Adam Howard on 08/11/15.
|
* Base players joining the common properties
|
||||||
* Copyright (c) Adam Howard <achdisposable1@gmail.com> 2015
|
|
||||||
*
|
*
|
||||||
* BackgroundPlayer.java is part of NewPipe.
|
* @author mauriciocolli
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
*/
|
||||||
|
public class BackgroundPlayer extends Service {
|
||||||
/**Plays the audio stream of videos in the background.*/
|
|
||||||
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
|
|
||||||
|
|
||||||
private static final String TAG = "BackgroundPlayer";
|
private static final String TAG = "BackgroundPlayer";
|
||||||
private static final String CLASSNAME = "org.schabi.newpipe.player.BackgroundPlayer";
|
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
private static final String ACTION_STOP = CLASSNAME + ".STOP";
|
|
||||||
private static final String ACTION_PLAYPAUSE = CLASSNAME + ".PLAYPAUSE";
|
|
||||||
private static final String ACTION_REWIND = CLASSNAME + ".REWIND";
|
|
||||||
private static final String ACTION_PLAYBACK_STATE = CLASSNAME + ".PLAYBACK_STATE";
|
|
||||||
private static final String EXTRA_PLAYBACK_STATE = CLASSNAME + ".extras.EXTRA_PLAYBACK_STATE";
|
|
||||||
|
|
||||||
// Extra intent arguments
|
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.BackgroundPlayer.CLOSE";
|
||||||
public static final String TITLE = "title";
|
public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.BackgroundPlayer.PLAY_PAUSE";
|
||||||
public static final String WEB_URL = "web_url";
|
public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.BackgroundPlayer.OPEN_DETAIL";
|
||||||
public static final String SERVICE_ID = "service_id";
|
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.BackgroundPlayer.REPEAT";
|
||||||
public static final String CHANNEL_NAME = "channel_name";
|
public static final String ACTION_FAST_REWIND = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_REWIND";
|
||||||
|
public static final String ACTION_FAST_FORWARD = "org.schabi.newpipe.player.BackgroundPlayer.ACTION_FAST_FORWARD";
|
||||||
|
|
||||||
private volatile String webUrl = "";
|
public static final String AUDIO_STREAM = "video_only_audio_stream";
|
||||||
private volatile int serviceId = -1;
|
private AudioStream audioStream;
|
||||||
private volatile String channelName = "";
|
|
||||||
|
|
||||||
// Determines if the service is already running.
|
private BasePlayerImpl basePlayerImpl;
|
||||||
// Prevents launching the service twice.
|
private PowerManager powerManager;
|
||||||
public static volatile boolean isRunning;
|
private WifiManager wifiManager;
|
||||||
|
|
||||||
public BackgroundPlayer() {
|
private PowerManager.WakeLock wakeLock;
|
||||||
super();
|
private WifiManager.WifiLock wifiLock;
|
||||||
}
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Notification
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
private static final int NOTIFICATION_ID = 123789;
|
||||||
|
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
private NotificationCompat.Builder notBuilder;
|
||||||
|
private RemoteViews notRemoteView;
|
||||||
|
private RemoteViews bigNotRemoteView;
|
||||||
|
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
|
||||||
|
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Service's LifeCycle
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
/*PendingIntent pi = PendingIntent.getActivity(this, 0,
|
if (DEBUG) Log.d(TAG, "onCreate() called");
|
||||||
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);*/
|
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||||
super.onCreate();
|
powerManager = ((PowerManager) getSystemService(POWER_SERVICE));
|
||||||
|
wifiManager = ((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE));
|
||||||
|
|
||||||
|
ThemeHelper.setTheme(this, false);
|
||||||
|
basePlayerImpl = new BasePlayerImpl(this);
|
||||||
|
basePlayerImpl.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
Toast.makeText(this, R.string.background_player_playing_toast,
|
if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
|
||||||
Toast.LENGTH_SHORT).show();
|
basePlayerImpl.handleIntent(intent);
|
||||||
|
|
||||||
String source = intent.getDataString();
|
|
||||||
//Log.i(TAG, "backgroundPLayer source:"+source);
|
|
||||||
String videoTitle = intent.getStringExtra(TITLE);
|
|
||||||
webUrl = intent.getStringExtra(WEB_URL);
|
|
||||||
serviceId = intent.getIntExtra(SERVICE_ID, -1);
|
|
||||||
channelName = intent.getStringExtra(CHANNEL_NAME);
|
|
||||||
|
|
||||||
//do nearly everything in a separate thread
|
|
||||||
PlayerThread player = new PlayerThread(source, videoTitle, this);
|
|
||||||
player.start();
|
|
||||||
|
|
||||||
isRunning = true;
|
|
||||||
|
|
||||||
// If we get killed after returning here, don't restart
|
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (DEBUG) Log.d(TAG, "destroy() called");
|
||||||
|
releaseWifiAndCpu();
|
||||||
|
stopForeground(true);
|
||||||
|
if (basePlayerImpl != null) basePlayerImpl.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
// We don't provide binding (yet?), so return null
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
public void onDestroy() {
|
// Actions
|
||||||
//Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
isRunning = 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onClose() {
|
||||||
private class PlayerThread extends Thread {
|
if (basePlayerImpl != null) basePlayerImpl.destroyPlayer();
|
||||||
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 PlayerThread(String src, String title, BackgroundPlayer owner) {
|
|
||||||
this.source = src;
|
|
||||||
this.title = title;
|
|
||||||
this.owner = owner;
|
|
||||||
mediaPlayer = new MediaPlayer();
|
|
||||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDonePlaying() {
|
|
||||||
return donePlaying;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 void broadcastState() {
|
|
||||||
PlaybackState state = getPlaybackState();
|
|
||||||
if(state == null) return;
|
|
||||||
Intent intent = new Intent(ACTION_PLAYBACK_STATE);
|
|
||||||
intent.putExtra(EXTRA_PLAYBACK_STATE, state);
|
|
||||||
sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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();
|
|
||||||
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Could not get video thumbnail from ActivityCommunicator");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
stopForeground(true);
|
||||||
try {
|
releaseWifiAndCpu();
|
||||||
// Wait for thread to stop
|
|
||||||
PlayerThread.this.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(TAG, "unable to join player thread", e);
|
|
||||||
}
|
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EndListener implements MediaPlayer.OnCompletionListener {
|
private void onScreenOnOff(boolean on) {
|
||||||
private WifiManager.WifiLock wl;
|
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||||
public EndListener(WifiManager.WifiLock wifiLock) {
|
if (on) {
|
||||||
this.wl = wifiLock;
|
if (basePlayerImpl.isPlaying() && !basePlayerImpl.isProgressLoopRunning.get()) basePlayerImpl.startProgressLoop();
|
||||||
|
} else basePlayerImpl.stopProgressLoop();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
public void onCompletion(MediaPlayer mp) {
|
// Notification
|
||||||
afterPlayCleanup();
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initNotificationBuilder() {
|
private NotificationCompat.Builder createNotification() {
|
||||||
Notification note;
|
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
|
||||||
Resources res = getApplicationContext().getResources();
|
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
|
||||||
|
|
||||||
/*
|
setupNotification(notRemoteView);
|
||||||
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
|
setupNotification(bigNotRemoteView);
|
||||||
(R.drawable.ic_pause_white, "Pause", playPI).build();
|
|
||||||
*/
|
|
||||||
|
|
||||||
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
||||||
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)
|
.setOngoing(true)
|
||||||
.setDeleteIntent(stopPI)
|
|
||||||
//doesn't fit with Notification.MediaStyle
|
|
||||||
//.setProgress(vidLength, 0, false)
|
|
||||||
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
|
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
|
||||||
.setContentIntent(PendingIntent.getActivity(getApplicationContext(),
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
noteID, openDetailViewIntent,
|
.setCustomContentView(notRemoteView)
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT))
|
.setCustomBigContentView(bigNotRemoteView);
|
||||||
.setContentIntent(openDetailView)
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) builder.setPriority(Notification.PRIORITY_MAX);
|
||||||
.setCategory(Notification.CATEGORY_TRANSPORT)
|
return builder;
|
||||||
//Make notification appear on lockscreen
|
|
||||||
.setVisibility(Notification.VISIBILITY_PUBLIC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification builder which works like the real builder but uses a custom view.
|
* 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
|
||||||
*/
|
*/
|
||||||
class NoteBuilder extends NotificationCompat.Builder {
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private void setControlsOpacity(@IntRange(from = 0, to = 255) int opacity) {
|
||||||
* @param context
|
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
|
||||||
* @inheritDoc
|
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
|
||||||
*/
|
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
|
||||||
public NoteBuilder(Context context, PendingIntent playPI, PendingIntent stopPI,
|
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
|
||||||
PendingIntent rewindPI, PendingIntent openDetailView) {
|
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
|
||||||
|
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void lockWifiAndCpu() {
|
||||||
|
if (DEBUG) Log.d(TAG, "lockWifiAndCpu() called");
|
||||||
|
if (wakeLock != null && wakeLock.isHeld() && wifiLock != null && wifiLock.isHeld()) return;
|
||||||
|
|
||||||
|
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||||
|
wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
|
||||||
|
|
||||||
|
if (wakeLock != null) wakeLock.acquire();
|
||||||
|
if (wifiLock != null) wifiLock.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseWifiAndCpu() {
|
||||||
|
if (DEBUG) Log.d(TAG, "releaseWifiAndCpu() called");
|
||||||
|
if (wakeLock != null && wakeLock.isHeld()) wakeLock.release();
|
||||||
|
if (wifiLock != null && wifiLock.isHeld()) wifiLock.release();
|
||||||
|
|
||||||
|
wakeLock = null;
|
||||||
|
wifiLock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private class BasePlayerImpl extends BasePlayer {
|
||||||
|
|
||||||
|
BasePlayerImpl(Context context) {
|
||||||
super(context);
|
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
|
@Override
|
||||||
public android.support.v4.app.NotificationCompat.Builder setProgress(int max, int progress, boolean indeterminate) {
|
public void handleIntent(Intent intent) {
|
||||||
super.setProgress(max, progress, indeterminate);
|
super.handleIntent(intent);
|
||||||
getBigContentView().setProgressBar(R.id.playbackProgress, max, progress, indeterminate);
|
Serializable serializable = intent.getSerializableExtra(BackgroundPlayer.AUDIO_STREAM);
|
||||||
return this;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Set the isPlaying state
|
public void initThumbnail() {
|
||||||
* @param isPlaying the is playing state
|
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
|
||||||
*/
|
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
|
||||||
public void setIsPlaying(boolean isPlaying) {
|
updateNotification(-1);
|
||||||
RemoteViews views = getContentView(), bigViews = getBigContentView();
|
super.initThumbnail();
|
||||||
int imageSrc;
|
}
|
||||||
if(isPlaying) {
|
|
||||||
imageSrc = R.drawable.ic_pause_white;
|
@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 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 {
|
} else {
|
||||||
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
|
PROGRESS_LOOP_INTERVAL = 2000;
|
||||||
|
FAST_FORWARD_REWIND_AMOUNT = 10000;
|
||||||
}
|
}
|
||||||
views.setImageViewResource(R.id.notificationPlayPause, imageSrc);
|
basePlayerImpl.getPlayer().setVolume(1f);
|
||||||
bigViews.setImageViewResource(R.id.notificationPlayPause, imageSrc);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the state of the player.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getDuration() {
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getPlayedTime() {
|
|
||||||
return played;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isPlaying() {
|
|
||||||
return booleanValues[INDEX_IS_PLAYING];
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isPrepared() {
|
|
||||||
return booleanValues[INDEX_IS_PREPARED];
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasErrors() {
|
|
||||||
return booleanValues[INDEX_HAS_ERROR];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeInt(duration);
|
|
||||||
dest.writeInt(played);
|
|
||||||
dest.writeBooleanArray(booleanValues);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public void onRepeatClicked() {
|
||||||
return 0;
|
super.onRepeatClicked();
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<PlaybackState> CREATOR = new Creator<PlaybackState>() {
|
int opacity = 255;
|
||||||
@Override
|
switch (currentRepeatMode) {
|
||||||
public PlaybackState createFromParcel(Parcel in) {
|
case REPEAT_DISABLED:
|
||||||
return new PlaybackState(in);
|
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
|
@Override
|
||||||
public PlaybackState[] newArray(int size) {
|
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||||
return new PlaybackState[size];
|
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 boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
PlaybackState that = (PlaybackState) o;
|
|
||||||
|
|
||||||
if (duration != that.duration) return false;
|
|
||||||
if (played != that.played) return false;
|
|
||||||
return Arrays.equals(booleanValues, that.booleanValues);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public void onFastRewind() {
|
||||||
if(this == UNPREPARED) return 1;
|
super.onFastRewind();
|
||||||
if(this == FAILED) return 2;
|
triggerProgressUpdate();
|
||||||
int result = duration;
|
}
|
||||||
result = 31 * result + played;
|
|
||||||
result = 31 * result + Arrays.hashCode(booleanValues);
|
@Override
|
||||||
return result + 2;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
717
app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
Normal file
717
app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
package org.schabi.newpipe.player;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
@ -23,22 +21,20 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PermissionHelper;
|
import org.schabi.newpipe.util.PermissionHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity Player implementing AbstractPlayer
|
* Activity Player implementing VideoPlayer
|
||||||
*
|
*
|
||||||
* @author mauriciocolli
|
* @author mauriciocolli
|
||||||
*/
|
*/
|
||||||
public class ExoPlayerActivity extends Activity {
|
public class MainVideoPlayer extends Activity {
|
||||||
private static final String TAG = ".ExoPlayerActivity";
|
private static final String TAG = ".MainVideoPlayer";
|
||||||
private static final boolean DEBUG = AbstractPlayer.DEBUG;
|
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
|
|
||||||
private AudioManager audioManager;
|
private AudioManager audioManager;
|
||||||
private BroadcastReceiver broadcastReceiver;
|
|
||||||
private GestureDetector gestureDetector;
|
private GestureDetector gestureDetector;
|
||||||
|
|
||||||
private final Runnable hideUiRunnable = new Runnable() {
|
private final Runnable hideUiRunnable = new Runnable() {
|
||||||
|
@ -49,7 +45,7 @@ public class ExoPlayerActivity extends Activity {
|
||||||
};
|
};
|
||||||
private boolean activityPaused;
|
private boolean activityPaused;
|
||||||
|
|
||||||
private AbstractPlayerImpl playerImpl;
|
private VideoPlayerImpl playerImpl;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Activity LifeCycle
|
// Activity LifeCycle
|
||||||
|
@ -72,9 +68,8 @@ public class ExoPlayerActivity extends Activity {
|
||||||
|
|
||||||
showSystemUi();
|
showSystemUi();
|
||||||
setContentView(R.layout.activity_exo_player);
|
setContentView(R.layout.activity_exo_player);
|
||||||
playerImpl = new AbstractPlayerImpl();
|
playerImpl = new VideoPlayerImpl();
|
||||||
playerImpl.setup(findViewById(android.R.id.content));
|
playerImpl.setup(findViewById(android.R.id.content));
|
||||||
initReceiver();
|
|
||||||
playerImpl.handleIntent(getIntent());
|
playerImpl.handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +92,10 @@ public class ExoPlayerActivity extends Activity {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
if (DEBUG) Log.d(TAG, "onStop() called");
|
if (DEBUG) Log.d(TAG, "onStop() called");
|
||||||
activityPaused = true;
|
activityPaused = true;
|
||||||
playerImpl.destroy();
|
if (playerImpl.getPlayer() != null) {
|
||||||
playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
|
playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
|
||||||
|
playerImpl.destroyPlayer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,9 +103,9 @@ public class ExoPlayerActivity extends Activity {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
if (DEBUG) Log.d(TAG, "onResume() called");
|
if (DEBUG) Log.d(TAG, "onResume() called");
|
||||||
if (activityPaused) {
|
if (activityPaused) {
|
||||||
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
|
|
||||||
playerImpl.initPlayer();
|
playerImpl.initPlayer();
|
||||||
playerImpl.playVideo(playerImpl.getSelectedVideoStream(), false);
|
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
|
||||||
|
playerImpl.play(false);
|
||||||
activityPaused = false;
|
activityPaused = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,29 +115,6 @@ public class ExoPlayerActivity extends Activity {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (DEBUG) Log.d(TAG, "onDestroy() called");
|
if (DEBUG) Log.d(TAG, "onDestroy() called");
|
||||||
if (playerImpl != null) playerImpl.destroy();
|
if (playerImpl != null) playerImpl.destroy();
|
||||||
if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Init
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private void initReceiver() {
|
|
||||||
if (DEBUG) Log.d(TAG, "initReceiver() called");
|
|
||||||
broadcastReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
|
|
||||||
switch (intent.getAction()) {
|
|
||||||
case AbstractPlayer.ACTION_UPDATE_THUMB:
|
|
||||||
playerImpl.onUpdateThumbnail(intent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
|
||||||
intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
|
|
||||||
registerReceiver(broadcastReceiver, intentFilter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -182,7 +156,7 @@ public class ExoPlayerActivity extends Activity {
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
private class AbstractPlayerImpl extends AbstractPlayer {
|
private class VideoPlayerImpl extends VideoPlayer {
|
||||||
private TextView titleTextView;
|
private TextView titleTextView;
|
||||||
private TextView channelTextView;
|
private TextView channelTextView;
|
||||||
private TextView volumeTextView;
|
private TextView volumeTextView;
|
||||||
|
@ -192,8 +166,8 @@ public class ExoPlayerActivity extends Activity {
|
||||||
private ImageButton screenRotationButton;
|
private ImageButton screenRotationButton;
|
||||||
private ImageButton playPauseButton;
|
private ImageButton playPauseButton;
|
||||||
|
|
||||||
AbstractPlayerImpl() {
|
VideoPlayerImpl() {
|
||||||
super("AbstractPlayerImpl" + ExoPlayerActivity.TAG, ExoPlayerActivity.this);
|
super("VideoPlayerImpl" + MainVideoPlayer.TAG, MainVideoPlayer.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -238,8 +212,8 @@ public class ExoPlayerActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void playVideo(VideoStream videoStream, boolean autoPlay) {
|
public void playUrl(String url, String format, boolean autoPlay) {
|
||||||
super.playVideo(videoStream, autoPlay);
|
super.playUrl(url, format, autoPlay);
|
||||||
playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white);
|
playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,16 +223,16 @@ public class ExoPlayerActivity extends Activity {
|
||||||
if (playerImpl.getPlayer() == null) return;
|
if (playerImpl.getPlayer() == null) return;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
&& !PermissionHelper.checkSystemAlertWindowPermission(ExoPlayerActivity.this)) {
|
&& !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) {
|
||||||
Toast.makeText(ExoPlayerActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
Toast.makeText(MainVideoPlayer.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerImpl != null) playerImpl.destroy();
|
context.startService(NavigationHelper.getOpenVideoPlayerIntent(context, PopupVideoPlayer.class, playerImpl));
|
||||||
context.startService(NavigationHelper.getOpenPlayerIntent(context, PopupVideoPlayer.class, playerImpl));
|
if (playerImpl != null) playerImpl.destroyPlayer();
|
||||||
|
|
||||||
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
|
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
|
||||||
ExoPlayerActivity.this.finish();
|
MainVideoPlayer.this.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -307,28 +281,6 @@ public class ExoPlayerActivity extends Activity {
|
||||||
toggleOrientation();
|
toggleOrientation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoPlayPause() {
|
|
||||||
super.onVideoPlayPause();
|
|
||||||
if (getPlayer().getPlayWhenReady()) {
|
|
||||||
animateView(playPauseButton, false, 80, 0, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
|
||||||
animateView(playPauseButton, true, 200, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
animateView(playPauseButton, false, 80, 0, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
|
||||||
animateView(playPauseButton, true, 200, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
super.onStopTrackingTouch(seekBar);
|
super.onStopTrackingTouch(seekBar);
|
||||||
|
@ -344,7 +296,8 @@ public class ExoPlayerActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError() {
|
public void onError(Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -366,10 +319,37 @@ public class ExoPlayerActivity extends Activity {
|
||||||
animateView(playPauseButton, false, 100, 0);
|
animateView(playPauseButton, false, 100, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaying() {
|
||||||
|
super.onPlaying();
|
||||||
|
//playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||||
|
//animateView(playPauseButton, true, 500, 0);
|
||||||
|
|
||||||
|
animateView(playPauseButton, false, 80, 0, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||||
|
animateView(playPauseButton, true, 200, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
showSystemUi();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPaused() {
|
public void onPaused() {
|
||||||
super.onPaused();
|
super.onPaused();
|
||||||
animateView(playPauseButton, true, 100, 0);
|
//playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
||||||
|
//animateView(playPauseButton, true, 100, 0);
|
||||||
|
|
||||||
|
animateView(playPauseButton, false, 80, 0, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
||||||
|
animateView(playPauseButton, true, 200, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
showSystemUi();
|
showSystemUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,12 +359,6 @@ public class ExoPlayerActivity extends Activity {
|
||||||
animateView(playPauseButton, false, 100, 0);
|
animateView(playPauseButton, false, 100, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaying() {
|
|
||||||
super.onPlaying();
|
|
||||||
animateView(playPauseButton, true, 500, 0);
|
|
||||||
showSystemUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCompleted() {
|
public void onCompleted() {
|
||||||
|
@ -467,14 +441,14 @@ public class ExoPlayerActivity extends Activity {
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||||
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
|
||||||
if (playerImpl.getCurrentState() != StateInterface.STATE_PLAYING) return true;
|
if (playerImpl.getCurrentState() != BasePlayer.STATE_PLAYING) return true;
|
||||||
|
|
||||||
if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true);
|
if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true);
|
||||||
else {
|
else {
|
||||||
playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
|
playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
|
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
showSystemUi();
|
showSystemUi();
|
||||||
|
@ -482,25 +456,25 @@ public class ExoPlayerActivity extends Activity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final float stepsBrightness = 21, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
|
private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
|
||||||
private float currentBrightness = .5f;
|
private float currentBrightness = .5f;
|
||||||
|
|
||||||
private int currentVolume, maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
private int currentVolume, maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
||||||
|
private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0;
|
||||||
|
|
||||||
private final String brightnessUnicode = new String(Character.toChars(0x2600));
|
private final String brightnessUnicode = new String(Character.toChars(0x2600));
|
||||||
// private final String volumeUnicode = new String(Character.toChars(0x1F50A));
|
|
||||||
private final String volumeUnicode = new String(Character.toChars(0x1F508));
|
private final String volumeUnicode = new String(Character.toChars(0x1F508));
|
||||||
|
|
||||||
|
|
||||||
private final int MOVEMENT_THRESHOLD = 40;
|
private final int MOVEMENT_THRESHOLD = 40;
|
||||||
private final int eventsThreshold = 3;
|
private final int eventsThreshold = 8;
|
||||||
private boolean triggered = false;
|
private boolean triggered = false;
|
||||||
private int eventsNum;
|
private int eventsNum;
|
||||||
|
|
||||||
|
// TODO: Improve video gesture controls
|
||||||
@Override
|
@Override
|
||||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||||
//noinspection PointlessBooleanExpression
|
//noinspection PointlessBooleanExpression
|
||||||
if (DEBUG && false) Log.d(TAG, "ExoPlayerActivity.onScroll = " +
|
if (DEBUG && true) Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||||
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
|
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
|
||||||
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
|
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
|
||||||
", distanceXy = [" + distanceX + ", " + distanceY + "]");
|
", distanceXy = [" + distanceX + ", " + distanceY + "]");
|
||||||
|
@ -510,16 +484,17 @@ public class ExoPlayerActivity extends Activity {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == StateInterface.STATE_COMPLETED) return false;
|
if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false;
|
||||||
isMoving = true;
|
isMoving = true;
|
||||||
// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
|
// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
|
||||||
boolean up = distanceY > 0;
|
boolean up = distanceY > 0;
|
||||||
|
|
||||||
|
|
||||||
if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
|
if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
|
||||||
currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + (up ? 1 : -1);
|
double floor = Math.floor(up ? stepVolume : -stepVolume);
|
||||||
|
currentVolume = (int) (audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + floor);
|
||||||
if (currentVolume >= maxVolume) currentVolume = maxVolume;
|
if (currentVolume >= maxVolume) currentVolume = maxVolume;
|
||||||
if (currentVolume <= 0) currentVolume = 0;
|
if (currentVolume <= minVolume) currentVolume = (int) minVolume;
|
||||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
|
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
|
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
|
||||||
|
@ -555,8 +530,8 @@ public class ExoPlayerActivity extends Activity {
|
||||||
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200);
|
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200);
|
||||||
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
|
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
|
||||||
|
|
||||||
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
|
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
|
||||||
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
|
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
@ -31,7 +30,6 @@ import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -39,7 +37,6 @@ import org.schabi.newpipe.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
@ -47,13 +44,13 @@ import org.schabi.newpipe.util.Utils;
|
||||||
import org.schabi.newpipe.workers.StreamExtractorWorker;
|
import org.schabi.newpipe.workers.StreamExtractorWorker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service Popup Player implementing AbstractPlayer
|
* Service Popup Player implementing VideoPlayer
|
||||||
*
|
*
|
||||||
* @author mauriciocolli
|
* @author mauriciocolli
|
||||||
*/
|
*/
|
||||||
public class PopupVideoPlayer extends Service {
|
public class PopupVideoPlayer extends Service {
|
||||||
private static final String TAG = ".PopupVideoPlayer";
|
private static final String TAG = ".PopupVideoPlayer";
|
||||||
private static final boolean DEBUG = AbstractPlayer.DEBUG;
|
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
|
|
||||||
private static final int NOTIFICATION_ID = 40028922;
|
private static final int NOTIFICATION_ID = 40028922;
|
||||||
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
|
public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
|
||||||
|
@ -61,8 +58,6 @@ public class PopupVideoPlayer extends Service {
|
||||||
public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
|
public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
|
||||||
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
|
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
|
||||||
|
|
||||||
private BroadcastReceiver broadcastReceiver;
|
|
||||||
|
|
||||||
private WindowManager windowManager;
|
private WindowManager windowManager;
|
||||||
private WindowManager.LayoutParams windowLayoutParams;
|
private WindowManager.LayoutParams windowLayoutParams;
|
||||||
private GestureDetector gestureDetector;
|
private GestureDetector gestureDetector;
|
||||||
|
@ -81,7 +76,7 @@ public class PopupVideoPlayer extends Service {
|
||||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||||
|
|
||||||
private AbstractPlayerImpl playerImpl;
|
private VideoPlayerImpl playerImpl;
|
||||||
private StreamExtractorWorker currentExtractorWorker;
|
private StreamExtractorWorker currentExtractorWorker;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -92,9 +87,8 @@ public class PopupVideoPlayer extends Service {
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||||
initReceiver();
|
|
||||||
|
|
||||||
playerImpl = new AbstractPlayerImpl();
|
playerImpl = new VideoPlayerImpl();
|
||||||
ThemeHelper.setTheme(this, false);
|
ThemeHelper.setTheme(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +126,6 @@ public class PopupVideoPlayer extends Service {
|
||||||
}
|
}
|
||||||
if (imageLoader != null) imageLoader.clearMemoryCache();
|
if (imageLoader != null) imageLoader.clearMemoryCache();
|
||||||
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
|
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
|
||||||
if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
|
|
||||||
if (currentExtractorWorker != null) {
|
if (currentExtractorWorker != null) {
|
||||||
currentExtractorWorker.cancel();
|
currentExtractorWorker.cancel();
|
||||||
currentExtractorWorker = null;
|
currentExtractorWorker = null;
|
||||||
|
@ -148,39 +141,6 @@ public class PopupVideoPlayer extends Service {
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private void initReceiver() {
|
|
||||||
broadcastReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
|
|
||||||
switch (intent.getAction()) {
|
|
||||||
case ACTION_CLOSE:
|
|
||||||
onVideoClose();
|
|
||||||
break;
|
|
||||||
case ACTION_PLAY_PAUSE:
|
|
||||||
playerImpl.onVideoPlayPause();
|
|
||||||
break;
|
|
||||||
case ACTION_OPEN_DETAIL:
|
|
||||||
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
|
|
||||||
break;
|
|
||||||
case ACTION_REPEAT:
|
|
||||||
playerImpl.onRepeatClicked();
|
|
||||||
break;
|
|
||||||
case AbstractPlayer.ACTION_UPDATE_THUMB:
|
|
||||||
playerImpl.onUpdateThumbnail(intent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
|
||||||
intentFilter.addAction(ACTION_CLOSE);
|
|
||||||
intentFilter.addAction(ACTION_PLAY_PAUSE);
|
|
||||||
intentFilter.addAction(ACTION_OPEN_DETAIL);
|
|
||||||
intentFilter.addAction(ACTION_REPEAT);
|
|
||||||
intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
|
|
||||||
registerReceiver(broadcastReceiver, intentFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RtlHardcoded")
|
@SuppressLint("RtlHardcoded")
|
||||||
private void initPopup() {
|
private void initPopup() {
|
||||||
if (DEBUG) Log.d(TAG, "initPopup() called");
|
if (DEBUG) Log.d(TAG, "initPopup() called");
|
||||||
|
@ -213,8 +173,9 @@ public class PopupVideoPlayer extends Service {
|
||||||
private NotificationCompat.Builder createNotification() {
|
private NotificationCompat.Builder createNotification() {
|
||||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
|
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
|
||||||
|
|
||||||
if (playerImpl.getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
|
if (playerImpl.getVideoThumbnail() == null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
|
||||||
else notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
|
else notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
|
||||||
|
|
||||||
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
|
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
|
||||||
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getChannelName());
|
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getChannelName());
|
||||||
|
|
||||||
|
@ -302,21 +263,35 @@ public class PopupVideoPlayer extends Service {
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private class AbstractPlayerImpl extends AbstractPlayer {
|
private class VideoPlayerImpl extends VideoPlayer {
|
||||||
AbstractPlayerImpl() {
|
VideoPlayerImpl() {
|
||||||
super("AbstractPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
|
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void playVideo(VideoStream videoStream, boolean autoPlay) {
|
public void playUrl(String url, String format, boolean autoPlay) {
|
||||||
super.playVideo(videoStream, autoPlay);
|
super.playUrl(url, format, autoPlay);
|
||||||
|
|
||||||
windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
|
windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
|
||||||
windowManager.updateViewLayout(getRootView(), windowLayoutParams);
|
windowManager.updateViewLayout(getRootView(), windowLayoutParams);
|
||||||
|
|
||||||
notBuilder = createNotification();
|
notBuilder = createNotification();
|
||||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||||
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
super.destroy();
|
||||||
|
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onThumbnailReceived(Bitmap thumbnail) {
|
||||||
|
super.onThumbnailReceived(thumbnail);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, thumbnail);
|
||||||
|
updateNotification(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -324,8 +299,8 @@ public class PopupVideoPlayer extends Service {
|
||||||
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||||
Intent intent;
|
Intent intent;
|
||||||
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
|
if (!getSharedPreferences().getBoolean(getResources().getString(R.string.use_old_player_key), false)) {
|
||||||
intent = NavigationHelper.getOpenPlayerIntent(context, ExoPlayerActivity.class, playerImpl);
|
intent = NavigationHelper.getOpenVideoPlayerIntent(context, MainVideoPlayer.class, playerImpl);
|
||||||
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(AbstractPlayer.STARTED_FROM_NEWPIPE, false);
|
if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
} else {
|
} else {
|
||||||
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
|
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
|
||||||
|
@ -335,9 +310,9 @@ public class PopupVideoPlayer extends Service {
|
||||||
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
|
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
}
|
}
|
||||||
stopSelf();
|
|
||||||
if (playerImpl != null) playerImpl.destroy();
|
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
|
if (playerImpl != null) playerImpl.destroyPlayer();
|
||||||
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -360,13 +335,6 @@ public class PopupVideoPlayer extends Service {
|
||||||
updateNotification(-1);
|
updateNotification(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpdateThumbnail(Intent intent) {
|
|
||||||
super.onUpdateThumbnail(intent);
|
|
||||||
if (getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, getVideoThumbnail());
|
|
||||||
updateNotification(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(PopupMenu menu) {
|
public void onDismiss(PopupMenu menu) {
|
||||||
super.onDismiss(menu);
|
super.onDismiss(menu);
|
||||||
|
@ -374,11 +342,45 @@ public class PopupVideoPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError() {
|
public void onError(Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Broadcast Receiver
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupBroadcastReceiver(IntentFilter intentFilter) {
|
||||||
|
super.setupBroadcastReceiver(intentFilter);
|
||||||
|
if (DEBUG) Log.d(TAG, "setupBroadcastReceiver() called with: intentFilter = [" + intentFilter + "]");
|
||||||
|
intentFilter.addAction(ACTION_CLOSE);
|
||||||
|
intentFilter.addAction(ACTION_PLAY_PAUSE);
|
||||||
|
intentFilter.addAction(ACTION_OPEN_DETAIL);
|
||||||
|
intentFilter.addAction(ACTION_REPEAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBroadcastReceived(Intent intent) {
|
||||||
|
super.onBroadcastReceived(intent);
|
||||||
|
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case ACTION_CLOSE:
|
||||||
|
onVideoClose();
|
||||||
|
break;
|
||||||
|
case ACTION_PLAY_PAUSE:
|
||||||
|
playerImpl.onVideoPlayPause();
|
||||||
|
break;
|
||||||
|
case ACTION_OPEN_DETAIL:
|
||||||
|
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
|
||||||
|
break;
|
||||||
|
case ACTION_REPEAT:
|
||||||
|
playerImpl.onRepeatClicked();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// States
|
// States
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -483,8 +485,8 @@ public class PopupVideoPlayer extends Service {
|
||||||
|
|
||||||
private void onScrollEnd() {
|
private void onScrollEnd() {
|
||||||
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
|
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
|
||||||
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
|
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
|
||||||
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
|
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,6 +518,7 @@ public class PopupVideoPlayer extends Service {
|
||||||
public void onReceive(StreamInfo info) {
|
public void onReceive(StreamInfo info) {
|
||||||
playerImpl.setVideoTitle(info.title);
|
playerImpl.setVideoTitle(info.title);
|
||||||
playerImpl.setVideoUrl(info.webpage_url);
|
playerImpl.setVideoUrl(info.webpage_url);
|
||||||
|
playerImpl.setVideoThumbnailUrl(info.thumbnail_url);
|
||||||
playerImpl.setChannelName(info.uploader);
|
playerImpl.setChannelName(info.uploader);
|
||||||
|
|
||||||
playerImpl.setVideoStreamsList(Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false));
|
playerImpl.setVideoStreamsList(Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false));
|
||||||
|
@ -537,7 +540,7 @@ public class PopupVideoPlayer extends Service {
|
||||||
mainHandler.post(new Runnable() {
|
mainHandler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
playerImpl.playVideo(playerImpl.getSelectedVideoStream(), true);
|
playerImpl.play(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -551,7 +554,6 @@ public class PopupVideoPlayer extends Service {
|
||||||
playerImpl.setVideoThumbnail(loadedImage);
|
playerImpl.setVideoThumbnail(loadedImage);
|
||||||
if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
|
if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
|
||||||
updateNotification(-1);
|
updateNotification(-1);
|
||||||
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = loadedImage;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -619,6 +621,7 @@ public class PopupVideoPlayer extends Service {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnrecoverableError(Exception exception) {
|
public void onUnrecoverableError(Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -7,16 +7,12 @@ import android.animation.PropertyValuesHolder;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
@ -29,84 +25,42 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
|
||||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
|
||||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
|
|
||||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Formatter;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common properties of the players
|
* Base for <b>video</b> players
|
||||||
*
|
*
|
||||||
* @author mauriciocolli
|
* @author mauriciocolli
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener, SimpleExoPlayer.VideoListener {
|
public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.VideoListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
|
||||||
public static final boolean DEBUG = false;
|
public static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
public final String TAG;
|
public final String TAG;
|
||||||
|
|
||||||
protected Context context;
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
private static int currentState = -1;
|
|
||||||
public static final String ACTION_UPDATE_THUMB = "org.schabi.newpipe.player.AbstractPlayer.UPDATE_THUMBNAIL";
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Intent
|
// Intent
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public static final String VIDEO_URL = "video_url";
|
|
||||||
public static final String VIDEO_STREAMS_LIST = "video_streams_list";
|
public static final String VIDEO_STREAMS_LIST = "video_streams_list";
|
||||||
public static final String VIDEO_ONLY_AUDIO_STREAM = "video_only_audio_stream";
|
public static final String VIDEO_ONLY_AUDIO_STREAM = "video_only_audio_stream";
|
||||||
public static final String VIDEO_TITLE = "video_title";
|
|
||||||
public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
|
public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
|
||||||
public static final String START_POSITION = "start_position";
|
|
||||||
public static final String CHANNEL_NAME = "channel_name";
|
|
||||||
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
|
public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
|
||||||
|
|
||||||
private String videoUrl = "";
|
|
||||||
private int videoStartPos = -1;
|
|
||||||
private String videoTitle = "";
|
|
||||||
private Bitmap videoThumbnail;
|
|
||||||
private String channelName = "";
|
|
||||||
private int selectedIndexStream;
|
private int selectedIndexStream;
|
||||||
private ArrayList<VideoStream> videoStreamsList = new ArrayList<>();
|
private ArrayList<VideoStream> videoStreamsList = new ArrayList<>();
|
||||||
private AudioStream videoOnlyAudioStream;
|
private AudioStream videoOnlyAudioStream;
|
||||||
|
@ -115,36 +69,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
// Player
|
// Player
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public static final int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
|
|
||||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
|
public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
|
||||||
public static final String CACHE_FOLDER_NAME = "exoplayer";
|
|
||||||
|
|
||||||
private boolean startedFromNewPipe = true;
|
private boolean startedFromNewPipe = true;
|
||||||
private boolean isPrepared = false;
|
|
||||||
private boolean wasPlaying = false;
|
private boolean wasPlaying = false;
|
||||||
private SimpleExoPlayer simpleExoPlayer;
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
|
||||||
private MediaSource videoSource;
|
|
||||||
private static CacheDataSourceFactory cacheDataSourceFactory;
|
|
||||||
private static final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
|
|
||||||
private static final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
|
||||||
|
|
||||||
private AtomicBoolean isProgressLoopRunning = new AtomicBoolean();
|
|
||||||
private Handler progressLoop;
|
|
||||||
private Runnable progressUpdate;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Repeat
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private RepeatMode currentRepeatMode = RepeatMode.REPEAT_DISABLED;
|
|
||||||
|
|
||||||
public enum RepeatMode {
|
|
||||||
REPEAT_DISABLED,
|
|
||||||
REPEAT_ONE,
|
|
||||||
REPEAT_ALL
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
|
@ -181,35 +109,15 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public AbstractPlayer(String debugTag, Context context) {
|
public VideoPlayer(String debugTag, Context context) {
|
||||||
|
super(context);
|
||||||
this.TAG = debugTag;
|
this.TAG = debugTag;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.progressLoop = new Handler();
|
|
||||||
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
|
|
||||||
if (cacheDataSourceFactory == null) {
|
|
||||||
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getPackageName()), bandwidthMeter);
|
|
||||||
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
|
|
||||||
if (!cacheDir.exists()) {
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
cacheDir.mkdir();
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "buildMediaSource: cacheDir = " + cacheDir.getAbsolutePath());
|
|
||||||
SimpleCache simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(64 * 1024 * 1024L));
|
|
||||||
cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE, 512 * 1024);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setup(View rootView) {
|
public void setup(View rootView) {
|
||||||
initViews(rootView);
|
initViews(rootView);
|
||||||
initListeners();
|
setup();
|
||||||
if (simpleExoPlayer == null) initPlayer();
|
|
||||||
else {
|
|
||||||
simpleExoPlayer.addListener(this);
|
|
||||||
simpleExoPlayer.setVideoListener(this);
|
|
||||||
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initViews(View rootView) {
|
public void initViews(View rootView) {
|
||||||
|
@ -241,36 +149,24 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initListeners() {
|
|
||||||
progressUpdate = new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void initListeners() {
|
||||||
//if(DEBUG) Log.d(TAG, "progressUpdate run() called");
|
super.initListeners();
|
||||||
onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
|
|
||||||
if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
playbackSeekBar.setOnSeekBarChangeListener(this);
|
playbackSeekBar.setOnSeekBarChangeListener(this);
|
||||||
fullScreenButton.setOnClickListener(this);
|
fullScreenButton.setOnClickListener(this);
|
||||||
qualityTextView.setOnClickListener(this);
|
qualityTextView.setOnClickListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void initPlayer() {
|
public void initPlayer() {
|
||||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
super.initPlayer();
|
||||||
|
|
||||||
AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
|
||||||
DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
|
|
||||||
DefaultLoadControl loadControl = new DefaultLoadControl();
|
|
||||||
|
|
||||||
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, defaultTrackSelector, loadControl);
|
|
||||||
simpleExoPlayer.addListener(this);
|
|
||||||
simpleExoPlayer.setVideoListener(this);
|
|
||||||
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
||||||
|
simpleExoPlayer.setVideoListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void handleIntent(Intent intent) {
|
public void handleIntent(Intent intent) {
|
||||||
|
super.handleIntent(intent);
|
||||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||||
if (intent == null) return;
|
if (intent == null) return;
|
||||||
|
|
||||||
|
@ -284,79 +180,36 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
Serializable audioStream = intent.getSerializableExtra(VIDEO_ONLY_AUDIO_STREAM);
|
Serializable audioStream = intent.getSerializableExtra(VIDEO_ONLY_AUDIO_STREAM);
|
||||||
if (audioStream != null) videoOnlyAudioStream = (AudioStream) audioStream;
|
if (audioStream != null) videoOnlyAudioStream = (AudioStream) audioStream;
|
||||||
|
|
||||||
videoUrl = intent.getStringExtra(VIDEO_URL);
|
|
||||||
videoTitle = intent.getStringExtra(VIDEO_TITLE);
|
|
||||||
videoStartPos = intent.getIntExtra(START_POSITION, -1);
|
|
||||||
channelName = intent.getStringExtra(CHANNEL_NAME);
|
|
||||||
startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true);
|
startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true);
|
||||||
try {
|
play(true);
|
||||||
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
playVideo(getSelectedVideoStream(), true);
|
|
||||||
|
public void play(boolean autoPlay) {
|
||||||
|
playUrl(getSelectedVideoStream().url, MediaFormat.getSuffixById(getSelectedVideoStream().format), autoPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playVideo(VideoStream videoStream, boolean autoPlay) {
|
@Override
|
||||||
if (DEBUG) {
|
public void playUrl(String url, String format, boolean autoPlay) {
|
||||||
Log.d(TAG, "playVideo() called with: videoStream = [" + videoStream + ", " + videoStream.url + ", isVideoOnly = " + videoStream.isVideoOnly + "], autoPlay = [" + autoPlay + "]");
|
if (DEBUG) Log.d(TAG, "play() called with: url = [" + url + "], autoPlay = [" + autoPlay + "]");
|
||||||
}
|
|
||||||
|
|
||||||
if (videoStream == null || videoStream.url == null || simpleExoPlayer == null) {
|
|
||||||
onError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isPrepared = false;
|
|
||||||
qualityChanged = false;
|
qualityChanged = false;
|
||||||
|
|
||||||
|
if (url == null || simpleExoPlayer == null) {
|
||||||
|
RuntimeException runtimeException = new RuntimeException((url == null ? "Url " : "Player ") + " null");
|
||||||
|
onError(runtimeException);
|
||||||
|
throw runtimeException;
|
||||||
|
}
|
||||||
|
|
||||||
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
|
||||||
buildQualityMenu(qualityPopupMenu);
|
buildQualityMenu(qualityPopupMenu);
|
||||||
|
|
||||||
videoSource = buildMediaSource(videoStream, MediaFormat.getSuffixById(getSelectedVideoStream().format));
|
super.playUrl(url, format, autoPlay);
|
||||||
|
|
||||||
if (simpleExoPlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) simpleExoPlayer.stop();
|
|
||||||
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
|
|
||||||
simpleExoPlayer.prepare(videoSource);
|
|
||||||
simpleExoPlayer.setPlayWhenReady(autoPlay);
|
|
||||||
changeState(STATE_LOADING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy() {
|
@Override
|
||||||
if (DEBUG) Log.d(TAG, "destroy() called");
|
public MediaSource buildMediaSource(String url, String overrideExtension) {
|
||||||
if (simpleExoPlayer != null) {
|
MediaSource mediaSource = super.buildMediaSource(url, overrideExtension);
|
||||||
simpleExoPlayer.stop();
|
if (!getSelectedVideoStream().isVideoOnly) return mediaSource;
|
||||||
simpleExoPlayer.release();
|
|
||||||
}
|
|
||||||
if (progressLoop != null) stopProgressLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaSource buildMediaSource(VideoStream videoStream, String overrideExtension) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "buildMediaSource() called with: videoStream = [" + videoStream + ", " + videoStream.url + "isVideoOnly = " + videoStream.isVideoOnly + "], overrideExtension = [" + overrideExtension + "]");
|
|
||||||
}
|
|
||||||
Uri uri = Uri.parse(videoStream.url);
|
|
||||||
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
|
||||||
MediaSource mediaSource;
|
|
||||||
switch (type) {
|
|
||||||
case C.TYPE_SS:
|
|
||||||
mediaSource = new SsMediaSource(uri, cacheDataSourceFactory, new DefaultSsChunkSource.Factory(cacheDataSourceFactory), null, null);
|
|
||||||
break;
|
|
||||||
case C.TYPE_DASH:
|
|
||||||
mediaSource = new DashMediaSource(uri, cacheDataSourceFactory, new DefaultDashChunkSource.Factory(cacheDataSourceFactory), null, null);
|
|
||||||
break;
|
|
||||||
case C.TYPE_HLS:
|
|
||||||
mediaSource = new HlsMediaSource(uri, cacheDataSourceFactory, null, null);
|
|
||||||
break;
|
|
||||||
case C.TYPE_OTHER:
|
|
||||||
mediaSource = new ExtractorMediaSource(uri, cacheDataSourceFactory, extractorsFactory, null, null);
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
throw new IllegalStateException("Unsupported type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!videoStream.isVideoOnly) return mediaSource;
|
|
||||||
|
|
||||||
Uri audioUri = Uri.parse(videoOnlyAudioStream.url);
|
Uri audioUri = Uri.parse(videoOnlyAudioStream.url);
|
||||||
return new MergingMediaSource(mediaSource, new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null));
|
return new MergingMediaSource(mediaSource, new ExtractorMediaSource(audioUri, cacheDataSourceFactory, extractorsFactory, null, null));
|
||||||
|
@ -370,39 +223,12 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
qualityTextView.setText(getSelectedVideoStream().resolution);
|
qualityTextView.setText(getSelectedVideoStream().resolution);
|
||||||
popupMenu.setOnMenuItemClickListener(this);
|
popupMenu.setOnMenuItemClickListener(this);
|
||||||
popupMenu.setOnDismissListener(this);
|
popupMenu.setOnDismissListener(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// States Implementation
|
// States Implementation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changeState(int state) {
|
|
||||||
if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
|
|
||||||
currentState = state;
|
|
||||||
switch (state) {
|
|
||||||
case STATE_LOADING:
|
|
||||||
onLoading();
|
|
||||||
break;
|
|
||||||
case STATE_PLAYING:
|
|
||||||
onPlaying();
|
|
||||||
break;
|
|
||||||
case STATE_BUFFERING:
|
|
||||||
onBuffering();
|
|
||||||
break;
|
|
||||||
case STATE_PAUSED:
|
|
||||||
onPaused();
|
|
||||||
break;
|
|
||||||
case STATE_PAUSED_SEEK:
|
|
||||||
onPausedSeek();
|
|
||||||
break;
|
|
||||||
case STATE_COMPLETED:
|
|
||||||
onCompleted();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoading() {
|
public void onLoading() {
|
||||||
if (DEBUG) Log.d(TAG, "onLoading() called");
|
if (DEBUG) Log.d(TAG, "onLoading() called");
|
||||||
|
@ -463,7 +289,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
|
|
||||||
if (isProgressLoopRunning.get()) stopProgressLoop();
|
if (isProgressLoopRunning.get()) stopProgressLoop();
|
||||||
|
|
||||||
if (videoThumbnail != null) endScreen.setImageBitmap(videoThumbnail);
|
|
||||||
animateView(controlsRoot, true, 500, 0);
|
animateView(controlsRoot, true, 500, 0);
|
||||||
animateView(endScreen, true, 800, 0);
|
animateView(endScreen, true, 800, 0);
|
||||||
animateView(currentDisplaySeek, false, 200, 0);
|
animateView(currentDisplaySeek, false, 200, 0);
|
||||||
|
@ -481,74 +306,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
|
|
||||||
if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
|
if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
|
||||||
changeState(STATE_LOADING);
|
changeState(STATE_LOADING);
|
||||||
getPlayer().seekTo(0);
|
simpleExoPlayer.seekTo(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// ExoPlayer Listener
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingChanged(boolean isLoading) {
|
|
||||||
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
|
|
||||||
|
|
||||||
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop();
|
|
||||||
else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
|
||||||
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
|
|
||||||
if (getCurrentState() == STATE_PAUSED_SEEK) {
|
|
||||||
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (playbackState) {
|
|
||||||
case ExoPlayer.STATE_IDLE: // 1
|
|
||||||
isPrepared = false;
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_BUFFERING: // 2
|
|
||||||
if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING);
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_READY: //3
|
|
||||||
if (!isPrepared) {
|
|
||||||
isPrepared = true;
|
|
||||||
onPrepared(playWhenReady);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (currentState == STATE_PAUSED_SEEK) break;
|
|
||||||
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_ENDED: // 4
|
|
||||||
changeState(STATE_COMPLETED);
|
|
||||||
isPrepared = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
|
||||||
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
|
|
||||||
onError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPositionDiscontinuity() {
|
|
||||||
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// ExoPlayer Video Listener
|
// ExoPlayer Video Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -570,8 +331,7 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
// General Player
|
// General Player
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public abstract void onError();
|
@Override
|
||||||
|
|
||||||
public void onPrepared(boolean playWhenReady) {
|
public void onPrepared(boolean playWhenReady) {
|
||||||
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
|
||||||
|
|
||||||
|
@ -584,11 +344,19 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
|
||||||
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
|
||||||
|
|
||||||
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
|
super.onPrepared(playWhenReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
super.destroy();
|
||||||
|
if (endScreen != null) endScreen.setImageBitmap(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
|
||||||
if (!isPrepared) return;
|
if (!isPrepared) return;
|
||||||
|
|
||||||
if (currentState != STATE_PAUSED) {
|
if (currentState != STATE_PAUSED) {
|
||||||
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
|
if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
|
||||||
playbackCurrentTime.setText(getTimeString(currentProgress));
|
playbackCurrentTime.setText(getTimeString(currentProgress));
|
||||||
|
@ -601,32 +369,32 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUpdateThumbnail(Intent intent) {
|
@Override
|
||||||
if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called with: intent = [" + intent + "]");
|
public void onVideoPlayPauseRepeat() {
|
||||||
if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return;
|
if (DEBUG) Log.d(TAG, "onVideoPlayPauseRepeat() called");
|
||||||
videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
|
if (qualityChanged) {
|
||||||
|
setVideoStartPos(0);
|
||||||
|
play(true);
|
||||||
|
} else super.onVideoPlayPauseRepeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onVideoPlayPause() {
|
@Override
|
||||||
if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
|
public void onThumbnailReceived(Bitmap thumbnail) {
|
||||||
if (currentState == STATE_COMPLETED) {
|
super.onThumbnailReceived(thumbnail);
|
||||||
changeState(STATE_LOADING);
|
if (thumbnail != null) endScreen.setImageBitmap(thumbnail);
|
||||||
if (qualityChanged) playVideo(getSelectedVideoStream(), true);
|
|
||||||
simpleExoPlayer.seekTo(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
simpleExoPlayer.setPlayWhenReady(!isPlaying());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract void onFullScreenButtonClicked();
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onFastRewind() {
|
public void onFastRewind() {
|
||||||
if (DEBUG) Log.d(TAG, "onFastRewind() called");
|
super.onFastRewind();
|
||||||
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
|
|
||||||
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
|
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onFastForward() {
|
public void onFastForward() {
|
||||||
if (DEBUG) Log.d(TAG, "onFastForward() called");
|
super.onFastForward();
|
||||||
seekBy(FAST_FORWARD_REWIND_AMOUNT);
|
|
||||||
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
|
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,10 +419,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||||
if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
|
||||||
if (selectedIndexStream == menuItem.getItemId()) return true;
|
if (selectedIndexStream == menuItem.getItemId()) return true;
|
||||||
setVideoStartPos((int) getPlayer().getCurrentPosition());
|
setVideoStartPos((int) simpleExoPlayer.getCurrentPosition());
|
||||||
|
|
||||||
selectedIndexStream = menuItem.getItemId();
|
selectedIndexStream = menuItem.getItemId();
|
||||||
if (!(getCurrentState() == STATE_COMPLETED)) playVideo(getSelectedVideoStream(), wasPlaying);
|
if (!(getCurrentState() == STATE_COMPLETED)) play(wasPlaying);
|
||||||
else qualityChanged = true;
|
else qualityChanged = true;
|
||||||
|
|
||||||
qualityTextView.setText(menuItem.getTitle());
|
qualityTextView.setText(menuItem.getTitle());
|
||||||
|
@ -671,8 +439,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
qualityTextView.setText(getSelectedVideoStream().resolution);
|
qualityTextView.setText(getSelectedVideoStream().resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onFullScreenButtonClicked();
|
|
||||||
|
|
||||||
public void onQualitySelectorClicked() {
|
public void onQualitySelectorClicked() {
|
||||||
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
|
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
|
||||||
qualityPopupMenu.show();
|
qualityPopupMenu.show();
|
||||||
|
@ -684,18 +450,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
wasPlaying = isPlaying();
|
wasPlaying = isPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRepeatClicked() {
|
|
||||||
if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
|
|
||||||
// TODO: implement repeat all when playlist is implemented
|
|
||||||
|
|
||||||
// Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
|
|
||||||
setCurrentRepeatMode(getCurrentRepeatMode() == RepeatMode.REPEAT_DISABLED ?
|
|
||||||
RepeatMode.REPEAT_ONE :
|
|
||||||
RepeatMode.REPEAT_DISABLED);
|
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getCurrentRepeatMode().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// SeekBar Listener
|
// SeekBar Listener
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -737,21 +491,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
private static final StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
private static final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
|
|
||||||
|
|
||||||
public String getTimeString(int milliSeconds) {
|
|
||||||
long seconds = (milliSeconds % 60000L) / 1000L;
|
|
||||||
long minutes = (milliSeconds % 3600000L) / 60000L;
|
|
||||||
long hours = (milliSeconds % 86400000L) / 3600000L;
|
|
||||||
long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
|
|
||||||
|
|
||||||
stringBuilder.setLength(0);
|
|
||||||
return days > 0 ? formatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
|
|
||||||
: hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
|
|
||||||
: formatter.format("%02d:%02d", minutes, seconds).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isControlsVisible() {
|
public boolean isControlsVisible() {
|
||||||
return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
|
return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
|
||||||
}
|
}
|
||||||
|
@ -908,62 +647,14 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekBy(int milliSeconds) {
|
|
||||||
if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
|
|
||||||
if (simpleExoPlayer == null) return;
|
|
||||||
int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds);
|
|
||||||
simpleExoPlayer.seekTo(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPlaying() {
|
|
||||||
return simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY && simpleExoPlayer.getPlayWhenReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isQualityMenuVisible() {
|
public boolean isQualityMenuVisible() {
|
||||||
return isQualityPopupMenuVisible;
|
return isQualityPopupMenuVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startProgressLoop() {
|
|
||||||
progressLoop.removeCallbacksAndMessages(null);
|
|
||||||
isProgressLoopRunning.set(true);
|
|
||||||
progressLoop.post(progressUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopProgressLoop() {
|
|
||||||
isProgressLoopRunning.set(false);
|
|
||||||
progressLoop.removeCallbacksAndMessages(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryDeleteCacheFiles(Context context) {
|
|
||||||
File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
|
|
||||||
|
|
||||||
if (cacheDir.exists()) {
|
|
||||||
try {
|
|
||||||
if (cacheDir.isDirectory()) {
|
|
||||||
for (File file : cacheDir.listFiles()) {
|
|
||||||
try {
|
|
||||||
if (DEBUG) Log.d(TAG, "tryDeleteCacheFiles: " + file.getAbsolutePath() + " deleted = " + file.delete());
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public SimpleExoPlayer getPlayer() {
|
|
||||||
return simpleExoPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SharedPreferences getSharedPreferences() {
|
|
||||||
return sharedPreferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
||||||
return aspectRatioFrameLayout;
|
return aspectRatioFrameLayout;
|
||||||
}
|
}
|
||||||
|
@ -972,22 +663,10 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
return surfaceView;
|
return surfaceView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RepeatMode getCurrentRepeatMode() {
|
|
||||||
return currentRepeatMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentRepeatMode(RepeatMode mode) {
|
|
||||||
currentRepeatMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasPlaying() {
|
public boolean wasPlaying() {
|
||||||
return wasPlaying;
|
return wasPlaying;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCurrentState() {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VideoStream getSelectedVideoStream() {
|
public VideoStream getSelectedVideoStream() {
|
||||||
return videoStreamsList.get(selectedIndexStream);
|
return videoStreamsList.get(selectedIndexStream);
|
||||||
}
|
}
|
||||||
|
@ -1000,46 +679,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
return qualityPopupMenuGroupId;
|
return qualityPopupMenuGroupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVideoUrl() {
|
|
||||||
return videoUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVideoUrl(String videoUrl) {
|
|
||||||
this.videoUrl = videoUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVideoStartPos() {
|
|
||||||
return videoStartPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVideoStartPos(int videoStartPos) {
|
|
||||||
this.videoStartPos = videoStartPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVideoTitle() {
|
|
||||||
return videoTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVideoTitle(String videoTitle) {
|
|
||||||
this.videoTitle = videoTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bitmap getVideoThumbnail() {
|
|
||||||
return videoThumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVideoThumbnail(Bitmap videoThumbnail) {
|
|
||||||
this.videoThumbnail = videoThumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChannelName() {
|
|
||||||
return channelName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChannelName(String channelName) {
|
|
||||||
this.channelName = channelName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSelectedStreamIndex() {
|
public int getSelectedStreamIndex() {
|
||||||
return selectedIndexStream;
|
return selectedIndexStream;
|
||||||
}
|
}
|
||||||
|
@ -1135,4 +774,5 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
|
||||||
public TextView getCurrentDisplaySeek() {
|
public TextView getCurrentDisplaySeek() {
|
||||||
return currentDisplaySeek;
|
return currentDisplaySeek;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,33 +8,57 @@ import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
import org.schabi.newpipe.fragments.OnItemSelectedListener;
|
import org.schabi.newpipe.fragments.OnItemSelectedListener;
|
||||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||||
import org.schabi.newpipe.player.AbstractPlayer;
|
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||||
|
import org.schabi.newpipe.player.BasePlayer;
|
||||||
|
import org.schabi.newpipe.player.VideoPlayer;
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
public class NavigationHelper {
|
public class NavigationHelper {
|
||||||
|
|
||||||
public static Intent getOpenPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
|
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
|
||||||
return new Intent(context, targetClazz)
|
Intent mIntent = new Intent(context, targetClazz)
|
||||||
.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
|
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
|
||||||
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
|
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
|
||||||
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
|
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
|
||||||
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
|
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||||
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false))
|
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamIndex)
|
||||||
.putExtra(AbstractPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams));
|
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, Utils.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false))
|
||||||
|
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, Utils.getHighestQualityAudio(info.audio_streams));
|
||||||
|
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
|
||||||
|
return mIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent getOpenPlayerIntent(Context context, Class targetClazz, AbstractPlayer instance) {
|
|
||||||
|
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, VideoPlayer instance) {
|
||||||
return new Intent(context, targetClazz)
|
return new Intent(context, targetClazz)
|
||||||
.putExtra(AbstractPlayer.VIDEO_TITLE, instance.getVideoTitle())
|
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
|
||||||
.putExtra(AbstractPlayer.VIDEO_URL, instance.getVideoUrl())
|
.putExtra(BasePlayer.VIDEO_URL, instance.getVideoUrl())
|
||||||
.putExtra(AbstractPlayer.CHANNEL_NAME, instance.getChannelName())
|
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, instance.getVideoThumbnailUrl())
|
||||||
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
|
.putExtra(BasePlayer.CHANNEL_NAME, instance.getChannelName())
|
||||||
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
|
.putExtra(VideoPlayer.INDEX_SEL_VIDEO_STREAM, instance.getSelectedStreamIndex())
|
||||||
.putExtra(AbstractPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
|
.putExtra(VideoPlayer.VIDEO_STREAMS_LIST, instance.getVideoStreamsList())
|
||||||
.putExtra(AbstractPlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()));
|
.putExtra(VideoPlayer.VIDEO_ONLY_AUDIO_STREAM, instance.getAudioStream())
|
||||||
|
.putExtra(BasePlayer.START_POSITION, ((int) instance.getPlayer().getCurrentPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info) {
|
||||||
|
return getOpenBackgroundPlayerIntent(context, info, info.audio_streams.get(Utils.getPreferredAudioFormat(context, info.audio_streams)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Intent getOpenBackgroundPlayerIntent(Context context, StreamInfo info, AudioStream audioStream) {
|
||||||
|
Intent mIntent = new Intent(context, BackgroundPlayer.class)
|
||||||
|
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
|
||||||
|
.putExtra(BasePlayer.VIDEO_URL, info.webpage_url)
|
||||||
|
.putExtra(BasePlayer.VIDEO_THUMBNAIL_URL, info.thumbnail_url)
|
||||||
|
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||||
|
.putExtra(BasePlayer.CHANNEL_NAME, info.uploader)
|
||||||
|
.putExtra(BackgroundPlayer.AUDIO_STREAM, audioStream);
|
||||||
|
if (info.start_position > 0) mIntent.putExtra(BasePlayer.START_POSITION, info.start_position * 1000);
|
||||||
|
return mIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -136,13 +136,19 @@ public class Utils {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int highestQualityIndex = 0;
|
||||||
|
|
||||||
|
// Try to find a audio stream with the preferred format
|
||||||
|
for (int i = 0; i < audioStreams.size(); i++) if (audioStreams.get(i).format == preferredFormat) highestQualityIndex = i;
|
||||||
|
|
||||||
|
// Try to find a audio stream with the highest bitrate and preferred format
|
||||||
for (int i = 0; i < audioStreams.size(); i++) {
|
for (int i = 0; i < audioStreams.size(); i++) {
|
||||||
if (audioStreams.get(i).format == preferredFormat) {
|
AudioStream audioStream = audioStreams.get(i);
|
||||||
return i;
|
if (audioStream.avgBitrate > audioStreams.get(highestQualityIndex).avgBitrate
|
||||||
}
|
&& audioStream.format == preferredFormat) highestQualityIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return highestQualityIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -191,14 +197,13 @@ public class Utils {
|
||||||
}
|
}
|
||||||
return getSortedStreamVideosList(preferredFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
return getSortedStreamVideosList(preferredFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
||||||
}
|
}
|
||||||
//show_higher_resolutions_key
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
|
* Join the two lists of video streams (video_only and normal videos), and sort them according with preferred format
|
||||||
* chosen by the user
|
* chosen by the user
|
||||||
*
|
*
|
||||||
* @param preferredFormat format to give preference
|
* @param preferredFormat format to give preference
|
||||||
* @param showHigherResolutions
|
* @param showHigherResolutions show >1080p resolutions
|
||||||
* @param videoStreams normal videos list
|
* @param videoStreams normal videos list
|
||||||
* @param videoOnlyStreams video only stream list
|
* @param videoOnlyStreams video only stream list
|
||||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
||||||
|
|
17
app/src/main/res/drawable/custom_progress_bar.xml
Normal file
17
app/src/main/res/drawable/custom_progress_bar.xml
Normal 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>
|
|
@ -1,16 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/notificationContent"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clickable="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/background_notification_color">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="64dp"
|
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:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
@ -18,65 +19,102 @@
|
||||||
android:id="@+id/notificationCover"
|
android:id="@+id/notificationCover"
|
||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/dummy_thumbnail"
|
android:src="@drawable/dummy_thumbnail"
|
||||||
android:scaleType="centerCrop"/>
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical"
|
||||||
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/notificationSongName"
|
android:id="@+id/notificationSongName"
|
||||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
|
android:ellipsize="end"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="marquee"
|
android:maxLines="1"
|
||||||
android:singleLine="true"
|
android:textSize="14sp"
|
||||||
android:text="title" />
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta."/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/notificationArtist"
|
android:id="@+id/notificationArtist"
|
||||||
|
android:layout_width="match_parent"
|
||||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||||
android:layout_width="wrap_content"
|
android:ellipsize="end"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="marquee"
|
android:maxLines="1"
|
||||||
android:singleLine="true"
|
android:textSize="12sp"
|
||||||
android:text="artist" />
|
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/notificationRewind"
|
android:id="@+id/notificationRepeat"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="5dp"
|
android:background="#00000000"
|
||||||
android:background="#00ffffff"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="fitXY"
|
android:padding="5dp"
|
||||||
android:src="@drawable/ic_action_av_fast_rewind" />
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_repeat_white"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationFRewind"
|
||||||
|
android:layout_width="45dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_action_av_fast_rewind"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/notificationPlayPause"
|
android:id="@+id/notificationPlayPause"
|
||||||
android:layout_width="40dp"
|
android:layout_width="45dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="5dp"
|
android:background="#00000000"
|
||||||
android:background="#00ffffff"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="fitXY"
|
android:src="@drawable/ic_pause_white"
|
||||||
android:src="@drawable/ic_pause_white" />
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationFForward"
|
||||||
|
android:layout_width="45dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_action_av_fast_forward"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/notificationStop"
|
android:id="@+id/notificationStop"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_margin="5dp"
|
android:layout_marginLeft="5dp"
|
||||||
android:background="#00ffffff"
|
android:background="#00000000"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:scaleType="fitXY"
|
android:padding="5dp"
|
||||||
android:src="@drawable/ic_close_white" />
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_close_white"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
</RelativeLayout>
|
android:id="@+id/notificationProgressBar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="3dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginLeft="64dp"
|
||||||
|
android:progressDrawable="@drawable/custom_progress_bar"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:progress="52"/>
|
||||||
|
</FrameLayout>
|
|
@ -1,4 +1,153 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/notificationContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="128dp"
|
||||||
|
android:background="@color/background_notification_color"
|
||||||
|
android:clickable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/notificationCover"
|
||||||
|
android:layout_width="128dp"
|
||||||
|
android:layout_height="128dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/dummy_thumbnail"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationStop"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_close_white"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_toLeftOf="@+id/notificationStop"
|
||||||
|
android:layout_toRightOf="@+id/notificationCover"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp"
|
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notificationSongName"
|
||||||
|
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta."/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notificationArtist"
|
||||||
|
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/notificationProgressBar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:layout_alignTop="@+id/notificationControls"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_toRightOf="@+id/notificationCover"
|
||||||
|
android:progressDrawable="@drawable/custom_progress_bar"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:progress="52"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/notificationControls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_toRightOf="@+id/notificationCover"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationRepeat"
|
||||||
|
android:layout_width="45dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:paddingLeft="11dp"
|
||||||
|
android:paddingRight="11dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_repeat_white"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationFRewind"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginRight="5dp"
|
||||||
|
android:layout_toLeftOf="@+id/notificationPlayPause"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_action_av_fast_rewind"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationPlayPause"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginRight="5dp"
|
||||||
|
android:layout_toLeftOf="@+id/notificationFForward"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_pause_white"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/notificationFForward"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_action_av_fast_forward"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/notificationContent"
|
android:id="@+id/notificationContent"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
@ -93,4 +242,4 @@
|
||||||
android:layout_alignParentLeft="true" />
|
android:layout_alignParentLeft="true" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>-->
|
|
@ -29,20 +29,23 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/notificationSongName"
|
android:id="@+id/notificationSongName"
|
||||||
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
style="@android:style/TextAppearance.StatusBar.EventContent.Title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
|
android:ellipsize="end"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
tools:text="a long, long, long, long, long title"/>
|
android:textSize="14sp"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec aliquam augue, eget cursus est. Ut id tristique enim, ut scelerisque tellus. Sed ultricies ipsum non mauris ultricies, commodo malesuada velit porta."/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/notificationArtist"
|
android:id="@+id/notificationArtist"
|
||||||
|
android:layout_width="match_parent"
|
||||||
style="@android:style/TextAppearance.StatusBar.EventContent"
|
style="@android:style/TextAppearance.StatusBar.EventContent"
|
||||||
android:layout_width="wrap_content"
|
android:ellipsize="end"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
tools:text="a long, long artist"/>
|
android:textSize="12sp"
|
||||||
|
tools:text="Duis posuere arcu condimentum lobortis mattis."/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
|
|
Loading…
Reference in a new issue