Skip to content

Commit

Permalink
Extract existing callback logic into interface
Browse files Browse the repository at this point in the history
Many call-backs already exist between the VideoManager and
PlaybackController. But they are created on an ad-hoc basis in various
methods.

By extracting these out to a method it means we can:
- Remove the boilerplate of @OverRide methods in PlaybackController
- Easily refactor these into a seperate class long-term
- Add new handlers
- Pass parameters around in the future
- Not have everything dispatch to a simple onEvent.

This also makes extending the interface easier where we have exoplayer
only notifications (such as onPlaybackSpeedChanged). Since the VLC code
can simply call the notifier without going through a PlaybackListener
  • Loading branch information
DavidFair committed Apr 6, 2022
1 parent f3721eb commit d6866c3
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
import kotlin.Lazy;
import timber.log.Timber;

public class PlaybackController {
public class PlaybackController implements PlaybackControllerNotifiable {
// Frequency to report playback progress
private final static long PROGRESS_REPORTING_INTERVAL = TimeUtils.secondsToMillis(3);
// Frequency to report paused state
Expand Down Expand Up @@ -145,9 +145,9 @@ public CustomPlaybackOverlayFragment getFragment() {

public void init(VideoManager mgr, CustomPlaybackOverlayFragment fragment) {
mVideoManager = mgr;
mVideoManager.subscribe(this);
mFragment = fragment;
directStreamLiveTv = userPreferences.getValue().get(UserPreferences.Companion.getLiveTvDirectPlayEnabled());
setupCallbacks();
}

public void setItems(List<BaseItemDto> items) {
Expand Down Expand Up @@ -1396,107 +1396,98 @@ private void itemComplete() {
}
}

private void setupCallbacks() {

mVideoManager.setOnErrorListener(new PlaybackListener() {

@Override
public void onEvent() {
if (mFragment == null) {
playerErrorEncountered();
return;
}

if (isLiveTv && directStreamLiveTv) {
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_error_live_stream));
directStreamLiveTv = false;
} else {
String msg = mFragment.getString(R.string.video_error_unknown_error);
Timber.e("Playback error - %s", msg);
}
playerErrorEncountered();
}
});

@Override
public void onPlaybackSpeedChange(float newSpeed) {
// TODO, implement speed change handling
}

mVideoManager.setOnPreparedListener(new PlaybackListener() {
@Override
public void onEvent() {
if (mPlaybackState == PlaybackState.BUFFERING) {
if (mFragment != null) mFragment.setFadingEnabled(true);
@Override
public void onPrepared() {
if (mPlaybackState == PlaybackState.BUFFERING) {
if (mFragment != null) mFragment.setFadingEnabled(true);

mPlaybackState = PlaybackState.PLAYING;
mCurrentTranscodeStartTime = mCurrentStreamInfo.getPlayMethod() == PlayMethod.Transcode ? System.currentTimeMillis() : 0;
startReportLoop();
}
mPlaybackState = PlaybackState.PLAYING;
mCurrentTranscodeStartTime = mCurrentStreamInfo.getPlayMethod() == PlayMethod.Transcode ? System.currentTimeMillis() : 0;
startReportLoop();
}

Timber.i("Play method: %s", mCurrentStreamInfo.getPlayMethod() == PlayMethod.Transcode ? "Trans" : "Direct");
Timber.i("Play method: %s", mCurrentStreamInfo.getPlayMethod() == PlayMethod.Transcode ? "Trans" : "Direct");

if (mPlaybackState == PlaybackState.PAUSED) {
mPlaybackState = PlaybackState.PLAYING;
} else {
// select or disable subtitles
Integer currentSubtitleIndex = mCurrentOptions.getSubtitleStreamIndex();
if (mDefaultSubIndex >= 0 && currentSubtitleIndex != null && currentSubtitleIndex == mDefaultSubIndex) {
Timber.i("subtitle stream %s is already selected", mDefaultSubIndex);
} else {
if (mDefaultSubIndex < 0)
Timber.i("Turning off subs");
else
Timber.i("Enabling default sub stream: %d", mDefaultSubIndex);
switchSubtitleStream(mDefaultSubIndex);
}
if (mPlaybackState == PlaybackState.PAUSED) {
mPlaybackState = PlaybackState.PLAYING;
} else {
// select or disable subtitles
Integer currentSubtitleIndex = mCurrentOptions.getSubtitleStreamIndex();
if (mDefaultSubIndex >= 0 && currentSubtitleIndex != null && currentSubtitleIndex == mDefaultSubIndex) {
Timber.i("subtitle stream %s is already selected", mDefaultSubIndex);
} else {
if (mDefaultSubIndex < 0)
Timber.i("Turning off subs");
else
Timber.i("Enabling default sub stream: %d", mDefaultSubIndex);
switchSubtitleStream(mDefaultSubIndex);
}

// select an audio track
int eligibleAudioTrack = mDefaultAudioIndex;
// select an audio track
int eligibleAudioTrack = mDefaultAudioIndex;

// if track switching is done without rebuilding the stream, mCurrentOptions is updated
// otherwise, use the server default
if (mCurrentOptions.getAudioStreamIndex() != null) {
eligibleAudioTrack = mCurrentOptions.getAudioStreamIndex();
} else if (getCurrentMediaSource().getDefaultAudioStreamIndex() != null) {
eligibleAudioTrack = getCurrentMediaSource().getDefaultAudioStreamIndex();
}
switchAudioStream(eligibleAudioTrack);
}
// if track switching is done without rebuilding the stream, mCurrentOptions is updated
// otherwise, use the server default
if (mCurrentOptions.getAudioStreamIndex() != null) {
eligibleAudioTrack = mCurrentOptions.getAudioStreamIndex();
} else if (getCurrentMediaSource().getDefaultAudioStreamIndex() != null) {
eligibleAudioTrack = getCurrentMediaSource().getDefaultAudioStreamIndex();
}
});
switchAudioStream(eligibleAudioTrack);
}
}

@Override
public void onError() {
if (mFragment == null) {
playerErrorEncountered();
return;
}

mVideoManager.setOnProgressListener(new PlaybackListener() {
@Override
public void onEvent() {
refreshCurrentPosition();
if (isPlaying()) {
if (!spinnerOff) {
if (mStartPosition > 0) {
initialSeek(mStartPosition);
mStartPosition = 0;
} else {
finishedInitialSeek = true;
stopSpinner();
}
}
if (isLiveTv && directStreamLiveTv) {
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_error_live_stream));
directStreamLiveTv = false;
} else {
String msg = mFragment.getString(R.string.video_error_unknown_error);
Timber.e("Playback error - %s", msg);
}
playerErrorEncountered();
}

if (isLiveTv && mCurrentProgramEndTime > 0 && System.currentTimeMillis() >= mCurrentProgramEndTime) {
// crossed fire off an async routine to update the program info
updateTvProgramInfo();
}
if (mFragment != null && finishedInitialSeek)
mFragment.updateSubtitles(mCurrentPosition);
@Override
public void onCompletion() {
Timber.d("On Completion fired");
itemComplete();
}

@Override
public void onProgress() {
refreshCurrentPosition();
if (isPlaying()) {
if (!spinnerOff) {
if (mStartPosition > 0) {
initialSeek(mStartPosition);
mStartPosition = 0;
} else {
finishedInitialSeek = true;
stopSpinner();
}
if (mFragment != null)
mFragment.setCurrentTime(mCurrentPosition);
}
});

mVideoManager.setOnCompletionListener(new PlaybackListener() {
@Override
public void onEvent() {
Timber.d("On Completion fired");
itemComplete();
if (isLiveTv && mCurrentProgramEndTime > 0 && System.currentTimeMillis() >= mCurrentProgramEndTime) {
// crossed fire off an async routine to update the program info
updateTvProgramInfo();
}
});
if (mFragment != null && finishedInitialSeek)
mFragment.updateSubtitles(mCurrentPosition);
}
if (mFragment != null)
mFragment.setCurrentTime(mCurrentPosition);
}

public long getDuration() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.jellyfin.androidtv.ui.playback

interface PlaybackControllerNotifiable {
fun onCompletion()
fun onError()
fun onPrepared()
fun onProgress()
fun onPlaybackSpeedChange(newSpeed: Float)
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
private int mZoomMode = ZOOM_FIT;

private PlaybackOverlayActivity mActivity;
private PlaybackControllerNotifiable mPlaybackControllerNotifiable;
private SurfaceHolder mSurfaceHolder;
private SurfaceView mSurfaceView;
private SurfaceView mSubtitlesSurface;
Expand Down Expand Up @@ -88,9 +89,6 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
private boolean nativeMode = false;
private boolean mSurfaceReady = false;
public boolean isContracted = false;
private PlaybackListener errorListener;
private PlaybackListener completionListener;
private PlaybackListener preparedListener;

public VideoManager(PlaybackOverlayActivity activity, View view) {
mActivity = activity;
Expand All @@ -114,14 +112,14 @@ public VideoManager(PlaybackOverlayActivity activity, View view) {
@Override
public void onPlayerError(@NonNull PlaybackException error) {
Timber.e("***** Got error from player");
if (errorListener != null) errorListener.onEvent();
if (mPlaybackControllerNotifiable != null) mPlaybackControllerNotifiable.onError();
stopProgressLoop();
}

@Override
public void onIsPlayingChanged(boolean isPlaying) {
if (isPlaying) {
if (preparedListener != null) preparedListener.onEvent();
if (mPlaybackControllerNotifiable != null) mPlaybackControllerNotifiable.onPrepared();
startProgressLoop();
} else {
stopProgressLoop();
Expand All @@ -135,11 +133,18 @@ public void onPlaybackStateChanged(int playbackState) {
}

if (playbackState == Player.STATE_ENDED) {
if (completionListener != null) completionListener.onEvent();
if (mPlaybackControllerNotifiable != null) mPlaybackControllerNotifiable.onCompletion();
stopProgressLoop();
}
}

@Override
public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) {
if (mPlaybackControllerNotifiable != null){
mPlaybackControllerNotifiable.onPlaybackSpeedChange(playbackParameters.speed);
}
}

@Override
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
// discontinuity for reason internal usually indicates an error, and that the player will reset to its default timestamp
Expand All @@ -161,6 +166,11 @@ public void onTracksInfoChanged(TracksInfo tracksInfo) {
});
}

public void subscribe(PlaybackControllerNotifiable notifier){
mPlaybackControllerNotifiable = notifier;
setupVLCListeners();
}

/**
* Configures Exoplayer for video playback. Initially we try with core decoders, but allow
* ExoPlayer to silently fallback to software renderers.
Expand Down Expand Up @@ -686,6 +696,10 @@ public void setPlaybackSpeed(float speed) {
mExoPlayer.setPlaybackParameters(new PlaybackParameters(speed));
} else {
mVlcPlayer.setRate(speed);
// VLC will always change rate, so we can post this immediately
if (mPlaybackControllerNotifiable != null){
mPlaybackControllerNotifiable.onPlaybackSpeedChange(speed);
}
}
}

Expand Down Expand Up @@ -741,6 +755,7 @@ public org.videolan.libvlc.MediaPlayer.TrackDescription[] getSubtitleTracks() {
}

public void destroy() {
mPlaybackControllerNotifiable = null;
stopPlayback();
releasePlayer();
}
Expand Down Expand Up @@ -814,7 +829,6 @@ private void releasePlayer() {
mExoPlayer.release();
mExoPlayer = null;
}
clearPlayerListeners();
}

int normalWidth;
Expand Down Expand Up @@ -929,43 +943,36 @@ private void changeSurfaceLayout(int videoWidth, int videoHeight, int videoVisib
mSubtitlesSurface.invalidate();
}

public void setOnErrorListener(final PlaybackListener listener) {
mVlcHandler.setOnErrorListener(listener);
errorListener = listener;
}

public void setOnCompletionListener(final PlaybackListener listener) {
completionListener = listener;
mVlcHandler.setOnCompletionListener(listener);
}

public void setOnPreparedListener(final PlaybackListener listener) {
preparedListener = listener;

mVlcHandler.setOnPreparedListener(listener);
}

public void setOnProgressListener(PlaybackListener listener) {
progressListener = listener;
mVlcHandler.setOnProgressListener(listener);
}

public void clearPlayerListeners() {
mVlcHandler.setOnErrorListener(null);
mVlcHandler.setOnCompletionListener(null);
mVlcHandler.setOnPreparedListener(null);
mVlcHandler.setOnProgressListener(null);
private void setupVLCListeners() {
mVlcHandler.setOnCompletionListener(() -> {
if (mPlaybackControllerNotifiable != null) {
mPlaybackControllerNotifiable.onCompletion();
}
});
mVlcHandler.setOnErrorListener(() -> {
if (mPlaybackControllerNotifiable != null) {
mPlaybackControllerNotifiable.onError();
}
});
mVlcHandler.setOnPreparedListener(() -> {
if (mPlaybackControllerNotifiable != null) {
mPlaybackControllerNotifiable.onPrepared();
}
});
mVlcHandler.setOnProgressListener(() -> {
if (mPlaybackControllerNotifiable != null) {
mPlaybackControllerNotifiable.onProgress();
}
});
}

private PlaybackListener progressListener;
private Runnable progressLoop;

private void startProgressLoop() {
stopProgressLoop();
progressLoop = new Runnable() {
@Override
public void run() {
if (progressListener != null) progressListener.onEvent();
if (mPlaybackControllerNotifiable != null) mPlaybackControllerNotifiable.onProgress();
mHandler.postDelayed(this, 500);
}
};
Expand Down

0 comments on commit d6866c3

Please sign in to comment.