From 4e1423d224296e89551636ec5e02ccc94ef7d421 Mon Sep 17 00:00:00 2001 From: Vasiliy Date: Sat, 13 Apr 2019 10:31:32 +0300 Subject: [PATCH] Implement playback state management --- .../org/schabi/newpipe/RouterActivity.java | 8 +- .../history/dao/StreamHistoryDAO.java | 5 + .../stream/model/StreamStateEntity.java | 14 + .../fragments/detail/VideoDetailFragment.java | 64 +- .../fragments/list/BaseListFragment.java | 6 +- .../list/channel/ChannelFragment.java | 16 +- .../list/playlist/PlaylistFragment.java | 22 +- .../local/history/HistoryRecordManager.java | 49 +- .../history/StatisticsPlaylistFragment.java | 16 +- .../local/playlist/LocalPlaylistFragment.java | 16 +- .../newpipe/player/BackgroundPlayer.java | 1 + .../org/schabi/newpipe/player/BasePlayer.java | 71 +- .../newpipe/player/MainVideoPlayer.java | 12 +- .../newpipe/player/PopupVideoPlayer.java | 4 +- .../newpipe/player/ServicePlayerActivity.java | 3 +- .../schabi/newpipe/player/VideoPlayer.java | 5 + .../util/CommentTextOnTouchListener.java | 2 +- .../schabi/newpipe/util/NavigationHelper.java | 47 +- .../newpipe/views/AnimatedProgressBar.java | 69 ++ .../progress_soundcloud_horizontal_dark.xml | 15 + .../progress_soundcloud_horizontal_light.xml | 15 + .../progress_youtube_horizontal_dark.xml | 15 + .../progress_youtube_horizontal_light.xml | 15 + .../fragment_video_detail.xml | 77 +- .../main/res/layout/fragment_video_detail.xml | 913 +++++++++--------- app/src/main/res/values-ru/strings.xml | 5 +- app/src/main/res/values-uk/strings.xml | 5 +- app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 5 +- app/src/main/res/values/styles.xml | 2 + app/src/main/res/values/styles_services.xml | 3 + app/src/main/res/xml/history_settings.xml | 58 +- 33 files changed, 978 insertions(+), 582 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java create mode 100644 app/src/main/res/drawable/progress_soundcloud_horizontal_dark.xml create mode 100644 app/src/main/res/drawable/progress_soundcloud_horizontal_light.xml create mode 100644 app/src/main/res/drawable/progress_youtube_horizontal_dark.xml create mode 100644 app/src/main/res/drawable/progress_youtube_horizontal_light.xml diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index f040dc8b4..c7bf4c881 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -574,7 +574,7 @@ public class RouterActivity extends AppCompatActivity { playQueue = new SinglePlayQueue((StreamInfo) info); if (playerChoice.equals(videoPlayerKey)) { - NavigationHelper.playOnMainPlayer(this, playQueue); + NavigationHelper.playOnMainPlayer(this, playQueue, true); } else if (playerChoice.equals(backgroundPlayerKey)) { NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); } else if (playerChoice.equals(popupPlayerKey)) { @@ -587,11 +587,11 @@ public class RouterActivity extends AppCompatActivity { playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); if (playerChoice.equals(videoPlayerKey)) { - NavigationHelper.playOnMainPlayer(this, playQueue); + NavigationHelper.playOnMainPlayer(this, playQueue, true); } else if (playerChoice.equals(backgroundPlayerKey)) { - NavigationHelper.playOnBackgroundPlayer(this, playQueue); + NavigationHelper.playOnBackgroundPlayer(this, playQueue, true); } else if (playerChoice.equals(popupPlayerKey)) { - NavigationHelper.playOnPopupPlayer(this, playQueue); + NavigationHelper.playOnPopupPlayer(this, playQueue, true); } } }; diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java index 847153e12..50d723f1f 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java @@ -50,6 +50,11 @@ public abstract class StreamHistoryDAO implements HistoryDAO> getHistory(); + @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + + " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") + @Nullable + public abstract StreamHistoryEntity getLatestEntry(final long streamId); + @Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") public abstract int deleteStreamHistory(final long streamId); diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java index 15940a964..946ee1182 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java @@ -5,6 +5,8 @@ import android.arch.persistence.room.ColumnInfo; import android.arch.persistence.room.Entity; import android.arch.persistence.room.ForeignKey; +import java.util.concurrent.TimeUnit; + import static android.arch.persistence.room.ForeignKey.CASCADE; import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID; import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; @@ -22,6 +24,12 @@ public class StreamStateEntity { final public static String JOIN_STREAM_ID = "stream_id"; final public static String STREAM_PROGRESS_TIME = "progress_time"; + + /** Playback state will not be saved, if playback time less than this threshold */ + private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5; + /** Playback state will not be saved, if time left less than this threshold */ + private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10; + @ColumnInfo(name = JOIN_STREAM_ID) private long streamUid; @@ -48,4 +56,10 @@ public class StreamStateEntity { public void setProgressTime(long progressTime) { this.progressTime = progressTime; } + + public boolean isValid(int durationInSeconds) { + final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime); + return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS + && seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS; + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index bbd1a315d..d6630c9c3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -89,12 +89,14 @@ import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.StreamItemAdapter; import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; +import org.schabi.newpipe.views.AnimatedProgressBar; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.TimeUnit; import icepick.State; import io.reactivex.Single; @@ -118,7 +120,7 @@ public class VideoDetailFragment private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2; private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; - private static final int COMMENTS_UPDATE_FLAG = 0x4; + private static final int COMMENTS_UPDATE_FLAG = 0x8; private boolean autoPlayEnabled; private boolean showRelatedStreams; @@ -136,6 +138,8 @@ public class VideoDetailFragment private Disposable currentWorker; @NonNull private CompositeDisposable disposables = new CompositeDisposable(); + @Nullable + private Disposable positionSubscriber = null; private List sortedVideoStreams; private int selectedVideoStreamIndex = -1; @@ -153,6 +157,7 @@ public class VideoDetailFragment private View thumbnailBackgroundButton; private ImageView thumbnailImageView; private ImageView thumbnailPlayButton; + private AnimatedProgressBar positionView; private View videoTitleRoot; private TextView videoTitleTextView; @@ -165,6 +170,7 @@ public class VideoDetailFragment private TextView detailControlsDownload; private TextView appendControlsDetail; private TextView detailDurationView; + private TextView detailPositionView; private LinearLayout videoDescriptionRootLayout; private TextView videoUploadDateView; @@ -259,6 +265,8 @@ public class VideoDetailFragment // Check if it was loading when the fragment was stopped/paused, if (wasLoading.getAndSet(false)) { selectAndLoadVideo(serviceId, url, name); + } else if (currentInfo != null) { + updateProgressInfo(currentInfo); } } @@ -268,8 +276,10 @@ public class VideoDetailFragment PreferenceManager.getDefaultSharedPreferences(activity) .unregisterOnSharedPreferenceChangeListener(this); + if (positionSubscriber != null) positionSubscriber.dispose(); if (currentWorker != null) currentWorker.dispose(); if (disposables != null) disposables.clear(); + positionSubscriber = null; currentWorker = null; disposables = null; } @@ -462,6 +472,7 @@ public class VideoDetailFragment videoTitleTextView = rootView.findViewById(R.id.detail_video_title_view); videoTitleToggleArrow = rootView.findViewById(R.id.detail_toggle_description_view); videoCountView = rootView.findViewById(R.id.detail_view_count_view); + positionView = rootView.findViewById(R.id.position_view); detailControlsBackground = rootView.findViewById(R.id.detail_controls_background); detailControlsPopup = rootView.findViewById(R.id.detail_controls_popup); @@ -469,6 +480,7 @@ public class VideoDetailFragment detailControlsDownload = rootView.findViewById(R.id.detail_controls_download); appendControlsDetail = rootView.findViewById(R.id.touch_append_detail); detailDurationView = rootView.findViewById(R.id.detail_duration_view); + detailPositionView = rootView.findViewById(R.id.detail_position_view); videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout); videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view); @@ -536,10 +548,10 @@ public class VideoDetailFragment final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> { switch (i) { case 0: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), true); break; case 1: - NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item)); + NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item), true); break; case 2: if (getFragmentManager() != null) { @@ -890,11 +902,11 @@ public class VideoDetailFragment final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); if (append) { - NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue); + NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue, false); } else { Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); final Intent intent = NavigationHelper.getPlayerIntent( - activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution + activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution, true ); activity.startService(intent); } @@ -914,9 +926,9 @@ public class VideoDetailFragment private void openNormalBackgroundPlayer(final boolean append) { final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); if (append) { - NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue); + NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue, false); } else { - NavigationHelper.playOnBackgroundPlayer(activity, itemQueue); + NavigationHelper.playOnBackgroundPlayer(activity, itemQueue, true); } } @@ -926,7 +938,7 @@ public class VideoDetailFragment mIntent = NavigationHelper.getPlayerIntent(activity, MainVideoPlayer.class, playQueue, - getSelectedVideoStream().getResolution()); + getSelectedVideoStream().getResolution(), true); startActivity(mIntent); } @@ -1032,6 +1044,8 @@ public class VideoDetailFragment animateView(spinnerToolbar, false, 200); animateView(thumbnailPlayButton, false, 50); animateView(detailDurationView, false, 100); + animateView(detailPositionView, false, 100); + animateView(positionView, false, 50); videoTitleTextView.setText(name != null ? name : ""); videoTitleTextView.setMaxLines(1); @@ -1146,6 +1160,7 @@ public class VideoDetailFragment videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate())); } prepareDescription(info.getDescription()); + updateProgressInfo(info); animateView(spinnerToolbar, true, 500); setupActionBar(info); @@ -1250,4 +1265,37 @@ public class VideoDetailFragment showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema); } + + private void updateProgressInfo(@NonNull final StreamInfo info) { + if (positionSubscriber != null) { + positionSubscriber.dispose(); + } + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + final boolean playbackResumeEnabled = + prefs.getBoolean(activity.getString(R.string.enable_watch_history_key), true) + && prefs.getBoolean(activity.getString(R.string.enable_playback_resume_key), true); + if (!playbackResumeEnabled || info.getDuration() <= 0) { + positionView.setVisibility(View.INVISIBLE); + detailPositionView.setVisibility(View.GONE); + return; + } + final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext()); + positionSubscriber = recordManager.loadStreamState(info) + .subscribeOn(Schedulers.io()) + .onErrorComplete() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(state -> { + final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()); + positionView.setMax((int) info.getDuration()); + positionView.setProgress(seconds); + detailPositionView.setText(Localization.getDurationString(seconds)); + animateView(positionView, true, 500); + animateView(detailPositionView, true, 500); + }, e -> { + if (DEBUG) e.printStackTrace(); + }, () -> { + animateView(positionView, false, 500); + animateView(detailPositionView, false, 500); + }); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index dbc3dd8a2..68784852e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -266,13 +266,13 @@ public abstract class BaseListFragment extends BaseStateFragment implem final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { switch (i) { case 0: - NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item)); + NavigationHelper.playOnBackgroundPlayer(context, new SinglePlayQueue(item), true); break; case 1: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), true); break; case 2: - NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item), true); break; case 3: if (getFragmentManager() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 71865b04d..934e934e9 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -170,19 +170,19 @@ public class ChannelFragment extends BaseListInfoFragment { final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0); switch (i) { case 0: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), false); break; case 1: - NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item), false); break; case 2: - NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); + NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true); break; case 3: - NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); + NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true); break; case 4: - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true); break; case 5: if (getFragmentManager() != null) { @@ -440,11 +440,11 @@ public class ChannelFragment extends BaseListInfoFragment { monitorSubscription(result); headerPlayAllButton.setOnClickListener( - view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); headerPopupButton.setOnClickListener( - view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); headerBackgroundButton.setOnClickListener( - view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); } private PlayQueue getPlayQueue() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 2a775fe8f..77aa0a250 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -154,22 +154,22 @@ public class PlaylistFragment extends BaseListInfoFragment { final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0); switch (i) { case 0: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item), false); break; case 1: - NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); + NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item), false); break; case 2: - NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); + NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true); break; case 3: - NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); + NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true); break; case 4: - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true); break; case 5: - ShareUtils.shareUrl(this.getContext(), item.getName(), item.getUrl()); + ShareUtils.shareUrl(requireContext(), item.getName(), item.getUrl()); break; default: break; @@ -301,19 +301,19 @@ public class PlaylistFragment extends BaseListInfoFragment { .subscribe(getPlaylistBookmarkSubscriber()); headerPlayAllButton.setOnClickListener(view -> - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); headerPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); headerBackgroundButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); headerPopupButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue()); + NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true); return true; }); headerBackgroundButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue()); + NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true); return true; }); } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index 56453773a..f9090128c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -37,12 +37,14 @@ import org.schabi.newpipe.database.stream.dao.StreamStateDAO; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; +import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Maybe; import io.reactivex.Single; @@ -80,9 +82,9 @@ public class HistoryRecordManager { final Date currentTime = new Date(); return Maybe.fromCallable(() -> database.runInTransaction(() -> { final long streamId = streamTable.upsert(new StreamEntity(info)); - StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(); + StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId); - if (latestEntry != null && latestEntry.getStreamUid() == streamId) { + if (latestEntry != null) { streamHistoryTable.delete(latestEntry); latestEntry.setAccessDate(currentTime); latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); @@ -99,7 +101,7 @@ public class HistoryRecordManager { } public Single deleteWholeStreamHistory() { - return Single.fromCallable(() -> streamHistoryTable.deleteAll()) + return Single.fromCallable(streamHistoryTable::deleteAll) .subscribeOn(Schedulers.io()); } @@ -160,7 +162,7 @@ public class HistoryRecordManager { } public Single deleteWholeSearchHistory() { - return Single.fromCallable(() -> searchHistoryTable.deleteAll()) + return Single.fromCallable(searchHistoryTable::deleteAll) .subscribeOn(Schedulers.io()); } @@ -180,18 +182,41 @@ public class HistoryRecordManager { // Stream State History /////////////////////////////////////////////////////// - @SuppressWarnings("unused") - public Maybe loadStreamState(final StreamInfo info) { - return Maybe.fromCallable(() -> streamTable.upsert(new StreamEntity(info))) - .flatMap(streamId -> streamStateTable.getState(streamId).firstElement()) - .flatMap(states -> states.isEmpty() ? Maybe.empty() : Maybe.just(states.get(0))) + public Maybe getStreamHistory(final StreamInfo info) { + return Maybe.fromCallable(() -> { + final long streamId = streamTable.upsert(new StreamEntity(info)); + return streamHistoryTable.getLatestEntry(streamId); + }).subscribeOn(Schedulers.io()); + } + + public Maybe loadStreamState(final PlayQueueItem queueItem) { + return queueItem.getStream() + .map((info) -> streamTable.upsert(new StreamEntity(info))) + .flatMapPublisher(streamStateTable::getState) + .firstElement() + .flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0))) + .filter(state -> state.isValid((int) queueItem.getDuration())) .subscribeOn(Schedulers.io()); } - public Maybe saveStreamState(@NonNull final StreamInfo info, final long progressTime) { - return Maybe.fromCallable(() -> database.runInTransaction(() -> { + public Maybe loadStreamState(final StreamInfo info) { + return Single.fromCallable(() -> streamTable.upsert(new StreamEntity(info))) + .flatMapPublisher(streamStateTable::getState) + .firstElement() + .flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0))) + .filter(state -> state.isValid((int) info.getDuration())) + .subscribeOn(Schedulers.io()); + } + + public Completable saveStreamState(@NonNull final StreamInfo info, final long progressTime) { + return Completable.fromAction(() -> database.runInTransaction(() -> { final long streamId = streamTable.upsert(new StreamEntity(info)); - return streamStateTable.upsert(new StreamStateEntity(streamId, progressTime)); + final StreamStateEntity state = new StreamStateEntity(streamId, progressTime); + if (state.isValid((int) info.getDuration())) { + streamStateTable.upsert(state); + } else { + streamStateTable.deleteState(streamId); + } })).subscribeOn(Schedulers.io()); } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 5a62a3969..b97fdf8f2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -310,11 +310,11 @@ public class StatisticsPlaylistFragment } headerPlayAllButton.setOnClickListener(view -> - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); headerPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); headerBackgroundButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); sortButton.setOnClickListener(view -> toggleSortMode()); hideLoading(); @@ -377,19 +377,19 @@ public class StatisticsPlaylistFragment final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0); switch (i) { case 0: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem), false); break; case 1: - NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem)); + NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem), false); break; case 2: - NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); + NavigationHelper.playOnMainPlayer(context, getPlayQueue(index), true); break; case 3: - NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); + NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index), true); break; case 4: - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index), true); break; case 5: deleteEntry(index); diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index dc101fade..501016642 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -319,11 +319,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); headerPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); headerBackgroundButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); hideLoading(); } @@ -534,20 +534,20 @@ public class LocalPlaylistFragment extends BaseLocalListFragment initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, + /*playOnInit=*/true)) + .subscribe( + state -> queue.setRecovery(queue.getIndex(), state.getProgressTime()), + error -> { + if (DEBUG) error.printStackTrace(); + } + ); + databaseUpdateReactor.add(stateLoader); + return; + } } - // Good to go... initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, /*playOnInit=*/true); @@ -615,6 +634,9 @@ public abstract class BasePlayer implements break; case Player.STATE_ENDED: // 4 changeState(STATE_COMPLETED); + if (currentMetadata != null) { + resetPlaybackState(currentMetadata.getMetadata()); + } isPrepared = false; break; } @@ -721,6 +743,7 @@ public abstract class BasePlayer implements case DISCONTINUITY_REASON_SEEK_ADJUSTMENT: case DISCONTINUITY_REASON_INTERNAL: if (playQueue.getIndex() != newWindowIndex) { + resetPlaybackState(playQueue.getItem()); playQueue.setIndex(newWindowIndex); } break; @@ -750,6 +773,9 @@ public abstract class BasePlayer implements @Override public void onSeekProcessed() { if (DEBUG) Log.d(TAG, "ExoPlayer - onSeekProcessed() called"); + if (isPrepared) { + savePlaybackState(); + } } /*////////////////////////////////////////////////////////////////////////// // Playback Listener @@ -1017,27 +1043,40 @@ public abstract class BasePlayer implements } } - protected void savePlaybackState(final StreamInfo info, final long progress) { + private void savePlaybackState(final StreamInfo info, final long progress) { if (info == null) return; + if (DEBUG) Log.d(TAG, "savePlaybackState() called"); final Disposable stateSaver = recordManager.saveStreamState(info, progress) .observeOn(AndroidSchedulers.mainThread()) + .doOnError((e) -> { + if (DEBUG) e.printStackTrace(); + }) .onErrorComplete() - .subscribe( - ignored -> {/* successful */}, - error -> Log.e(TAG, "savePlaybackState() failure: ", error) - ); + .subscribe(); databaseUpdateReactor.add(stateSaver); } - private void savePlaybackState() { + private void resetPlaybackState(final PlayQueueItem queueItem) { + if (queueItem == null) return; + final Disposable stateSaver = queueItem.getStream() + .flatMapCompletable(info -> recordManager.saveStreamState(info, 0)) + .observeOn(AndroidSchedulers.mainThread()) + .doOnError((e) -> { + if (DEBUG) e.printStackTrace(); + }) + .onErrorComplete() + .subscribe(); + databaseUpdateReactor.add(stateSaver); + } + + public void resetPlaybackState(final StreamInfo info) { + savePlaybackState(info, 0); + } + + public void savePlaybackState() { if (simpleExoPlayer == null || currentMetadata == null) return; final StreamInfo currentInfo = currentMetadata.getMetadata(); - - if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS && - simpleExoPlayer.getCurrentPosition() < - simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD_MILLIS) { - savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition()); - } + savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition()); } private void maybeUpdateCurrentMetadata() { @@ -1225,4 +1264,10 @@ public abstract class BasePlayer implements public boolean gotDestroyed() { return simpleExoPlayer == null; } + + private boolean isPlaybackResumeEnabled() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true) + && prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true); + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index cf179917d..902ee5065 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -247,6 +247,12 @@ public final class MainVideoPlayer extends AppCompatActivity super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase)); } + @Override + protected void onPause() { + playerImpl.savePlaybackState(); + super.onPause(); + } + /*////////////////////////////////////////////////////////////////////////// // State Saving //////////////////////////////////////////////////////////////////////////*/ @@ -579,7 +585,8 @@ public final class MainVideoPlayer extends AppCompatActivity this.getPlaybackSpeed(), this.getPlaybackPitch(), this.getPlaybackSkipSilence(), - this.getPlaybackQuality() + this.getPlaybackQuality(), + false ); context.startService(intent); @@ -601,7 +608,8 @@ public final class MainVideoPlayer extends AppCompatActivity this.getPlaybackSpeed(), this.getPlaybackPitch(), this.getPlaybackSkipSilence(), - this.getPlaybackQuality() + this.getPlaybackQuality(), + false ); context.startService(intent); diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 7578c444c..3782d85c0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -325,6 +325,7 @@ public final class PopupVideoPlayer extends Service { isPopupClosing = true; if (playerImpl != null) { + playerImpl.savePlaybackState(); if (playerImpl.getRootView() != null) { windowManager.removeView(playerImpl.getRootView()); } @@ -565,7 +566,8 @@ public final class PopupVideoPlayer extends Service { this.getPlaybackSpeed(), this.getPlaybackPitch(), this.getPlaybackSkipSilence(), - this.getPlaybackQuality() + this.getPlaybackQuality(), + false ); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 3e04f1e3a..bdd31f21b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -188,7 +188,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(), this.player.getPlaybackSkipSilence(), - null + null, + false ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index fb60ac473..bbfe805a5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -543,6 +543,11 @@ public abstract class VideoPlayer extends BasePlayer playbackSpeedTextView.setText(formatSpeed(getPlaybackSpeed())); super.onPrepared(playWhenReady); + + if (simpleExoPlayer.getCurrentPosition() != 0 && !isControlsVisible()) { + controlsVisibilityHandler.removeCallbacksAndMessages(null); + controlsVisibilityHandler.postDelayed(this::showControlsThenHide, DEFAULT_CONTROLS_DURATION); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java index e1ecc662d..ac79fee23 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -124,7 +124,7 @@ public class CommentTextOnTouchListener implements View.OnTouchListener { .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds*1000); - NavigationHelper.playOnPopupPlayer(context, playQueue); + NavigationHelper.playOnPopupPlayer(context, playQueue, false); }); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 98ae3a88a..89c4b33fe 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -69,12 +69,14 @@ public class NavigationHelper { public static Intent getPlayerIntent(@NonNull final Context context, @NonNull final Class targetClazz, @NonNull final PlayQueue playQueue, - @Nullable final String quality) { + @Nullable final String quality, + final boolean resumePlayback) { Intent intent = new Intent(context, targetClazz); final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class); if (cacheKey != null) intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey); if (quality != null) intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality); + intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback); return intent; } @@ -82,16 +84,18 @@ public class NavigationHelper { @NonNull public static Intent getPlayerIntent(@NonNull final Context context, @NonNull final Class targetClazz, - @NonNull final PlayQueue playQueue) { - return getPlayerIntent(context, targetClazz, playQueue, null); + @NonNull final PlayQueue playQueue, + final boolean resumePlayback) { + return getPlayerIntent(context, targetClazz, playQueue, null, resumePlayback); } @NonNull public static Intent getPlayerEnqueueIntent(@NonNull final Context context, @NonNull final Class targetClazz, @NonNull final PlayQueue playQueue, - final boolean selectOnAppend) { - return getPlayerIntent(context, targetClazz, playQueue) + final boolean selectOnAppend, + final boolean resumePlayback) { + return getPlayerIntent(context, targetClazz, playQueue, resumePlayback) .putExtra(BasePlayer.APPEND_ONLY, true) .putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend); } @@ -104,40 +108,41 @@ public class NavigationHelper { final float playbackSpeed, final float playbackPitch, final boolean playbackSkipSilence, - @Nullable final String playbackQuality) { - return getPlayerIntent(context, targetClazz, playQueue, playbackQuality) + @Nullable final String playbackQuality, + final boolean resumePlayback) { + return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) .putExtra(BasePlayer.REPEAT_MODE, repeatMode) .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed) .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch) .putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence); } - public static void playOnMainPlayer(final Context context, final PlayQueue queue) { - final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue); + public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { + final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback); playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(playerIntent); } - public static void playOnPopupPlayer(final Context context, final PlayQueue queue) { + public static void playOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { if (!PermissionHelper.isPopupEnabled(context)) { PermissionHelper.showPopupEnablementToast(context); return; } Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - startService(context, getPlayerIntent(context, PopupVideoPlayer.class, queue)); + startService(context, getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback)); } - public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) { + public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show(); - startService(context, getPlayerIntent(context, BackgroundPlayer.class, queue)); + startService(context, getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback)); } - public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) { - enqueueOnPopupPlayer(context, queue, false); + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { + enqueueOnPopupPlayer(context, queue, false, resumePlayback); } - public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) { + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) { if (!PermissionHelper.isPopupEnabled(context)) { PermissionHelper.showPopupEnablementToast(context); return; @@ -145,17 +150,17 @@ public class NavigationHelper { Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); startService(context, - getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend)); + getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend, resumePlayback)); } - public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) { - enqueueOnBackgroundPlayer(context, queue, false); + public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { + enqueueOnBackgroundPlayer(context, queue, false, resumePlayback); } - public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) { + public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) { Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); startService(context, - getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend)); + getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend, resumePlayback)); } public static void startService(@NonNull final Context context, @NonNull final Intent intent) { diff --git a/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java b/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java new file mode 100644 index 000000000..39c6e707e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/AnimatedProgressBar.java @@ -0,0 +1,69 @@ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.widget.ProgressBar; + +public final class AnimatedProgressBar extends ProgressBar { + + @Nullable + private ProgressBarAnimation animation = null; + + public AnimatedProgressBar(Context context) { + super(context); + } + + public AnimatedProgressBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AnimatedProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public synchronized void setProgress(int progress) { + cancelAnimation(); + animation = new ProgressBarAnimation(this, getProgress(), progress); + startAnimation(animation); + } + + private void cancelAnimation() { + if (animation != null) { + animation.cancel(); + animation = null; + } + clearAnimation(); + } + + private void setProgressInternal(int progress) { + super.setProgress(progress); + } + + private static class ProgressBarAnimation extends Animation { + + private final AnimatedProgressBar progressBar; + private final float from; + private final float to; + + ProgressBarAnimation(AnimatedProgressBar progressBar, float from, float to) { + super(); + this.progressBar = progressBar; + this.from = from; + this.to = to; + setDuration(500); + setInterpolator(new AccelerateDecelerateInterpolator()); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + super.applyTransformation(interpolatedTime, t); + float value = from + (to - from) * interpolatedTime; + progressBar.setProgressInternal((int) value); + } + } +} diff --git a/app/src/main/res/drawable/progress_soundcloud_horizontal_dark.xml b/app/src/main/res/drawable/progress_soundcloud_horizontal_dark.xml new file mode 100644 index 000000000..1ec9f67b6 --- /dev/null +++ b/app/src/main/res/drawable/progress_soundcloud_horizontal_dark.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_soundcloud_horizontal_light.xml b/app/src/main/res/drawable/progress_soundcloud_horizontal_light.xml new file mode 100644 index 000000000..c326c5c04 --- /dev/null +++ b/app/src/main/res/drawable/progress_soundcloud_horizontal_light.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_youtube_horizontal_dark.xml b/app/src/main/res/drawable/progress_youtube_horizontal_dark.xml new file mode 100644 index 000000000..404410f98 --- /dev/null +++ b/app/src/main/res/drawable/progress_youtube_horizontal_dark.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_youtube_horizontal_light.xml b/app/src/main/res/drawable/progress_youtube_horizontal_light.xml new file mode 100644 index 000000000..120a6e5fb --- /dev/null +++ b/app/src/main/res/drawable/progress_youtube_horizontal_light.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 8cdc2f307..a8fbcca89 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -1,7 +1,7 @@ @@ -67,10 +68,10 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:background="#64000000" - android:paddingBottom="10dp" android:paddingLeft="30dp" - android:paddingRight="30dp" android:paddingTop="10dp" + android:paddingRight="30dp" + android:paddingBottom="10dp" android:text="@string/hold_to_append" android:textColor="@android:color/white" android:textSize="20sp" @@ -84,17 +85,42 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" - android:layout_marginBottom="8dp" android:layout_marginLeft="12dp" - android:layout_marginRight="12dp" android:layout_marginTop="8dp" + android:layout_marginRight="12dp" + android:layout_marginBottom="8dp" android:alpha=".6" android:background="#23000000" android:gravity="center" - android:paddingBottom="2dp" android:paddingLeft="6dp" - android:paddingRight="6dp" android:paddingTop="2dp" + android:paddingRight="6dp" + android:paddingBottom="2dp" + android:textAllCaps="true" + android:textColor="@android:color/white" + android:textSize="12sp" + android:textStyle="bold" + android:visibility="gone" + tools:ignore="RtlHardcoded" + tools:text="12:38" + tools:visibility="visible" /> + + + + @@ -191,8 +230,8 @@ android:layout_width="match_parent" android:layout_height="55dp" android:layout_marginLeft="12dp" - android:layout_marginRight="12dp" android:layout_marginTop="6dp" + android:layout_marginRight="12dp" android:baselineAligned="false" android:orientation="horizontal"> @@ -201,8 +240,8 @@ android:id="@+id/detail_uploader_root_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_toLeftOf="@id/details_panel" android:layout_toStartOf="@id/details_panel" + android:layout_toLeftOf="@id/details_panel" android:background="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal" @@ -261,8 +300,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" - android:layout_marginBottom="6dp" android:layout_marginTop="6dp" + android:layout_marginBottom="6dp" android:lines="1" android:textAppearance="?android:attr/textAppearanceLarge" android:textSize="@dimen/video_item_detail_views_text_size" @@ -354,8 +393,8 @@ android:drawableTop="?attr/ic_playlist_add" android:focusable="true" android:gravity="center" - android:paddingBottom="6dp" android:paddingTop="6dp" + android:paddingBottom="6dp" android:text="@string/controls_add_to_playlist_title" android:textSize="12sp" /> @@ -371,8 +410,8 @@ android:drawableTop="?attr/audio" android:focusable="true" android:gravity="center" - android:paddingBottom="6dp" android:paddingTop="6dp" + android:paddingBottom="6dp" android:text="@string/controls_background_title" android:textSize="12sp" /> @@ -388,8 +427,8 @@ android:drawableTop="?attr/popup" android:focusable="true" android:gravity="center" - android:paddingBottom="6dp" android:paddingTop="6dp" + android:paddingBottom="6dp" android:text="@string/controls_popup_title" android:textSize="12sp" /> @@ -405,8 +444,8 @@ android:drawableTop="?attr/download" android:focusable="true" android:gravity="center" - android:paddingBottom="6dp" android:paddingTop="6dp" + android:paddingBottom="6dp" android:text="@string/download" android:textSize="12sp" /> @@ -444,10 +483,10 @@ android:id="@+id/detail_description_view" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="8dp" android:layout_marginLeft="12dp" - android:layout_marginRight="12dp" android:layout_marginTop="3dp" + android:layout_marginRight="12dp" + android:layout_marginBottom="8dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textIsSelectable="true" android:textSize="@dimen/video_item_detail_description_text_size" @@ -490,7 +529,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/video_item_detail" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusableInTouchMode="true"> - + - + - + - - + + - + - + - + - - + - + - - + - - + - + - + + - + + - - + - - + - - + - - + + - - + + - + + - + + - - + + - - + - + - + + - + + - + - + - - - + - + - - + - + + + - + - + + - + - + - - + - + - + - + + - + - - + - + + + + + + + + - + - + - + - + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 32d57db8b..759c24460 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -167,7 +167,10 @@ Что нового История поиска Хранить запросы поиска локально - История и кэш + История просмотров + Продолжать воспроизведение + Восстанавливать с последней позиции + Очистить данные Запоминать воспроизведённые потоки Возобновить при фокусе Возобновлять воспроизведение после перерывов (например, телефонных звонков) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index eaca5719a..41e6f22ba 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -153,7 +153,10 @@ Показувати пропозиції під час пошуку Історія пошуків Зберігати пошукові запити локально - Історія та кеш + Історія переглядiв + Продовживати перегляд + Відновлювати останню позицію + Очистити дані Вести облік перегляду відеозаписів Відновити відтворення Продовжувати відтворення опісля переривання (наприклад телефонного дзвінка) diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 865b68c24..bdf42c4ed 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -43,6 +43,7 @@ + diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 3861f53d5..b70305d56 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -150,6 +150,7 @@ enable_search_history enable_watch_history main_page_content + enable_playback_resume import_data export_data diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98a32d9e6..a84d7db36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -95,7 +95,10 @@ Show suggestions when searching Search history Store search queries locally - History & Cache + Watch history + Resume playback + Restore last playback position + Clear data Keep track of watched videos Resume on focus gain Continue playing after interruptions (e.g. phone calls) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a7686dedc..6df9069ff 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -65,6 +65,7 @@ @drawable/toolbar_shadow_light @drawable/light_selector @color/light_ripple_color + @drawable/progress_youtube_horizontal_light @style/PreferenceThemeOverlay.v14.Material @@ -128,6 +129,7 @@ @drawable/toolbar_shadow_dark @drawable/dark_selector @color/dark_ripple_color + @drawable/progress_youtube_horizontal_dark @style/PreferenceThemeOverlay.v14.Material diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml index 257b1905d..d6ab239e4 100644 --- a/app/src/main/res/values/styles_services.xml +++ b/app/src/main/res/values/styles_services.xml @@ -15,18 +15,21 @@ @color/light_soundcloud_primary_color @color/light_soundcloud_dark_color @color/light_soundcloud_accent_color + @drawable/progress_soundcloud_horizontal_light diff --git a/app/src/main/res/xml/history_settings.xml b/app/src/main/res/xml/history_settings.xml index a7428d340..305b1c360 100644 --- a/app/src/main/res/xml/history_settings.xml +++ b/app/src/main/res/xml/history_settings.xml @@ -1,40 +1,54 @@ - + android:title="@string/enable_watch_history_title" + app:iconSpaceReserved="false" /> + + + android:title="@string/enable_search_history_title" + app:iconSpaceReserved="false" /> - + - + - + - + + + + + \ No newline at end of file