Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Playback speed controls #1302

Merged
merged 14 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.jellyfin.androidtv.di

import org.jellyfin.androidtv.preference.*
import org.koin.android.ext.koin.androidApplication
import org.koin.dsl.binds
import org.koin.dsl.module

val preferenceModule = module {
Expand All @@ -10,6 +11,9 @@ val preferenceModule = module {
single { LiveTvPreferences(get(userApiClient)) }
single { UserSettingPreferences(get(userApiClient)) }
single { AuthenticationPreferences(androidApplication()) }
single { UserPreferences(androidApplication()) }
single { UserPreferences(androidApplication()) } binds arrayOf(
UserPreferences::class,
PreferenceStore::class
)
DavidFair marked this conversation as resolved.
Show resolved Hide resolved
single { SystemPreferences(androidApplication()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import org.jellyfin.androidtv.util.DeviceUtils
*
* @param context Context to get the SharedPreferences from
*/
class UserPreferences(context: Context) : SharedPreferenceStore(
class UserPreferences(context: Context) : PreferenceStore, SharedPreferenceStore(
DavidFair marked this conversation as resolved.
Show resolved Hide resolved
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
) {
companion object {
Expand Down Expand Up @@ -127,12 +127,14 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
/**
* Shortcut used for changing the audio track
*/
var shortcutAudioTrack = Preference.int("shortcut_audio_track", KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK)
var shortcutAudioTrack =
DavidFair marked this conversation as resolved.
Show resolved Hide resolved
Preference.int("shortcut_audio_track", KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK)

/**
* Shortcut used for changing the subtitle track
*/
var shortcutSubtitleTrack = Preference.int("shortcut_subtitle_track", KeyEvent.KEYCODE_CAPTIONS)
var shortcutSubtitleTrack =
Preference.int("shortcut_subtitle_track", KeyEvent.KEYCODE_CAPTIONS)

/* Developer options */
/**
Expand Down Expand Up @@ -174,13 +176,14 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
/**
* Set when watched indicators should show on MyImageCardViews
*/
var watchedIndicatorBehavior = Preference.enum("pref_watched_indicator_behavior", WatchedIndicatorBehavior.ALWAYS)
var watchedIndicatorBehavior =
Preference.enum("pref_watched_indicator_behavior", WatchedIndicatorBehavior.ALWAYS)

/**
* Enable series thumbnails in home screen rows
*/
var seriesThumbnailsEnabled = Preference.boolean("pref_enable_series_thumbnails", true)

/**
* Enable subtitles background
*/
Expand All @@ -201,21 +204,40 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
// Migrate to video player enum
// Note: This is the only time we need to check if the value is not set yet because the version numbers were reset
if (!it.contains("video_player"))
putEnum("video_player", if (it.getBoolean("pref_video_use_external", false)) PreferredVideoPlayer.EXTERNAL else PreferredVideoPlayer.AUTO)
putEnum(
"video_player",
if (it.getBoolean(
"pref_video_use_external",
false
)
) PreferredVideoPlayer.EXTERNAL else PreferredVideoPlayer.AUTO
)
}

// v0.11.x to v0.12.x
migration(toVersion = 5) {
// Migrate to audio behavior enum
putEnum("audio_behavior", if (it.getString("pref_audio_option", "0") == "1") AudioBehavior.DOWNMIX_TO_STEREO else AudioBehavior.DIRECT_STREAM)
putEnum(
"audio_behavior",
if (it.getString(
"pref_audio_option",
"0"
) == "1"
) AudioBehavior.DOWNMIX_TO_STEREO else AudioBehavior.DIRECT_STREAM
)

// Migrate live tv player to use enum
putEnum("live_tv_video_player",
putEnum(
"live_tv_video_player",
when {
it.getBoolean("pref_live_tv_use_external", false) -> PreferredVideoPlayer.EXTERNAL
it.getBoolean(
"pref_live_tv_use_external",
false
) -> PreferredVideoPlayer.EXTERNAL
it.getBoolean("pref_enable_vlc_livetv", false) -> PreferredVideoPlayer.VLC
else -> PreferredVideoPlayer.AUTO
})
}
)

// Change audio delay type from long to int
putInt("libvlc_audio_delay", it.getLong("libvlc_audio_delay", 0).toInt())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.view.Display;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.jellyfin.androidtv.R;
Expand All @@ -19,6 +20,7 @@
import org.jellyfin.androidtv.data.compat.SubtitleStreamInfo;
import org.jellyfin.androidtv.data.compat.VideoOptions;
import org.jellyfin.androidtv.data.model.DataRefreshService;
import org.jellyfin.androidtv.preference.PreferenceStore;
import org.jellyfin.androidtv.preference.SystemPreferences;
import org.jellyfin.androidtv.preference.UserPreferences;
import org.jellyfin.androidtv.preference.UserSettingPreferences;
Expand Down Expand Up @@ -66,7 +68,7 @@ public class PlaybackController {

private Lazy<ApiClient> apiClient = inject(ApiClient.class);
private Lazy<PlaybackManager> playbackManager = inject(PlaybackManager.class);
private Lazy<UserPreferences> userPreferences = inject(UserPreferences.class);
private Lazy<PreferenceStore> userPreferences = inject(PreferenceStore.class);
DavidFair marked this conversation as resolved.
Show resolved Hide resolved
private Lazy<SystemPreferences> systemPreferences = inject(SystemPreferences.class);
private Lazy<MediaManager> mediaManager = inject(MediaManager.class);

Expand All @@ -86,6 +88,7 @@ public class PlaybackController {
private VideoOptions mCurrentOptions;
private int mDefaultSubIndex = -1;
private int mDefaultAudioIndex = -1;
private double mRequestedPlaybackSpeed = -1.0;
DavidFair marked this conversation as resolved.
Show resolved Hide resolved

private PlayMethod mPlaybackMethod = PlayMethod.Transcode;

Expand Down Expand Up @@ -150,10 +153,17 @@ public PlayMethod getPlaybackMethod() {
return mPlaybackMethod;
}

public void setPlaybackMethod(PlayMethod value) {
public void setPlaybackMethod(@NonNull PlayMethod value) {
mPlaybackMethod = value;
}

public void setPlaybackSpeed(@NonNull Double speed) {
mRequestedPlaybackSpeed = speed;
if (hasInitializedVideoManager()) {
mVideoManager.setPlaybackSpeed(speed);
}
}

public BaseItemDto getCurrentlyPlayingItem() {
return mItems.size() > mCurrentIndex ? mItems.get(mCurrentIndex) : null;
}
Expand Down Expand Up @@ -719,6 +729,7 @@ private void startItem(BaseItemDto item, long position, StreamInfo response) {

// get subtitle info
mSubtitleStreams = response.GetSubtitleProfiles(false, apiClient.getValue().getApiUrl(), apiClient.getValue().getAccessToken());
mVideoManager.setPlaybackSpeed(mRequestedPlaybackSpeed);

if (mFragment != null) mFragment.updateDisplay();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.exoplayer2.DefaultRenderersFactory;
Expand Down Expand Up @@ -143,8 +144,13 @@ public void setNativeMode(boolean value) {
}
}

public boolean isNativeMode() { return nativeMode; }
public int getZoomMode() { return mZoomMode; }
public boolean isNativeMode() {
return nativeMode;
}

public int getZoomMode() {
return mZoomMode;
}

public void setZoom(int mode) {
mZoomMode = mode;
Expand Down Expand Up @@ -175,7 +181,7 @@ public void setMetaDuration(long duration) {
}

public long getDuration() {
if (nativeMode){
if (nativeMode) {
return mExoPlayer.getDuration() > 0 ? mExoPlayer.getDuration() : mMetaDuration;
} else {
return mVlcPlayer.getLength() > 0 ? mVlcPlayer.getLength() : mMetaDuration;
Expand Down Expand Up @@ -285,13 +291,14 @@ public long seekTo(long pos) {
mLastTime = mVlcPlayer.getTime();
Timber.i("VLC length in seek is: %d", mVlcPlayer.getLength());
try {
if (getDuration() > 0) mVlcPlayer.setPosition((float)pos / getDuration()); else mVlcPlayer.setTime(pos);
if (getDuration() > 0) mVlcPlayer.setPosition((float) pos / getDuration());
else mVlcPlayer.setTime(pos);

return pos;

} catch (Exception e) {
Timber.e(e, "Error seeking in VLC");
Utils.showToast(mActivity, mActivity.getString(R.string.seek_error));
Utils.showToast(mActivity, mActivity.getString(R.string.seek_error));
return -1;
}
}
Expand Down Expand Up @@ -349,7 +356,7 @@ public boolean setSubtitleTrack(int index, @Nullable List<MediaStream> allStream
} catch (IndexOutOfBoundsException e) {
Timber.e("Could not locate subtitle with index %s in vlc track info", index);
return false;
} catch (NullPointerException e){
} catch (NullPointerException e) {
Timber.e("No subtitle tracks found in player trying to set subtitle with index %s in vlc track info", index);
return false;
}
Expand Down Expand Up @@ -387,7 +394,7 @@ public void setAudioTrack(int ndx, List<MediaStream> allStreams) {
Timber.e("Could not locate audio with index %s in vlc track info", ndx);
mVlcPlayer.setAudioTrack(ndx);
return;
} catch (NullPointerException e){
} catch (NullPointerException e) {
Timber.e("No subtitle tracks found in player trying to set subtitle with index %s in vlc track info", ndx);
mVlcPlayer.setAudioTrack(vlcIndex);
return;
Expand All @@ -409,6 +416,19 @@ public void setAudioTrack(int ndx, List<MediaStream> allStreams) {
}
}

public void setPlaybackSpeed(@NonNull Double speed) {
if (speed < 0.25) {
Timber.w("Invalid playback speed requested: %d", speed);
return;
}

if (nativeMode) {
DavidFair marked this conversation as resolved.
Show resolved Hide resolved
mExoPlayer.setPlaybackSpeed(speed.floatValue());
} else {
mVlcPlayer.setRate(speed.floatValue());
}
}

public void setAudioDelay(long value) {
if (!nativeMode && mVlcPlayer != null) {
if (!mVlcPlayer.setAudioDelay(value * 1000)) {
Expand Down Expand Up @@ -437,7 +457,7 @@ public void setAudioMode() {
}

private void setVlcAudioOptions() {
if(!Utils.downMixAudio()) {
if (!Utils.downMixAudio()) {
mVlcPlayer.setAudioDigitalOutputEnabled(true);
} else {
setCompatibleAudio();
Expand Down Expand Up @@ -544,7 +564,7 @@ public void contractVideo(int height) {
Activity activity = TvApp.getApplication().getCurrentActivity();
int sw = activity.getWindow().getDecorView().getWidth();
int sh = activity.getWindow().getDecorView().getHeight();
float ar = (float)sw / sh;
float ar = (float) sw / sh;
lp.height = height;
lp.width = (int) Math.ceil(height * ar);
lp.rightMargin = ((lp.width - normalWidth) / 2) - 110;
Expand Down Expand Up @@ -610,10 +630,10 @@ private void changeSurfaceLayout(int videoWidth, int videoHeight, int videoVisib
double ar;
if (sarDen == sarNum) {
/* No indication about the density, assuming 1:1 */
ar = (double)videoVisibleWidth / (double)videoVisibleHeight;
ar = (double) videoVisibleWidth / (double) videoVisibleHeight;
} else {
/* Use the specified aspect ratio */
double vw = videoVisibleWidth * (double)sarNum / sarDen;
double vw = videoVisibleWidth * (double) sarNum / sarDen;
ar = vw / videoVisibleHeight;
}

Expand All @@ -627,7 +647,7 @@ private void changeSurfaceLayout(int videoWidth, int videoHeight, int videoVisib

// set display size
ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
lp.width = (int) Math.ceil(dw * videoWidth / videoVisibleWidth);
lp.width = (int) Math.ceil(dw * videoWidth / videoVisibleWidth);
lp.height = (int) Math.ceil(dh * videoHeight / videoVisibleHeight);
normalWidth = lp.width;
normalHeight = lp.height;
Expand Down Expand Up @@ -671,6 +691,7 @@ public void setOnProgressListener(PlaybackListener listener) {

private PlaybackListener progressListener;
private Runnable progressLoop;

private void startProgressLoop() {
progressLoop = new Runnable() {
@Override
Expand Down Expand Up @@ -716,7 +737,7 @@ public void onNewVideoLayout(IVLCVout vout, int width, int height, int visibleWi
mVideoHeight = height;
mVideoWidth = width;
mVideoVisibleHeight = visibleHeight;
mVideoVisibleWidth = visibleWidth;
mVideoVisibleWidth = visibleWidth;
mSarNum = sarNum;
mSarDen = sarDen;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.jellyfin.androidtv.ui.playback

class VideoSpeedController(playbackController: PlaybackController) {
enum class SpeedSteps(val speed: Double) {
// Use named parameter so detekt knows these aren't magic values
SPEED_0_25(speed = 0.25),
SPEED_0_50(speed = 0.5),
SPEED_0_75(speed = 0.75),
SPEED_1_00(speed = 1.0),
SPEED_1_25(speed = 1.25),
SPEED_1_50(speed = 1.50),
SPEED_1_75(speed = 1.75),
SPEED_2_00(speed = 2.0),
}

companion object {
private var previousSpeedSelection = SpeedSteps.SPEED_1_00
fun resetPreviousSpeedToDefault() {
previousSpeedSelection = SpeedSteps.SPEED_1_00
}
}

private val parentController = playbackController
DavidFair marked this conversation as resolved.
Show resolved Hide resolved

init {
// Carry forward the user's recent speed selection onto the next video(s)
setNewSpeed(previousSpeedSelection)
}

fun getCurrentSpeed(): SpeedSteps {
// Currently getCurrentSpeed uses previousSpeedSelection (from the companion)
// but this is an implementation detail I'd rather not leak in-case we ever need
// to separate out the two details. So implement a custom named getter...
return previousSpeedSelection
DavidFair marked this conversation as resolved.
Show resolved Hide resolved
}

fun setNewSpeed(selectedSpeed: SpeedSteps) {
previousSpeedSelection = selectedSpeed
parentController.setPlaybackSpeed(selectedSpeed.speed)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.jellyfin.androidtv.ui.playback.overlay.action.ChapterAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.ClosedCaptionsAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.GuideAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.PlaybackSpeedAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.PreviousLiveTvChannelAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.RecordAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.SelectAudioAction;
Expand All @@ -48,6 +49,7 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
private PlaybackControlsRow.SkipNextAction skipNextAction;
private SelectAudioAction selectAudioAction;
private ClosedCaptionsAction closedCaptionsAction;
private PlaybackSpeedAction playbackSpeedAction;
private AdjustAudioDelayAction adjustAudioDelayAction;
private ZoomAction zoomAction;
private ChapterAction chapterAction;
Expand Down Expand Up @@ -173,6 +175,8 @@ private void initActions(Context context) {
selectAudioAction.setLabels(new String[]{context.getString(R.string.lbl_audio_track)});
closedCaptionsAction = new ClosedCaptionsAction(context, this);
closedCaptionsAction.setLabels(new String[]{context.getString(R.string.lbl_subtitle_track)});
playbackSpeedAction = new PlaybackSpeedAction(context, this, playbackController);
playbackSpeedAction.setLabels(new String[]{context.getString(R.string.lbl_playback_speed)});
adjustAudioDelayAction = new AdjustAudioDelayAction(context, this);
adjustAudioDelayAction.setLabels(new String[]{context.getString(R.string.lbl_audio_delay)});
zoomAction = new ZoomAction(context, this);
Expand Down Expand Up @@ -219,6 +223,8 @@ void addMediaActions() {
primaryActionsAdapter.add(closedCaptionsAction);
}

primaryActionsAdapter.add(playbackSpeedAction);

if (hasMultiAudio()) {
primaryActionsAdapter.add(selectAudioAction);
}
Expand Down Expand Up @@ -282,6 +288,9 @@ public void onCustomActionClicked(Action action, View view) {
} else if (action == closedCaptionsAction) {
leanbackOverlayFragment.setFading(false);
closedCaptionsAction.handleClickAction(playbackController, leanbackOverlayFragment, getContext(), view);
} else if (action == playbackSpeedAction) {
leanbackOverlayFragment.setFading(false);
playbackSpeedAction.handleClickAction(playbackController, leanbackOverlayFragment, getContext(), view);
} else if (action == adjustAudioDelayAction) {
leanbackOverlayFragment.setFading(false);
adjustAudioDelayAction.handleClickAction(playbackController, leanbackOverlayFragment, getContext(), view);
Expand Down
Loading