add seperate button to adjust subtitle delay (if subtitles are present). (#1731)

* add seperate button to adjust subtitle delay (if subtitles are present).

* merge subtitle delay action and audio delay action to single button.

* Remove "m" prefix
Fix magic numbers
Fix formatting
Remove superfluous member variables

* Changes after review
Move all logic from `init` to constructor.

* Minor changes to SettingsPopup

Co-authored-by: Tim van Katwijk <tim.van.katwijk@moba.net>
This commit is contained in:
Timmeh
2022-05-29 11:12:46 +02:00
committed by GitHub
parent ba38e778dd
commit 4c2ce10004
12 changed files with 184 additions and 124 deletions

View File

@@ -127,6 +127,11 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
*/
var libVLCAudioDelay = intPreference("libvlc_audio_delay", 0)
/**
* Default audio subtitle in milliseconds for libVLC
*/
var libVLCSubtitleDelay = intPreference("libvlc_subtitle_delay", 0)
/* Live TV */
/**
* Use direct play

View File

@@ -1,52 +0,0 @@
package org.jellyfin.androidtv.ui;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.PopupWindow;
import androidx.annotation.Nullable;
import org.jellyfin.androidtv.databinding.AudioDelayPopupBinding;
import org.jellyfin.androidtv.util.Utils;
public class AudioDelayPopup {
private PopupWindow mPopup;
private View mAnchor;
private NumberSpinnerView mDelaySpinner;
public AudioDelayPopup(Context context, View anchor, ValueChangedListener<Long> listener) {
LayoutInflater inflater = LayoutInflater.from(context);
AudioDelayPopupBinding binding = AudioDelayPopupBinding.inflate(inflater, null, false);
int width = Utils.convertDpToPixel(context, 240);
int height = Utils.convertDpToPixel(context, 130);
mPopup = new PopupWindow(binding.getRoot(), width, height);
mPopup.setFocusable(true);
mPopup.setOutsideTouchable(true);
mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); // necessary for popup to dismiss
mAnchor = anchor;
mDelaySpinner = binding.numberSpinner;
mDelaySpinner.setValueChangedListener(listener);
}
@Nullable
public PopupWindow getPopupWindow() {
return mPopup;
}
public boolean isShowing() {
return (mPopup != null && mPopup.isShowing());
}
public void show(long value) {
mDelaySpinner.setValue(value);
mPopup.showAsDropDown(mAnchor, 0, 0, Gravity.END);
}
}

View File

@@ -0,0 +1,55 @@
package org.jellyfin.androidtv.ui
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.PopupWindow
import androidx.core.view.isVisible
import org.jellyfin.androidtv.databinding.SettingsPopupBinding
import org.jellyfin.androidtv.util.dp
class SettingsPopup(
private val context: Context,
private val anchor: View,
audioChangedListener: ValueChangedListener<Long>?,
subtitleChangedListener: ValueChangedListener<Long>?
) {
private val binding = SettingsPopupBinding.inflate(LayoutInflater.from(context), null, false)
.apply {
subtitleDelay.increment = SUBTITLE_DELAY_INCREMENT
subtitleDelay.valueChangedListener = subtitleChangedListener
audioDelay.valueChangedListener = audioChangedListener
}
val popupWindow = PopupWindow(binding.root, WIDTH.dp(context), FULL_HEIGHT.dp(context))
.apply {
isFocusable = true
isOutsideTouchable = true
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
fun show(
hasSubtitles: Boolean,
audioDelay: Long,
subtitleDelay: Long
) {
binding.audioDelay.value = audioDelay
binding.subtitleDelayTxt.isVisible = hasSubtitles
binding.subtitleDelay.isVisible = hasSubtitles
binding.subtitleDelay.value = subtitleDelay
popupWindow.height = (if (hasSubtitles) FULL_HEIGHT else SINGLE_HEIGHT).dp(context)
popupWindow.showAsDropDown(anchor, 0, 0, Gravity.END)
}
companion object {
private const val WIDTH = 240
private const val FULL_HEIGHT = 230
private const val SINGLE_HEIGHT = 130
private const val SUBTITLE_DELAY_INCREMENT = 500L
}
}

View File

@@ -264,6 +264,14 @@ public class PlaybackController implements PlaybackControllerNotifiable {
return hasInitializedVideoManager() ? mVideoManager.getAudioDelay() : 0;
}
public void setSubtitleDelay(long value) {
if (hasInitializedVideoManager()) mVideoManager.setSubtitleDelay(value);
}
public long getSubtitleDelay() {
return hasInitializedVideoManager() ? mVideoManager.getSubtitleDelay() : 0;
}
public void playerErrorEncountered() {
if (isNativeMode()) exoErrorEncountered = true;
else vlcErrorEncountered = true;

View File

@@ -680,6 +680,20 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
}
}
public void setSubtitleDelay(long value) {
if (!nativeMode && mVlcPlayer != null) {
if (!mVlcPlayer.setSpuDelay(value * 1000)) {
Timber.e("Error setting subtitle offset");
} else {
Timber.i("Subtitle offset set to %d", value);
}
}
}
public long getSubtitleDelay() {
return mVlcPlayer != null ? mVlcPlayer.getSpuDelay() / 1000 : 0;
}
public void setAudioDelay(long value) {
if (!nativeMode && mVlcPlayer != null) {
if (!mVlcPlayer.setAudioDelay(value * 1000)) {

View File

@@ -28,7 +28,6 @@ import org.jellyfin.androidtv.preference.UserPreferences;
import org.jellyfin.androidtv.preference.constant.ClockBehavior;
import org.jellyfin.androidtv.ui.livetv.TvManager;
import org.jellyfin.androidtv.ui.playback.PlaybackController;
import org.jellyfin.androidtv.ui.playback.overlay.action.AdjustAudioDelayAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.ChannelBarChannelAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.ChapterAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.ClosedCaptionsAction;
@@ -37,6 +36,7 @@ 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;
import org.jellyfin.androidtv.ui.playback.overlay.action.SettingAction;
import org.jellyfin.androidtv.ui.playback.overlay.action.ZoomAction;
import org.koin.java.KoinJavaComponent;
@@ -51,7 +51,7 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
private PlaybackControlsRow.SkipNextAction skipNextAction;
private SelectAudioAction selectAudioAction;
private ClosedCaptionsAction closedCaptionsAction;
private AdjustAudioDelayAction adjustAudioDelayAction;
private SettingAction settingAction;
private PlaybackSpeedAction playbackSpeedAction;
private ZoomAction zoomAction;
private ChapterAction chapterAction;
@@ -179,8 +179,8 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
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)});
adjustAudioDelayAction = new AdjustAudioDelayAction(context, this);
adjustAudioDelayAction.setLabels(new String[]{context.getString(R.string.lbl_audio_delay)});
settingAction = new SettingAction(context, this);
settingAction.setLabels(new String[]{context.getString(R.string.lbl_adjust)});
playbackSpeedAction = new PlaybackSpeedAction(context, this, playbackController);
playbackSpeedAction.setLabels(new String[]{context.getString(R.string.lbl_playback_speed)});
zoomAction = new ZoomAction(context, this);
@@ -262,7 +262,7 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
if (!isNativeMode()) {
secondaryActionsAdapter.add(adjustAudioDelayAction);
secondaryActionsAdapter.add(settingAction);
} else {
secondaryActionsAdapter.add(zoomAction);
}
@@ -296,6 +296,10 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
} else if (action == closedCaptionsAction) {
getPlayerAdapter().getLeanbackOverlayFragment().setFading(false);
closedCaptionsAction.handleClickAction(playbackController, getPlayerAdapter().getLeanbackOverlayFragment(), getContext(), view);
} else if (action == settingAction) {
getPlayerAdapter().getLeanbackOverlayFragment().setFading(false);
settingAction.setSubtitlesPresent(hasSubs());
settingAction.handleClickAction(playbackController, getPlayerAdapter().getLeanbackOverlayFragment(), getContext(), view);
} else if (action == playbackSpeedAction) {
getPlayerAdapter().getLeanbackOverlayFragment().setFading(false);
playbackSpeedAction.handleClickAction(playbackController, getPlayerAdapter().getLeanbackOverlayFragment(), getContext(), view);
@@ -303,9 +307,6 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
// This is a hack, we should instead have onPlaybackParametersChanged call out to this
// class to notify rather than poll. But communication is unidirectional at the moment:
mHandler.postDelayed(mRefreshEndTime, 5000); // 5 seconds
} else if (action == adjustAudioDelayAction) {
getPlayerAdapter().getLeanbackOverlayFragment().setFading(false);
adjustAudioDelayAction.handleClickAction(playbackController, getPlayerAdapter().getLeanbackOverlayFragment(), getContext(), view);
} else if (action == zoomAction) {
getPlayerAdapter().getLeanbackOverlayFragment().setFading(false);
zoomAction.handleClickAction(playbackController, getPlayerAdapter().getLeanbackOverlayFragment(), getContext(), view);

View File

@@ -1,39 +0,0 @@
package org.jellyfin.androidtv.ui.playback.overlay.action;
import android.content.Context;
import android.view.View;
import android.widget.PopupWindow;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.ui.AudioDelayPopup;
import org.jellyfin.androidtv.ui.ValueChangedListener;
import org.jellyfin.androidtv.ui.playback.PlaybackController;
import org.jellyfin.androidtv.ui.playback.overlay.CustomPlaybackTransportControlGlue;
import org.jellyfin.androidtv.ui.playback.overlay.LeanbackOverlayFragment;
public class AdjustAudioDelayAction extends CustomAction {
public AdjustAudioDelayAction(Context context, CustomPlaybackTransportControlGlue customPlaybackTransportControlGlue) {
super(context, customPlaybackTransportControlGlue);
initializeWithIcon(R.drawable.ic_adjust);
}
@Override
public void handleClickAction(PlaybackController playbackController, LeanbackOverlayFragment leanbackOverlayFragment, Context context, View view) {
AudioDelayPopup audioDelayPopup = new AudioDelayPopup(context, view, new ValueChangedListener<Long>() {
@Override
public void onValueChanged(Long value) {
playbackController.setAudioDelay(value);
}
});
PopupWindow popupWindow = audioDelayPopup.getPopupWindow();
if (popupWindow != null) {
popupWindow.setOnDismissListener(() -> {
leanbackOverlayFragment.setFading(true);
});
}
audioDelayPopup.show(playbackController.getAudioDelay());
}
}

View File

@@ -0,0 +1,36 @@
package org.jellyfin.androidtv.ui.playback.overlay.action
import android.content.Context
import android.view.View
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.SettingsPopup
import org.jellyfin.androidtv.ui.playback.PlaybackController
import org.jellyfin.androidtv.ui.playback.overlay.CustomPlaybackTransportControlGlue
import org.jellyfin.androidtv.ui.playback.overlay.LeanbackOverlayFragment
class SettingAction(
context: Context,
customPlaybackTransportControlGlue: CustomPlaybackTransportControlGlue
) : CustomAction(context, customPlaybackTransportControlGlue) {
var subtitlesPresent = false
init {
initializeWithIcon(R.drawable.ic_adjust)
}
override fun handleClickAction(
playbackController: PlaybackController,
leanbackOverlayFragment: LeanbackOverlayFragment,
context: Context,
view: View
) = SettingsPopup(context, view,
{ value -> playbackController.audioDelay = value },
{ value -> playbackController.subtitleDelay = value }
).apply {
popupWindow.setOnDismissListener { leanbackOverlayFragment.setFading(true) }
}.show(
subtitlesPresent,
playbackController.audioDelay,
playbackController.subtitleDelay
)
}

View File

@@ -151,6 +151,17 @@ class PlaybackPreferencesScreen : OptionsFragment() {
bind(userPreferences, UserPreferences.subtitlesBackgroundEnabled)
}
@Suppress("MagicNumber")
seekbar {
setTitle(R.string.pref_libvlc_subtitle_delay_title)
min = -50_000
max = 50_000
valueFormatter = object : DurationSeekBarPreference.ValueFormatter() {
override fun display(value: Int) = "${value}ms"
}
bind(userPreferences, UserPreferences.libVLCSubtitleDelay)
}
@Suppress("MagicNumber")
seekbar {
setTitle(R.string.pref_subtitles_size)

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/popupWindowBackground"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/lbl_audio_delay"
android:id="@+id/textView18"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<org.jellyfin.androidtv.ui.NumberSpinnerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/numberSpinner"
android:layout_below="@+id/textView18"
android:layout_centerHorizontal="true"
android:layout_marginTop="15sp" />
</RelativeLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/popupWindowBackground"
android:padding="20dp">
<TextView
android:id="@+id/audioDelaytxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/lbl_audio_delay"
android:textAppearance="?android:attr/textAppearanceMedium" />
<org.jellyfin.androidtv.ui.NumberSpinnerView
android:id="@+id/audioDelay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/audioDelaytxt"
android:layout_centerHorizontal="true"
android:layout_marginTop="15sp" />
<TextView
android:id="@+id/subtitleDelayTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="15sp"
android:layout_below="@+id/audioDelay"
android:text="@string/lbl_subtitle_delay"
android:textAppearance="?android:attr/textAppearanceMedium" />
<org.jellyfin.androidtv.ui.NumberSpinnerView
android:id="@+id/subtitleDelay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/subtitleDelayTxt"
android:layout_centerHorizontal="true"
android:layout_marginTop="15sp" />
</RelativeLayout>

View File

@@ -261,6 +261,8 @@
<string name="lbl_news">News</string>
<string name="lbl_new_only">New Only</string>
<string name="lbl_audio_delay">Audio Delay</string>
<string name="lbl_adjust">Adjust</string>
<string name="lbl_subtitle_delay">Subtitle Delay</string>
<string name="lbl_new_episodes">New Episodes</string>
<string name="lbl_premieres">Premieres</string>
<string name="lbl_repeat_episodes">Repeat Episodes</string>
@@ -285,7 +287,8 @@
<string name="pref_next_up_timeout_disabled"></string>
<string name="watch_now">Watch now</string>
<string name="pref_next_up_behavior_title">Show next video info</string>
<string name="pref_libvlc_audio_delay_title">Default Audio Delay for LibVLC</string>
<string name="pref_libvlc_audio_delay_title">Default audio delay for LibVLC</string>
<string name="pref_libvlc_subtitle_delay_title">Default subtitle delay for LibVLC</string>
<string name="pref_app_theme">App theme</string>
<string name="pref_device_model">Device model</string>
<string name="grid_direction">Grid Direction</string>