2017-09-03 08:04:18 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
|
|
|
* BasePlayer.java is part of NewPipe
|
|
|
|
*
|
|
|
|
* License: GPL-3.0+
|
|
|
|
* This program 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.
|
|
|
|
*
|
|
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
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.preference.PreferenceManager;
|
|
|
|
import android.text.TextUtils;
|
|
|
|
import android.util.Log;
|
2017-09-08 06:47:43 +02:00
|
|
|
import android.view.View;
|
2017-04-17 06:19:53 +02:00
|
|
|
|
|
|
|
import com.google.android.exoplayer2.C;
|
|
|
|
import com.google.android.exoplayer2.DefaultLoadControl;
|
2017-08-18 20:07:57 +02:00
|
|
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
2017-04-17 06:19:53 +02:00
|
|
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
|
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
2017-06-26 04:41:52 +02:00
|
|
|
import com.google.android.exoplayer2.PlaybackParameters;
|
2017-08-18 20:07:57 +02:00
|
|
|
import com.google.android.exoplayer2.Player;
|
|
|
|
import com.google.android.exoplayer2.RenderersFactory;
|
2017-04-17 06:19:53 +02:00
|
|
|
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;
|
2017-09-08 06:47:43 +02:00
|
|
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-09-03 08:04:18 +02:00
|
|
|
import org.schabi.newpipe.Downloader;
|
2017-08-07 15:04:36 +02:00
|
|
|
import org.schabi.newpipe.R;
|
2017-09-05 21:27:12 +02:00
|
|
|
import org.schabi.newpipe.extractor.InfoItem;
|
2017-09-04 19:23:56 +02:00
|
|
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
2017-09-05 21:27:12 +02:00
|
|
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
2017-09-06 08:49:00 +02:00
|
|
|
import org.schabi.newpipe.player.playback.PlaybackListener;
|
2017-09-07 22:01:02 +02:00
|
|
|
import org.schabi.newpipe.player.playback.PlaybackManager;
|
2017-09-05 21:27:12 +02:00
|
|
|
import org.schabi.newpipe.playlist.ExternalPlayQueue;
|
2017-08-31 19:07:18 +02:00
|
|
|
import org.schabi.newpipe.playlist.PlayQueue;
|
2017-09-05 21:27:12 +02:00
|
|
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
|
|
|
import org.schabi.newpipe.playlist.SinglePlayQueue;
|
2017-08-07 15:04:36 +02:00
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
import java.io.File;
|
2017-09-05 21:27:12 +02:00
|
|
|
import java.io.Serializable;
|
2017-06-26 04:41:52 +02:00
|
|
|
import java.text.DecimalFormat;
|
|
|
|
import java.text.NumberFormat;
|
2017-09-05 21:27:12 +02:00
|
|
|
import java.util.ArrayList;
|
2017-04-17 06:19:53 +02:00
|
|
|
import java.util.Formatter;
|
2017-09-05 21:27:12 +02:00
|
|
|
import java.util.List;
|
2017-04-17 06:19:53 +02:00
|
|
|
import java.util.Locale;
|
2017-09-07 22:01:02 +02:00
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
import io.reactivex.Observable;
|
|
|
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
|
|
import io.reactivex.annotations.NonNull;
|
|
|
|
import io.reactivex.disposables.Disposable;
|
|
|
|
import io.reactivex.disposables.SerialDisposable;
|
|
|
|
import io.reactivex.functions.Consumer;
|
|
|
|
import io.reactivex.functions.Predicate;
|
2017-04-17 06:19:53 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base for the players, joining the common properties
|
|
|
|
*
|
|
|
|
* @author mauriciocolli
|
|
|
|
*/
|
|
|
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
2017-08-31 19:07:18 +02:00
|
|
|
public abstract class BasePlayer implements Player.EventListener,
|
2017-09-06 08:49:00 +02:00
|
|
|
AudioManager.OnAudioFocusChangeListener, PlaybackListener {
|
2017-09-03 08:04:18 +02:00
|
|
|
// TODO: Check api version for deprecated audio manager methods
|
2017-09-03 04:30:34 +02:00
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
public static final boolean DEBUG = true;
|
2017-04-17 06:19:53 +02:00
|
|
|
public static final String TAG = "BasePlayer";
|
|
|
|
|
|
|
|
protected Context context;
|
|
|
|
protected SharedPreferences sharedPreferences;
|
|
|
|
protected AudioManager audioManager;
|
|
|
|
|
|
|
|
protected BroadcastReceiver broadcastReceiver;
|
|
|
|
protected IntentFilter intentFilter;
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Intent
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
public static final String INTENT_TYPE = "intent_type";
|
|
|
|
public static final String SINGLE_STREAM = "single";
|
|
|
|
public static final String EXTERNAL_PLAYLIST = "external";
|
|
|
|
public static final String INTERNAL_PLAYLIST = "internal";
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
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";
|
2017-06-26 04:41:52 +02:00
|
|
|
public static final String PLAYBACK_SPEED = "playback_speed";
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
public static final String RESTORE_QUEUE_INDEX = "restore_queue_index";
|
|
|
|
public static final String RESTORE_WINDOW_POS = "restore_window_pos";
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
protected String videoUrl = "";
|
|
|
|
protected String videoTitle = "";
|
|
|
|
protected String videoThumbnailUrl = "";
|
2017-09-03 08:04:18 +02:00
|
|
|
protected String uploaderName = "";
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-08-31 19:07:18 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Playlist
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
protected PlaybackManager playbackManager;
|
2017-08-31 19:07:18 +02:00
|
|
|
protected PlayQueue playQueue;
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
protected int queueStartPos = 0;
|
|
|
|
protected long videoStartPos = -1;
|
2017-09-05 00:38:58 +02:00
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// 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 CacheDataSourceFactory cacheDataSourceFactory;
|
|
|
|
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
|
|
|
|
protected final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
|
|
|
|
2017-09-08 06:47:43 +02:00
|
|
|
protected int PROGRESS_LOOP_INTERVAL = 500;
|
2017-09-07 22:01:02 +02:00
|
|
|
protected Disposable progressUpdateReactor;
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
public BasePlayer(Context context) {
|
|
|
|
this.context = context;
|
|
|
|
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) {
|
2017-09-03 08:04:18 +02:00
|
|
|
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Downloader.USER_AGENT, bandwidthMeter);
|
2017-04-17 06:19:53 +02:00
|
|
|
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();
|
|
|
|
|
2017-10-08 15:23:05 +02:00
|
|
|
if (audioManager == null) {
|
|
|
|
this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
|
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
|
|
|
DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
|
|
|
|
DefaultLoadControl loadControl = new DefaultLoadControl();
|
|
|
|
|
2017-08-18 20:07:57 +02:00
|
|
|
final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
|
|
|
|
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, defaultTrackSelector, loadControl);
|
2017-04-17 06:19:53 +02:00
|
|
|
simpleExoPlayer.addListener(this);
|
|
|
|
}
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
public void initListeners() {}
|
|
|
|
|
|
|
|
protected Disposable getProgressReactor() {
|
|
|
|
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
.filter(new Predicate<Long>() {
|
|
|
|
@Override
|
|
|
|
public boolean test(@NonNull Long aLong) throws Exception {
|
|
|
|
return isProgressLoopRunning();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.subscribe(new Consumer<Long>() {
|
|
|
|
@Override
|
|
|
|
public void accept(Long aLong) throws Exception {
|
|
|
|
triggerProgressUpdate();
|
|
|
|
}
|
|
|
|
});
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void handleIntent(Intent intent) {
|
|
|
|
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
|
|
|
if (intent == null) return;
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
queueStartPos = intent.getIntExtra(RESTORE_QUEUE_INDEX, 0);
|
|
|
|
videoStartPos = intent.getLongExtra(START_POSITION, 0);
|
2017-06-26 04:41:52 +02:00
|
|
|
setPlaybackSpeed(intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed()));
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
switch (intent.getStringExtra(INTENT_TYPE)) {
|
|
|
|
case SINGLE_STREAM:
|
|
|
|
handleSinglePlaylistIntent(intent);
|
|
|
|
break;
|
|
|
|
case EXTERNAL_PLAYLIST:
|
|
|
|
handleExternalPlaylistIntent(intent);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public void handleExternalPlaylistIntent(Intent intent) {
|
|
|
|
final int serviceId = intent.getIntExtra(ExternalPlayQueue.SERVICE_ID, -1);
|
|
|
|
final int index = intent.getIntExtra(ExternalPlayQueue.INDEX, 0);
|
|
|
|
final Serializable serializable = intent.getSerializableExtra(ExternalPlayQueue.STREAMS);
|
|
|
|
final String nextPageUrl = intent.getStringExtra(ExternalPlayQueue.NEXT_PAGE_URL);
|
|
|
|
|
|
|
|
List<InfoItem> info = new ArrayList<>();
|
|
|
|
if (serializable instanceof List) {
|
|
|
|
for (final Object o : (List) serializable) {
|
|
|
|
if (o instanceof InfoItem) info.add((StreamInfoItem) o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index);
|
|
|
|
playQueue.init();
|
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
playbackManager = new PlaybackManager(this, playQueue);
|
2017-09-05 21:27:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public void handleSinglePlaylistIntent(Intent intent) {
|
|
|
|
final Serializable serializable = intent.getSerializableExtra(SinglePlayQueue.STREAM);
|
|
|
|
if (!(serializable instanceof StreamInfo)) return;
|
|
|
|
|
|
|
|
playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY);
|
|
|
|
playQueue.init();
|
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
playbackManager = new PlaybackManager(this, playQueue);
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
public void initThumbnail(final String url) {
|
2017-09-08 06:47:43 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "initThumbnail() called");
|
|
|
|
if (url == null || url.isEmpty()) return;
|
|
|
|
ImageLoader.getInstance().resume();
|
|
|
|
ImageLoader.getInstance().loadImage(url, new SimpleImageLoadingListener() {
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
2017-09-08 06:47:43 +02:00
|
|
|
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
|
|
|
if (simpleExoPlayer == null) return;
|
|
|
|
if (DEBUG) Log.d(TAG, "onLoadingComplete() called with: imageUri = [" + imageUri + "], view = [" + view + "], loadedImage = [" + loadedImage + "]");
|
|
|
|
onThumbnailReceived(loadedImage);
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
2017-09-08 06:47:43 +02:00
|
|
|
});
|
2017-09-07 22:01:02 +02:00
|
|
|
}
|
|
|
|
|
2017-09-08 06:47:43 +02:00
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
public void onThumbnailReceived(Bitmap thumbnail) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onThumbnailReceived() called with: thumbnail = [" + thumbnail + "]");
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void destroyPlayer() {
|
|
|
|
if (DEBUG) Log.d(TAG, "destroyPlayer() called");
|
|
|
|
if (simpleExoPlayer != null) {
|
|
|
|
simpleExoPlayer.stop();
|
|
|
|
simpleExoPlayer.release();
|
|
|
|
}
|
2017-09-07 22:01:02 +02:00
|
|
|
if (isProgressLoopRunning()) stopProgressLoop();
|
2017-09-03 08:04:18 +02:00
|
|
|
if (audioManager != null) {
|
|
|
|
audioManager.abandonAudioFocus(this);
|
|
|
|
audioManager = null;
|
|
|
|
}
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void destroy() {
|
|
|
|
if (DEBUG) Log.d(TAG, "destroy() called");
|
|
|
|
destroyPlayer();
|
|
|
|
unregisterBroadcastReceiver();
|
2017-09-07 22:01:02 +02:00
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-07 15:04:36 +02:00
|
|
|
private boolean isResumeAfterAudioFocusGain() {
|
2017-09-03 08:04:18 +02:00
|
|
|
return sharedPreferences != null && context != null
|
|
|
|
&& sharedPreferences.getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), false);
|
2017-08-07 15:04:36 +02:00
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
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);
|
2017-08-07 15:04:36 +02:00
|
|
|
|
|
|
|
if (isResumeAfterAudioFocusGain()) simpleExoPlayer.setPlayWhenReady(true);
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2017-09-07 22:01:02 +02:00
|
|
|
if (!isProgressLoopRunning()) startProgressLoop();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void onPlaying() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPlaying() called");
|
2017-09-07 22:01:02 +02:00
|
|
|
if (!isProgressLoopRunning()) startProgressLoop();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void onBuffering() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onPaused() {
|
2017-09-07 22:01:02 +02:00
|
|
|
if (isProgressLoopRunning()) stopProgressLoop();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void onPausedSeek() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onCompleted() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onCompleted() called");
|
2017-09-07 22:01:02 +02:00
|
|
|
if (isProgressLoopRunning()) stopProgressLoop();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Repeat
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
public void onRepeatClicked() {
|
|
|
|
if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
|
|
|
|
|
2017-09-05 00:38:58 +02:00
|
|
|
final int mode;
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-09-05 00:38:58 +02:00
|
|
|
switch (simpleExoPlayer.getRepeatMode()) {
|
|
|
|
case Player.REPEAT_MODE_OFF:
|
|
|
|
mode = Player.REPEAT_MODE_ONE;
|
|
|
|
break;
|
|
|
|
case Player.REPEAT_MODE_ONE:
|
|
|
|
mode = Player.REPEAT_MODE_ALL;
|
|
|
|
break;
|
|
|
|
case Player.REPEAT_MODE_ALL:
|
|
|
|
default:
|
|
|
|
mode = Player.REPEAT_MODE_OFF;
|
|
|
|
break;
|
|
|
|
}
|
2017-09-05 21:27:12 +02:00
|
|
|
|
2017-09-05 00:38:58 +02:00
|
|
|
simpleExoPlayer.setRepeatMode(mode);
|
|
|
|
if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + simpleExoPlayer.getRepeatMode());
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// ExoPlayer Listener
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
2017-09-07 22:01:02 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "onTimelineChanged(), timeline size = " + timeline.getWindowCount());
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
2017-09-07 22:01:02 +02:00
|
|
|
Log.w(TAG, "onTracksChanged() called, unsupported operation. Is this expected?");
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
2017-06-26 04:41:52 +02:00
|
|
|
@Override
|
|
|
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
2017-09-07 22:01:02 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "playbackParameters(), speed: " + playbackParameters.speed + ", pitch: " + playbackParameters.pitch);
|
2017-06-26 04:41:52 +02:00
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
@Override
|
|
|
|
public void onLoadingChanged(boolean isLoading) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning()) stopProgressLoop();
|
|
|
|
else if (isLoading && !isProgressLoopRunning()) startProgressLoop();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
2017-09-03 08:04:18 +02:00
|
|
|
if (DEBUG)
|
|
|
|
Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
|
2017-04-17 06:19:53 +02:00
|
|
|
if (getCurrentState() == STATE_PAUSED_SEEK) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (playbackState) {
|
2017-08-18 20:07:57 +02:00
|
|
|
case Player.STATE_IDLE: // 1
|
2017-04-17 06:19:53 +02:00
|
|
|
isPrepared = false;
|
|
|
|
break;
|
2017-08-18 20:07:57 +02:00
|
|
|
case Player.STATE_BUFFERING: // 2
|
2017-04-17 06:19:53 +02:00
|
|
|
if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING);
|
|
|
|
break;
|
2017-08-18 20:07:57 +02:00
|
|
|
case Player.STATE_READY: //3
|
2017-04-17 06:19:53 +02:00
|
|
|
if (!isPrepared) {
|
|
|
|
isPrepared = true;
|
|
|
|
onPrepared(playWhenReady);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (currentState == STATE_PAUSED_SEEK) break;
|
|
|
|
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
|
|
|
|
break;
|
2017-08-18 20:07:57 +02:00
|
|
|
case Player.STATE_ENDED: // 4
|
2017-09-06 08:49:00 +02:00
|
|
|
if (playQueue.getIndex() < playQueue.size() - 1) {
|
|
|
|
playQueue.offsetIndex(+1);
|
|
|
|
break;
|
|
|
|
}
|
2017-04-17 06:19:53 +02:00
|
|
|
changeState(STATE_COMPLETED);
|
|
|
|
isPrepared = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPlayerError(ExoPlaybackException error) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
|
2017-09-04 14:42:34 +02:00
|
|
|
playbackManager.report(error);
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
onError(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPositionDiscontinuity() {
|
2017-09-06 02:48:48 +02:00
|
|
|
// Refresh the playback if there is a transition to the next video
|
2017-08-31 19:07:18 +02:00
|
|
|
int newIndex = simpleExoPlayer.getCurrentWindowIndex();
|
2017-09-06 02:48:48 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: index = [" + newIndex + "]");
|
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
playbackManager.refresh(newIndex);
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
@Override
|
|
|
|
public void onRepeatModeChanged(int i) {
|
|
|
|
if (DEBUG) Log.d(TAG, "onRepeatModeChanged() called with: mode = [" + i + "]");
|
|
|
|
}
|
|
|
|
|
2017-08-31 19:07:18 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Playback Listener
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void block() {
|
2017-09-06 02:48:48 +02:00
|
|
|
if (simpleExoPlayer == null) return;
|
2017-09-06 08:49:00 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "Blocking...");
|
2017-09-03 04:30:34 +02:00
|
|
|
|
2017-09-04 04:15:11 +02:00
|
|
|
simpleExoPlayer.stop();
|
|
|
|
|
|
|
|
changeState(STATE_BUFFERING);
|
2017-08-31 19:07:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void unblock() {
|
2017-09-06 02:48:48 +02:00
|
|
|
if (simpleExoPlayer == null) return;
|
2017-09-06 08:49:00 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "Unblocking...");
|
2017-09-04 04:15:11 +02:00
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
if (queueStartPos != playQueue.getIndex()) {
|
|
|
|
queueStartPos = playQueue.getIndex();
|
|
|
|
videoStartPos = 0;
|
2017-09-03 04:30:34 +02:00
|
|
|
}
|
2017-09-04 04:15:11 +02:00
|
|
|
|
|
|
|
simpleExoPlayer.prepare(playbackManager.getMediaSource());
|
2017-09-07 22:01:02 +02:00
|
|
|
simpleExoPlayer.seekTo(playbackManager.getCurrentSourceIndex(), videoStartPos);
|
2017-09-05 21:27:12 +02:00
|
|
|
simpleExoPlayer.setPlayWhenReady(false);
|
2017-09-02 20:06:36 +02:00
|
|
|
}
|
|
|
|
|
2017-09-01 02:47:56 +02:00
|
|
|
@Override
|
2017-09-05 21:27:12 +02:00
|
|
|
public void sync(final StreamInfo info, final int sortedStreamsIndex) {
|
2017-09-06 02:48:48 +02:00
|
|
|
if (simpleExoPlayer == null) return;
|
2017-09-06 08:49:00 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "Syncing...");
|
2017-09-04 04:15:11 +02:00
|
|
|
|
2017-09-04 19:23:56 +02:00
|
|
|
videoUrl = info.url;
|
2017-09-03 04:30:34 +02:00
|
|
|
videoThumbnailUrl = info.thumbnail_url;
|
2017-09-04 19:23:56 +02:00
|
|
|
videoTitle = info.name;
|
2017-09-03 04:30:34 +02:00
|
|
|
|
2017-09-07 22:01:02 +02:00
|
|
|
initThumbnail(videoThumbnailUrl);
|
2017-09-06 02:48:48 +02:00
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) {
|
2017-09-06 08:49:00 +02:00
|
|
|
if (DEBUG) Log.w(TAG, "Rewinding to correct window");
|
2017-09-06 02:48:48 +02:00
|
|
|
if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) {
|
|
|
|
simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex());
|
|
|
|
} else {
|
2017-09-07 22:01:02 +02:00
|
|
|
if (DEBUG) Log.w(TAG, "Play Queue out of sync");
|
|
|
|
playbackManager.reset();
|
|
|
|
return;
|
2017-09-06 02:48:48 +02:00
|
|
|
}
|
2017-09-03 04:30:34 +02:00
|
|
|
}
|
2017-09-05 21:27:12 +02:00
|
|
|
|
|
|
|
simpleExoPlayer.setPlayWhenReady(true);
|
2017-09-03 04:30:34 +02:00
|
|
|
}
|
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
@Override
|
|
|
|
public void shutdown() {
|
2017-09-06 08:49:00 +02:00
|
|
|
if (DEBUG) Log.d(TAG, "Shutting down...");
|
2017-09-05 21:27:12 +02:00
|
|
|
|
|
|
|
playbackManager.dispose();
|
|
|
|
playQueue.dispose();
|
|
|
|
destroy();
|
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// General Player
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2017-09-05 21:27:12 +02:00
|
|
|
public abstract void onError(Exception exception);
|
2017-04-17 06:19:53 +02:00
|
|
|
|
|
|
|
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 (!isPlaying()) audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
2017-08-02 19:14:45 +02:00
|
|
|
else audioManager.abandonAudioFocus(this);
|
2017-04-17 06:19:53 +02:00
|
|
|
|
2017-09-08 16:52:38 +02:00
|
|
|
if (getCurrentState() == STATE_COMPLETED) playQueue.setIndex(0);
|
2017-04-17 06:19:53 +02:00
|
|
|
simpleExoPlayer.setPlayWhenReady(!isPlaying());
|
|
|
|
}
|
|
|
|
|
|
|
|
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 seekBy(int milliSeconds) {
|
|
|
|
if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
|
2017-09-03 08:04:18 +02:00
|
|
|
if (simpleExoPlayer == null || (isCompleted() && milliSeconds > 0) || ((milliSeconds < 0 && simpleExoPlayer.getCurrentPosition() == 0)))
|
|
|
|
return;
|
2017-04-17 06:19:53 +02:00
|
|
|
int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds);
|
|
|
|
if (progress < 0) progress = 0;
|
|
|
|
simpleExoPlayer.seekTo(progress);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isPlaying() {
|
2017-08-18 20:07:57 +02:00
|
|
|
return simpleExoPlayer.getPlaybackState() == Player.STATE_READY && simpleExoPlayer.getPlayWhenReady();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Utils
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
private final StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
private final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
|
2017-06-26 04:41:52 +02:00
|
|
|
private final NumberFormat speedFormatter = new DecimalFormat("0.##x");
|
2017-04-17 06:19:53 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2017-06-26 04:41:52 +02:00
|
|
|
protected String formatSpeed(float speed) {
|
|
|
|
return speedFormatter.format(speed);
|
|
|
|
}
|
|
|
|
|
2017-04-17 06:19:53 +02:00
|
|
|
protected void startProgressLoop() {
|
2017-09-07 22:01:02 +02:00
|
|
|
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
|
|
|
|
progressUpdateReactor = getProgressReactor();
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected void stopProgressLoop() {
|
2017-09-07 22:01:02 +02:00
|
|
|
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
|
|
|
|
progressUpdateReactor = null;
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 int getCurrentState() {
|
|
|
|
return currentState;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getVideoUrl() {
|
|
|
|
return videoUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setVideoUrl(String videoUrl) {
|
|
|
|
this.videoUrl = videoUrl;
|
|
|
|
}
|
|
|
|
|
2017-09-03 08:04:18 +02:00
|
|
|
public long getVideoStartPos() {
|
2017-04-17 06:19:53 +02:00
|
|
|
return videoStartPos;
|
|
|
|
}
|
|
|
|
|
2017-09-03 08:04:18 +02:00
|
|
|
public void setVideoStartPos(long videoStartPos) {
|
2017-04-17 06:19:53 +02:00
|
|
|
this.videoStartPos = videoStartPos;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getVideoTitle() {
|
|
|
|
return videoTitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setVideoTitle(String videoTitle) {
|
|
|
|
this.videoTitle = videoTitle;
|
|
|
|
}
|
|
|
|
|
2017-09-03 08:04:18 +02:00
|
|
|
public String getUploaderName() {
|
|
|
|
return uploaderName;
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
2017-09-03 08:04:18 +02:00
|
|
|
public void setUploaderName(String uploaderName) {
|
|
|
|
this.uploaderName = uploaderName;
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isCompleted() {
|
2017-08-18 20:07:57 +02:00
|
|
|
return simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_ENDED;
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isPrepared() {
|
|
|
|
return isPrepared;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setPrepared(boolean prepared) {
|
|
|
|
isPrepared = prepared;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getVideoThumbnailUrl() {
|
|
|
|
return videoThumbnailUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setVideoThumbnailUrl(String videoThumbnailUrl) {
|
|
|
|
this.videoThumbnailUrl = videoThumbnailUrl;
|
|
|
|
}
|
2017-06-26 04:41:52 +02:00
|
|
|
|
|
|
|
public float getPlaybackSpeed() {
|
|
|
|
return simpleExoPlayer.getPlaybackParameters().speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setPlaybackSpeed(float speed) {
|
|
|
|
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, 1f));
|
|
|
|
}
|
2017-09-05 21:27:12 +02:00
|
|
|
|
|
|
|
public int getCurrentQueueIndex() {
|
|
|
|
return playQueue != null ? playQueue.getIndex() : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public PlayQueue getPlayQueue() {
|
|
|
|
return playQueue;
|
|
|
|
}
|
2017-09-06 02:48:48 +02:00
|
|
|
|
2017-09-06 08:49:00 +02:00
|
|
|
public boolean isPlayerReady() {
|
|
|
|
return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED;
|
2017-09-06 02:48:48 +02:00
|
|
|
}
|
2017-09-07 22:01:02 +02:00
|
|
|
|
|
|
|
public boolean isProgressLoopRunning() {
|
|
|
|
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
|
|
|
|
}
|
2017-04-17 06:19:53 +02:00
|
|
|
}
|