mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-08 08:55:15 +01:00
-Added toggle to enable fast inexact seek in players.
-Improved player sync calls to recognize different metadata updates. -Changed MediaSourceManager to synchronize only after timeline changes and reenabled multiple sync calls to player. -Renamed listener and synchronization methods related to MediaSourceManager.
This commit is contained in:
parent
0c17f0825b
commit
a88e19a8ed
@ -380,11 +380,10 @@ public final class BackgroundPlayer extends Service {
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
super.sync(item, info);
|
||||
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
updateMetadata();
|
||||
@ -405,8 +404,8 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
public void onPlaybackShutdown() {
|
||||
super.onPlaybackShutdown();
|
||||
onClose();
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ import org.schabi.newpipe.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||
import org.schabi.newpipe.player.helper.LoadController;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playback.CustomTrackSelector;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
@ -196,6 +197,7 @@ public abstract class BasePlayer implements
|
||||
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
||||
}
|
||||
|
||||
public void initListeners() {}
|
||||
@ -688,7 +690,7 @@ public abstract class BasePlayer implements
|
||||
break;
|
||||
default:
|
||||
showUnrecoverableError(error);
|
||||
shutdown();
|
||||
onPlaybackShutdown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -774,9 +776,9 @@ public abstract class BasePlayer implements
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void block() {
|
||||
public void onPlaybackBlock() {
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Playback - block() called");
|
||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called");
|
||||
|
||||
currentItem = null;
|
||||
currentInfo = null;
|
||||
@ -787,9 +789,9 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unblock(final MediaSource mediaSource) {
|
||||
public void onPlaybackUnblock(final MediaSource mediaSource) {
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (DEBUG) Log.d(TAG, "Playback - unblock() called");
|
||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackUnblock() called");
|
||||
|
||||
if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING);
|
||||
|
||||
@ -798,34 +800,50 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
currentItem = item;
|
||||
currentInfo = info;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Playback - sync() called with " +
|
||||
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " +
|
||||
(info == null ? "available" : "null") + " info, " +
|
||||
"item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
|
||||
if (simpleExoPlayer == null) return;
|
||||
|
||||
final boolean hasPlayQueueItemChanged = currentItem != item;
|
||||
final boolean hasStreamInfoChanged = currentInfo != info;
|
||||
if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) {
|
||||
return; // Nothing to synchronize
|
||||
}
|
||||
|
||||
currentItem = item;
|
||||
currentInfo = info;
|
||||
if (hasPlayQueueItemChanged) {
|
||||
// updates only to the stream info should not trigger another view count
|
||||
registerView();
|
||||
initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl());
|
||||
}
|
||||
|
||||
final int currentSourceIndex = playQueue.indexOf(item);
|
||||
onMetadataChanged(item, info, currentSourceIndex, hasPlayQueueItemChanged);
|
||||
|
||||
// Check if on wrong window
|
||||
final int currentSourceIndex = playQueue.indexOf(item);
|
||||
if (simpleExoPlayer == null) return;
|
||||
if (currentSourceIndex != playQueue.getIndex()) {
|
||||
Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex +
|
||||
"], queue index=[" + playQueue.getIndex() + "]");
|
||||
} else if (simpleExoPlayer.getCurrentPeriodIndex() != currentSourceIndex || !isPlaying()) {
|
||||
|
||||
// on metadata changed
|
||||
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
|
||||
final long startPos = info != null ? info.start_position : 0;
|
||||
if (DEBUG) Log.d(TAG, "Rewinding to correct window=[" + currentSourceIndex + "]," +
|
||||
" at=[" + getTimeString((int)startPos) + "]," +
|
||||
" from=[" + simpleExoPlayer.getCurrentPeriodIndex() + "].");
|
||||
" from=[" + simpleExoPlayer.getCurrentWindowIndex() + "].");
|
||||
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
|
||||
}
|
||||
|
||||
registerView();
|
||||
initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
|
||||
}
|
||||
|
||||
abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) {
|
||||
@ -839,7 +857,7 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
public void onPlaybackShutdown() {
|
||||
if (DEBUG) Log.d(TAG, "Shutting down...");
|
||||
destroy();
|
||||
}
|
||||
|
@ -391,31 +391,32 @@ public final class MainVideoPlayer extends Activity {
|
||||
updatePlaybackButtons();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
|
||||
super.sync(item, info);
|
||||
titleTextView.setText(getVideoTitle());
|
||||
channelTextView.setText(getUploaderName());
|
||||
|
||||
//playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleClicked() {
|
||||
super.onShuffleClicked();
|
||||
updatePlaybackButtons();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
|
||||
|
||||
titleTextView.setText(getVideoTitle());
|
||||
channelTextView.setText(getUploaderName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackShutdown() {
|
||||
super.onPlaybackShutdown();
|
||||
finish();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player Overrides
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -535,7 +535,8 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
private void updatePlayback() {
|
||||
if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
|
||||
activityListener.onPlaybackUpdate(currentState, getRepeatMode(), playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
|
||||
activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
|
||||
playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
|
||||
}
|
||||
}
|
||||
|
||||
@ -574,16 +575,17 @@ public final class PopupVideoPlayer extends Service {
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) {
|
||||
if (currentItem == item && currentInfo == info) return;
|
||||
super.sync(item, info);
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
public void onPlaybackShutdown() {
|
||||
super.onPlaybackShutdown();
|
||||
onClose();
|
||||
}
|
||||
|
||||
|
@ -324,9 +324,10 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
|
||||
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
|
||||
|
||||
@Override
|
||||
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
|
||||
super.sync(item, info);
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
qualityTextView.setVisibility(View.GONE);
|
||||
playbackSpeedTextView.setVisibility(View.GONE);
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
@ -109,6 +110,12 @@ public class PlayerHelper {
|
||||
return isRememberingPopupDimensions(context, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SeekParameters getSeekParameters(@NonNull final Context context) {
|
||||
return isUsingInexactSeek(context, false) ?
|
||||
SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
||||
}
|
||||
|
||||
public static long getPreferredCacheSize(@NonNull final Context context) {
|
||||
return 64 * 1024 * 1024L;
|
||||
}
|
||||
@ -176,4 +183,8 @@ public class PlayerHelper {
|
||||
private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
}
|
||||
|
||||
private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ public class MediaSourceManager {
|
||||
* streams before will only be cached for future usage.
|
||||
*
|
||||
* @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource)
|
||||
* @see #update(int, MediaSource)
|
||||
* @see #update(int, MediaSource, Runnable)
|
||||
* */
|
||||
private final static int WINDOW_SIZE = 1;
|
||||
|
||||
@ -95,15 +95,13 @@ public class MediaSourceManager {
|
||||
* */
|
||||
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||
@NonNull private final CompositeDisposable loaderReactor;
|
||||
@NonNull private Set<PlayQueueItem> loadingItems;
|
||||
@NonNull private final Set<PlayQueueItem> loadingItems;
|
||||
@NonNull private final SerialDisposable syncReactor;
|
||||
|
||||
@NonNull private final AtomicBoolean isBlocked;
|
||||
|
||||
@NonNull private DynamicConcatenatingMediaSource sources;
|
||||
|
||||
@Nullable private PlayQueueItem syncedItem;
|
||||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue,
|
||||
@ -159,8 +157,6 @@ public class MediaSourceManager {
|
||||
loaderReactor.dispose();
|
||||
syncReactor.dispose();
|
||||
sources.releaseSource();
|
||||
|
||||
syncedItem = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,9 +178,7 @@ public class MediaSourceManager {
|
||||
public void reset() {
|
||||
if (DEBUG) Log.d(TAG, "reset() called.");
|
||||
|
||||
tryBlock();
|
||||
|
||||
syncedItem = null;
|
||||
maybeBlock();
|
||||
populateSources();
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -215,7 +209,7 @@ public class MediaSourceManager {
|
||||
|
||||
private void onPlayQueueChanged(final PlayQueueEvent event) {
|
||||
if (playQueue.isEmpty() && playQueue.isComplete()) {
|
||||
playbackListener.shutdown();
|
||||
playbackListener.onPlaybackShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -261,7 +255,7 @@ public class MediaSourceManager {
|
||||
}
|
||||
|
||||
if (!isPlayQueueReady()) {
|
||||
tryBlock();
|
||||
maybeBlock();
|
||||
playQueue.fetch();
|
||||
}
|
||||
playQueueReactor.request(1);
|
||||
@ -290,23 +284,23 @@ public class MediaSourceManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void tryBlock() {
|
||||
if (DEBUG) Log.d(TAG, "tryBlock() called.");
|
||||
private void maybeBlock() {
|
||||
if (DEBUG) Log.d(TAG, "maybeBlock() called.");
|
||||
|
||||
if (isBlocked.get()) return;
|
||||
|
||||
playbackListener.block();
|
||||
playbackListener.onPlaybackBlock();
|
||||
resetSources();
|
||||
|
||||
isBlocked.set(true);
|
||||
}
|
||||
|
||||
private void tryUnblock() {
|
||||
if (DEBUG) Log.d(TAG, "tryUnblock() called.");
|
||||
private void maybeUnblock() {
|
||||
if (DEBUG) Log.d(TAG, "maybeUnblock() called.");
|
||||
|
||||
if (isPlayQueueReady() && isPlaybackReady() && isBlocked.get()) {
|
||||
isBlocked.set(false);
|
||||
playbackListener.unblock(sources);
|
||||
playbackListener.onPlaybackUnblock(sources);
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,8 +308,8 @@ public class MediaSourceManager {
|
||||
// Metadata Synchronization TODO: maybe this should be a separate manager
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void sync() {
|
||||
if (DEBUG) Log.d(TAG, "sync() called.");
|
||||
private void maybeSync() {
|
||||
if (DEBUG) Log.d(TAG, "onPlaybackSynchronize() called.");
|
||||
|
||||
final PlayQueueItem currentItem = playQueue.getItem();
|
||||
if (isBlocked.get() || currentItem == null) return;
|
||||
@ -323,23 +317,25 @@ public class MediaSourceManager {
|
||||
final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info);
|
||||
final Consumer<Throwable> onError = throwable -> syncInternal(currentItem, null);
|
||||
|
||||
if (syncedItem != currentItem) {
|
||||
syncedItem = currentItem;
|
||||
final Disposable sync = currentItem.getStream()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError);
|
||||
syncReactor.set(sync);
|
||||
}
|
||||
final Disposable sync = currentItem.getStream()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError);
|
||||
syncReactor.set(sync);
|
||||
}
|
||||
|
||||
private void syncInternal(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
// Ensure the current item is up to date with the play queue
|
||||
if (playQueue.getItem() == item && playQueue.getItem() == syncedItem) {
|
||||
playbackListener.sync(item, info);
|
||||
if (playQueue.getItem() == item) {
|
||||
playbackListener.onPlaybackSynchronize(item, info);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeSynchronizePlayer() {
|
||||
maybeUnblock();
|
||||
maybeSync();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// MediaSource Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@ -404,8 +400,7 @@ public class MediaSourceManager {
|
||||
loaderReactor.add(loader);
|
||||
}
|
||||
|
||||
tryUnblock();
|
||||
sync();
|
||||
maybeSynchronizePlayer();
|
||||
}
|
||||
|
||||
private Single<ManagedMediaSource> getLoadedMediaSource(@NonNull final PlayQueueItem stream) {
|
||||
@ -431,17 +426,15 @@ public class MediaSourceManager {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Loaded: [" + item.getTitle() +
|
||||
"] with url: " + item.getUrl());
|
||||
|
||||
loadingItems.remove(item);
|
||||
|
||||
final int itemIndex = playQueue.indexOf(item);
|
||||
// Only update the playlist timeline for items at the current index or after.
|
||||
if (itemIndex >= playQueue.getIndex() && isCorrectionNeeded(item)) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Updating: [" + item.getTitle() +
|
||||
"] with url: " + item.getUrl());
|
||||
update(itemIndex, mediaSource);
|
||||
update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
|
||||
}
|
||||
|
||||
loadingItems.remove(item);
|
||||
tryUnblock();
|
||||
sync();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -533,10 +526,11 @@ public class MediaSourceManager {
|
||||
* this will modify the playback timeline prior to the index and may cause desynchronization
|
||||
* on the playing item between {@link PlayQueue} and {@link DynamicConcatenatingMediaSource}.
|
||||
* */
|
||||
private synchronized void update(final int index, @NonNull final MediaSource source) {
|
||||
private synchronized void update(final int index, @NonNull final MediaSource source,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
if (index < 0 || index >= sources.getSize()) return;
|
||||
|
||||
sources.addMediaSource(index + 1, source, () ->
|
||||
sources.removeMediaSource(index));
|
||||
sources.removeMediaSource(index, finalizingAction));
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public interface PlaybackListener {
|
||||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
void block();
|
||||
void onPlaybackBlock();
|
||||
|
||||
/**
|
||||
* Called when the stream at the current queue index is ready.
|
||||
@ -26,18 +26,16 @@ public interface PlaybackListener {
|
||||
*
|
||||
* May be called only when the player is blocked.
|
||||
* */
|
||||
void unblock(final MediaSource mediaSource);
|
||||
void onPlaybackUnblock(final MediaSource mediaSource);
|
||||
|
||||
/**
|
||||
* Called when the queue index is refreshed.
|
||||
* Signals to the listener to synchronize the player's window to the manager's
|
||||
* window.
|
||||
*
|
||||
* Occurs once only per play queue item change.
|
||||
*
|
||||
* May be called only after unblock is called.
|
||||
* May be called anytime at any amount once unblock is called.
|
||||
* */
|
||||
void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
|
||||
void onPlaybackSynchronize(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
|
||||
|
||||
/**
|
||||
* Requests the listener to resolve a stream info into a media source
|
||||
@ -55,5 +53,5 @@ public interface PlaybackListener {
|
||||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
void shutdown();
|
||||
void onPlaybackShutdown();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
<string name="player_gesture_controls_key" translatable="false">player_gesture_controls</string>
|
||||
<string name="resume_on_audio_focus_gain_key" translatable="false">resume_on_audio_focus_gain</string>
|
||||
<string name="popup_remember_size_pos_key" translatable="false">popup_remember_size_pos_key</string>
|
||||
<string name="use_inexact_seek_key" translatable="false">use_inexact_seek_key</string>
|
||||
|
||||
<string name="default_resolution_key" translatable="false">default_resolution</string>
|
||||
<string name="default_resolution_value" translatable="false">360p</string>
|
||||
|
@ -72,6 +72,8 @@
|
||||
<string name="black_theme_title">Black</string>
|
||||
<string name="popup_remember_size_pos_title">Remember popup size and position</string>
|
||||
<string name="popup_remember_size_pos_summary">Remember last size and position of popup</string>
|
||||
<string name="use_inexact_seek_title">Use fast inexact seek</string>
|
||||
<string name="use_inexact_seek_summary">Inexact seek allows the player to seek to positions faster with reduced precision</string>
|
||||
<string name="player_gesture_controls_title">Player gesture controls</string>
|
||||
<string name="player_gesture_controls_summary">Use gestures to control the brightness and volume of the player</string>
|
||||
<string name="show_search_suggestions_title">Search suggestions</string>
|
||||
|
@ -100,5 +100,10 @@
|
||||
android:summary="@string/popup_remember_size_pos_summary"
|
||||
android:title="@string/popup_remember_size_pos_title"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/use_inexact_seek_key"
|
||||
android:summary="@string/use_inexact_seek_summary"
|
||||
android:title="@string/use_inexact_seek_title"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
Loading…
Reference in New Issue
Block a user