local player improvements
This commit is contained in:
parent
c0e9b673db
commit
14e1abc28e
4 changed files with 423 additions and 245 deletions
|
@ -318,7 +318,8 @@
|
|||
android:name=".LocalPlayerActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:configChanges="orientation|screenSize|layoutDirection" />
|
||||
|
||||
<service
|
||||
android:name=".RouterActivity$FetcherService"
|
||||
|
|
|
@ -1,91 +1,105 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.LocalPlayer;
|
||||
import org.schabi.newpipe.player.LocalPlayerListener;
|
||||
import org.schabi.newpipe.util.VideoSegment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.disposables.SerialDisposable;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.schabi.newpipe.player.Player.STATE_BLOCKED;
|
||||
import static org.schabi.newpipe.player.Player.STATE_BUFFERING;
|
||||
import static org.schabi.newpipe.player.Player.STATE_COMPLETED;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PAUSED;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PAUSED_SEEK;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PLAYING;
|
||||
|
||||
public class LocalPlayerActivity extends AppCompatActivity implements Player.EventListener {
|
||||
private SimpleExoPlayer simpleExoPlayer;
|
||||
private SerialDisposable progressUpdateReactor;
|
||||
private int lastCurrentProgress = -1;
|
||||
protected static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
|
||||
public static final String TAG = "LocalPlayer";
|
||||
private VideoSegment[] segments;
|
||||
private SharedPreferences mPrefs;
|
||||
public class LocalPlayerActivity extends AppCompatActivity implements Player.EventListener,
|
||||
LocalPlayerListener {
|
||||
private LocalPlayer localPlayer;
|
||||
public static final String TAG = "LocalPlayerActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_local_player);
|
||||
|
||||
this.mPrefs = PreferenceManager.getDefaultSharedPreferences(App.getApp());
|
||||
hideSystemUi(isLandscape());
|
||||
|
||||
initPlayer();
|
||||
initSegments();
|
||||
}
|
||||
final Intent intent = getIntent();
|
||||
|
||||
private void initPlayer() {
|
||||
final String uri = getIntent().getDataString();
|
||||
final String uri = intent.getDataString();
|
||||
final VideoSegment[] segments = getSegmentsFromIntent(intent);
|
||||
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
localPlayer = new LocalPlayer(this);
|
||||
localPlayer.initPlayer(uri, segments);
|
||||
|
||||
simpleExoPlayer = new SimpleExoPlayer
|
||||
.Builder(this)
|
||||
.build();
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(this));
|
||||
simpleExoPlayer.setHandleAudioBecomingNoisy(true);
|
||||
final MediaSource videoSource = new ProgressiveMediaSource
|
||||
.Factory(new DefaultDataSourceFactory(this, DownloaderImpl.USER_AGENT))
|
||||
.createMediaSource(Uri.parse(uri));
|
||||
simpleExoPlayer.prepare(videoSource);
|
||||
final PlayerView playerView = findViewById(R.id.player_view);
|
||||
playerView.setPlayer(simpleExoPlayer);
|
||||
|
||||
this.progressUpdateReactor = new SerialDisposable();
|
||||
playerView.setPlayer(localPlayer.getPlayer());
|
||||
}
|
||||
|
||||
private void initSegments() {
|
||||
final String segmentsJson = getIntent().getStringExtra("segments");
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
localPlayer.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull final Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
hideSystemUi(isLandscape());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlocked(final SimpleExoPlayer player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaying(final SimpleExoPlayer player) {
|
||||
setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuffering(final SimpleExoPlayer player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused(final SimpleExoPlayer player) {
|
||||
setKeepScreenOn(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPausedSeek(final SimpleExoPlayer player) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted(final SimpleExoPlayer player) {
|
||||
setKeepScreenOn(false);
|
||||
}
|
||||
|
||||
private static VideoSegment[] getSegmentsFromIntent(final Intent intent) {
|
||||
final List<VideoSegment> result = new ArrayList<>();
|
||||
|
||||
final String segmentsJson = intent.getStringExtra("segments");
|
||||
|
||||
if (segmentsJson != null && segmentsJson.length() > 0) {
|
||||
try {
|
||||
final ArrayList<VideoSegment> segmentsArrayList = new ArrayList<>();
|
||||
final JsonObject obj = JsonParser.object().from(segmentsJson);
|
||||
|
||||
for (final Object item : obj.getArray("segments")) {
|
||||
|
@ -96,216 +110,63 @@ public class LocalPlayerActivity extends AppCompatActivity implements Player.Eve
|
|||
final String category = itemObject.getString("category");
|
||||
|
||||
final VideoSegment segment = new VideoSegment(startTime, endTime, category);
|
||||
segmentsArrayList.add(segment);
|
||||
result.add(segment);
|
||||
}
|
||||
|
||||
segments = segmentsArrayList.toArray(new VideoSegment[0]);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Error initializing segments", e);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toArray(new VideoSegment[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
simpleExoPlayer.removeListener(this);
|
||||
simpleExoPlayer.stop();
|
||||
simpleExoPlayer.release();
|
||||
progressUpdateReactor.set(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
|
||||
switch (playbackState) {
|
||||
case Player.STATE_IDLE:
|
||||
break;
|
||||
case Player.STATE_BUFFERING:
|
||||
break;
|
||||
case Player.STATE_READY:
|
||||
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
|
||||
break;
|
||||
case Player.STATE_ENDED:
|
||||
changeState(STATE_COMPLETED);
|
||||
break;
|
||||
private void setKeepScreenOn(final boolean keepScreenOn) {
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeState(final int state) {
|
||||
switch (state) {
|
||||
case STATE_BLOCKED:
|
||||
onBlocked();
|
||||
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;
|
||||
}
|
||||
private void hideSystemUi(final boolean isLandscape) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
|
||||
private void onBlocked() {
|
||||
if (!isProgressLoopRunning()) {
|
||||
startProgressLoop();
|
||||
}
|
||||
int visibility;
|
||||
|
||||
if (isLandscape) {
|
||||
visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
} else {
|
||||
visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
}
|
||||
|
||||
private void onPlaying() {
|
||||
if (!isProgressLoopRunning()) {
|
||||
startProgressLoop();
|
||||
}
|
||||
if (!isInMultiWindow()) {
|
||||
visibility |= View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
}
|
||||
|
||||
private void onBuffering() {
|
||||
getWindow().getDecorView().setSystemUiVisibility(visibility);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (isInMultiWindow())) {
|
||||
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
getWindow().setNavigationBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
private void onPaused() {
|
||||
if (isProgressLoopRunning()) {
|
||||
stopProgressLoop();
|
||||
}
|
||||
private boolean isInMultiWindow() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode();
|
||||
}
|
||||
|
||||
private void onPausedSeek() {
|
||||
}
|
||||
|
||||
private void onCompleted() {
|
||||
if (isProgressLoopRunning()) {
|
||||
stopProgressLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProgressLoopRunning() {
|
||||
return progressUpdateReactor.get() != null;
|
||||
}
|
||||
|
||||
protected void startProgressLoop() {
|
||||
progressUpdateReactor.set(getProgressReactor());
|
||||
}
|
||||
|
||||
protected void stopProgressLoop() {
|
||||
progressUpdateReactor.set(null);
|
||||
}
|
||||
|
||||
private Disposable getProgressReactor() {
|
||||
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
|
||||
AndroidSchedulers.mainThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> triggerProgressUpdate(),
|
||||
error -> Log.e(TAG, "Progress update failure: ", error));
|
||||
}
|
||||
|
||||
private void triggerProgressUpdate() {
|
||||
if (simpleExoPlayer == null) {
|
||||
return;
|
||||
}
|
||||
final int currentProgress = Math.max((int) simpleExoPlayer.getCurrentPosition(), 0);
|
||||
|
||||
final boolean isRewind = currentProgress < lastCurrentProgress;
|
||||
|
||||
lastCurrentProgress = currentProgress;
|
||||
|
||||
if (!mPrefs.getBoolean(
|
||||
this.getString(R.string.sponsor_block_enable_key), false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final VideoSegment segment = getSkippableSegment(currentProgress);
|
||||
if (segment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int skipTarget = isRewind
|
||||
? (int) Math.ceil((segment.startTime)) - 1
|
||||
: (int) Math.ceil((segment.endTime));
|
||||
|
||||
if (skipTarget < 0) {
|
||||
skipTarget = 0;
|
||||
}
|
||||
|
||||
// temporarily force EXACT seek parameters to prevent infinite skip looping
|
||||
final SeekParameters seekParams = simpleExoPlayer.getSeekParameters();
|
||||
simpleExoPlayer.setSeekParameters(SeekParameters.EXACT);
|
||||
|
||||
seekTo(skipTarget);
|
||||
|
||||
simpleExoPlayer.setSeekParameters(seekParams);
|
||||
|
||||
if (mPrefs.getBoolean(
|
||||
this.getString(R.string.sponsor_block_notifications_key), false)) {
|
||||
String toastText = "";
|
||||
|
||||
switch (segment.category) {
|
||||
case "sponsor":
|
||||
toastText = this
|
||||
.getString(R.string.sponsor_block_skip_sponsor_toast);
|
||||
break;
|
||||
case "intro":
|
||||
toastText = this
|
||||
.getString(R.string.sponsor_block_skip_intro_toast);
|
||||
break;
|
||||
case "outro":
|
||||
toastText = this
|
||||
.getString(R.string.sponsor_block_skip_outro_toast);
|
||||
break;
|
||||
case "interaction":
|
||||
toastText = this
|
||||
.getString(R.string.sponsor_block_skip_interaction_toast);
|
||||
break;
|
||||
case "selfpromo":
|
||||
toastText = this
|
||||
.getString(R.string.sponsor_block_skip_self_promo_toast);
|
||||
break;
|
||||
case "music_offtopic":
|
||||
toastText = this
|
||||
.getString(R.string.sponsor_block_skip_non_music_toast);
|
||||
break;
|
||||
}
|
||||
|
||||
Toast.makeText(this, toastText, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void seekTo(final long positionMillis) {
|
||||
if (simpleExoPlayer != null) {
|
||||
// prevent invalid positions when fast-forwarding/-rewinding
|
||||
long normalizedPositionMillis = positionMillis;
|
||||
if (normalizedPositionMillis < 0) {
|
||||
normalizedPositionMillis = 0;
|
||||
} else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) {
|
||||
normalizedPositionMillis = simpleExoPlayer.getDuration();
|
||||
}
|
||||
|
||||
simpleExoPlayer.seekTo(normalizedPositionMillis);
|
||||
}
|
||||
}
|
||||
|
||||
public VideoSegment getSkippableSegment(final int progress) {
|
||||
if (segments == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (final VideoSegment segment : segments) {
|
||||
if (progress < segment.startTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (progress > segment.endTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
return null;
|
||||
boolean isLandscape() {
|
||||
final DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
return metrics.heightPixels < metrics.widthPixels;
|
||||
}
|
||||
}
|
||||
|
|
304
app/src/main/java/org/schabi/newpipe/player/LocalPlayer.java
Normal file
304
app/src/main/java/org/schabi/newpipe/player/LocalPlayer.java
Normal file
|
@ -0,0 +1,304 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.exoplayer2.Player.EventListener;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.util.VideoSegment;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.disposables.SerialDisposable;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.schabi.newpipe.player.Player.STATE_BLOCKED;
|
||||
import static org.schabi.newpipe.player.Player.STATE_BUFFERING;
|
||||
import static org.schabi.newpipe.player.Player.STATE_COMPLETED;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PAUSED;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PAUSED_SEEK;
|
||||
import static org.schabi.newpipe.player.Player.STATE_PLAYING;
|
||||
|
||||
public class LocalPlayer implements EventListener {
|
||||
private static final String TAG = "LocalPlayer";
|
||||
private static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
|
||||
|
||||
private final Context context;
|
||||
private final SharedPreferences mPrefs;
|
||||
private SimpleExoPlayer simpleExoPlayer;
|
||||
private SerialDisposable progressUpdateReactor;
|
||||
private VideoSegment[] videoSegments;
|
||||
private LocalPlayerListener listener;
|
||||
private int lastCurrentProgress = -1;
|
||||
|
||||
public LocalPlayer(final Context context) {
|
||||
this.context = context;
|
||||
this.mPrefs = PreferenceManager.getDefaultSharedPreferences(App.getApp());
|
||||
}
|
||||
|
||||
public void initPlayer(final String uri, final VideoSegment[] segments) {
|
||||
this.videoSegments = segments;
|
||||
this.progressUpdateReactor = new SerialDisposable();
|
||||
|
||||
simpleExoPlayer = new SimpleExoPlayer
|
||||
.Builder(context)
|
||||
.build();
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
||||
simpleExoPlayer.setHandleAudioBecomingNoisy(true);
|
||||
|
||||
if (uri == null || uri.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MediaSource videoSource = new ProgressiveMediaSource
|
||||
.Factory(new DefaultDataSourceFactory(context, DownloaderImpl.USER_AGENT))
|
||||
.createMediaSource(Uri.parse(uri));
|
||||
simpleExoPlayer.prepare(videoSource);
|
||||
}
|
||||
|
||||
public SimpleExoPlayer getPlayer() {
|
||||
return this.simpleExoPlayer;
|
||||
}
|
||||
|
||||
public void setListener(final LocalPlayerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
simpleExoPlayer.removeListener(this);
|
||||
simpleExoPlayer.stop();
|
||||
simpleExoPlayer.release();
|
||||
progressUpdateReactor.set(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
|
||||
switch (playbackState) {
|
||||
case com.google.android.exoplayer2.Player.STATE_IDLE:
|
||||
break;
|
||||
case com.google.android.exoplayer2.Player.STATE_BUFFERING:
|
||||
break;
|
||||
case com.google.android.exoplayer2.Player.STATE_READY:
|
||||
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
|
||||
break;
|
||||
case com.google.android.exoplayer2.Player.STATE_ENDED:
|
||||
changeState(STATE_COMPLETED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProgressLoopRunning() {
|
||||
return progressUpdateReactor.get() != null;
|
||||
}
|
||||
|
||||
private void startProgressLoop() {
|
||||
progressUpdateReactor.set(getProgressReactor());
|
||||
}
|
||||
|
||||
private void stopProgressLoop() {
|
||||
progressUpdateReactor.set(null);
|
||||
}
|
||||
|
||||
private Disposable getProgressReactor() {
|
||||
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
|
||||
AndroidSchedulers.mainThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> triggerProgressUpdate(),
|
||||
error -> Log.e(TAG, "Progress update failure: ", error));
|
||||
}
|
||||
|
||||
private void changeState(final int state) {
|
||||
switch (state) {
|
||||
case STATE_BLOCKED:
|
||||
onBlocked();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void onBlocked() {
|
||||
if (!isProgressLoopRunning()) {
|
||||
startProgressLoop();
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onBlocked(simpleExoPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlaying() {
|
||||
if (!isProgressLoopRunning()) {
|
||||
startProgressLoop();
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onPlaying(simpleExoPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBuffering() {
|
||||
if (listener != null) {
|
||||
listener.onBuffering(simpleExoPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPaused() {
|
||||
if (isProgressLoopRunning()) {
|
||||
stopProgressLoop();
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onPaused(simpleExoPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPausedSeek() {
|
||||
if (listener != null) {
|
||||
listener.onPausedSeek(simpleExoPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCompleted() {
|
||||
if (isProgressLoopRunning()) {
|
||||
stopProgressLoop();
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onCompleted(simpleExoPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerProgressUpdate() {
|
||||
if (simpleExoPlayer == null) {
|
||||
return;
|
||||
}
|
||||
final int currentProgress = Math.max((int) simpleExoPlayer.getCurrentPosition(), 0);
|
||||
|
||||
final boolean isRewind = currentProgress < lastCurrentProgress;
|
||||
|
||||
lastCurrentProgress = currentProgress;
|
||||
|
||||
if (!mPrefs.getBoolean(
|
||||
context.getString(R.string.sponsor_block_enable_key), false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final VideoSegment segment = getSkippableSegment(currentProgress);
|
||||
if (segment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int skipTarget = isRewind
|
||||
? (int) Math.ceil((segment.startTime)) - 1
|
||||
: (int) Math.ceil((segment.endTime));
|
||||
|
||||
if (skipTarget < 0) {
|
||||
skipTarget = 0;
|
||||
}
|
||||
|
||||
// temporarily force EXACT seek parameters to prevent infinite skip looping
|
||||
final SeekParameters seekParams = simpleExoPlayer.getSeekParameters();
|
||||
simpleExoPlayer.setSeekParameters(SeekParameters.EXACT);
|
||||
|
||||
seekTo(skipTarget);
|
||||
|
||||
simpleExoPlayer.setSeekParameters(seekParams);
|
||||
|
||||
if (mPrefs.getBoolean(
|
||||
context.getString(R.string.sponsor_block_notifications_key), false)) {
|
||||
String toastText = "";
|
||||
|
||||
switch (segment.category) {
|
||||
case "sponsor":
|
||||
toastText = context
|
||||
.getString(R.string.sponsor_block_skip_sponsor_toast);
|
||||
break;
|
||||
case "intro":
|
||||
toastText = context
|
||||
.getString(R.string.sponsor_block_skip_intro_toast);
|
||||
break;
|
||||
case "outro":
|
||||
toastText = context
|
||||
.getString(R.string.sponsor_block_skip_outro_toast);
|
||||
break;
|
||||
case "interaction":
|
||||
toastText = context
|
||||
.getString(R.string.sponsor_block_skip_interaction_toast);
|
||||
break;
|
||||
case "selfpromo":
|
||||
toastText = context
|
||||
.getString(R.string.sponsor_block_skip_self_promo_toast);
|
||||
break;
|
||||
case "music_offtopic":
|
||||
toastText = context
|
||||
.getString(R.string.sponsor_block_skip_non_music_toast);
|
||||
break;
|
||||
}
|
||||
|
||||
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void seekTo(final long positionMillis) {
|
||||
if (simpleExoPlayer != null) {
|
||||
long normalizedPositionMillis = positionMillis;
|
||||
if (normalizedPositionMillis < 0) {
|
||||
normalizedPositionMillis = 0;
|
||||
} else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) {
|
||||
normalizedPositionMillis = simpleExoPlayer.getDuration();
|
||||
}
|
||||
|
||||
simpleExoPlayer.seekTo(normalizedPositionMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private VideoSegment getSkippableSegment(final int progress) {
|
||||
if (videoSegments == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (final VideoSegment segment : videoSegments) {
|
||||
if (progress < segment.startTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (progress > segment.endTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
|
||||
public interface LocalPlayerListener {
|
||||
void onBlocked(SimpleExoPlayer player);
|
||||
void onPlaying(SimpleExoPlayer player);
|
||||
void onBuffering(SimpleExoPlayer player);
|
||||
void onPaused(SimpleExoPlayer player);
|
||||
void onPausedSeek(SimpleExoPlayer player);
|
||||
void onCompleted(SimpleExoPlayer player);
|
||||
}
|
Loading…
Reference in a new issue