-Added state saving for streams on skip and player exception events.

-Added query for loading saved stream states.
-Modified orphan record removal to no longer consider stream table records.
This commit is contained in:
John Zhen Mo 2018-01-29 14:08:26 -08:00
parent 9b4a07de34
commit d3160eed9d
5 changed files with 75 additions and 13 deletions

View File

@ -92,10 +92,6 @@ public abstract class StreamDAO implements BasicDAO<StreamEntity> {
" ON " + STREAM_ID + " = " + " ON " + STREAM_ID + " = " +
StreamHistoryEntity.STREAM_HISTORY_TABLE + "." + StreamHistoryEntity.JOIN_STREAM_ID + StreamHistoryEntity.STREAM_HISTORY_TABLE + "." + StreamHistoryEntity.JOIN_STREAM_ID +
" LEFT JOIN " + STREAM_STATE_TABLE +
" ON " + STREAM_ID + " = " +
StreamStateEntity.STREAM_STATE_TABLE + "." + StreamStateEntity.JOIN_STREAM_ID +
" LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
" ON " + STREAM_ID + " = " + " ON " + STREAM_ID + " = " +
PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE + "." + PlaylistStreamEntity.JOIN_STREAM_ID + PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE + "." + PlaylistStreamEntity.JOIN_STREAM_ID +

View File

@ -1,7 +1,10 @@
package org.schabi.newpipe.database.stream.dao; package org.schabi.newpipe.database.stream.dao;
import android.arch.persistence.room.Dao; import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query; import android.arch.persistence.room.Query;
import android.arch.persistence.room.Transaction;
import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity;
@ -28,6 +31,18 @@ public abstract class StreamStateDAO implements BasicDAO<StreamStateEntity> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Query("SELECT * FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract Flowable<List<StreamStateEntity>> getState(final long streamId);
@Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") @Query("DELETE FROM " + STREAM_STATE_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract int deleteState(final long streamId); public abstract int deleteState(final long streamId);
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract void silentInsertInternal(final StreamStateEntity streamState);
@Transaction
public long upsert(StreamStateEntity stream) {
silentInsertInternal(stream);
return update(stream);
}
} }

View File

@ -156,7 +156,7 @@ public abstract class HistoryFragment<E> extends BaseFragment
.setMessage(R.string.delete_all_history_prompt) .setMessage(R.string.delete_all_history_prompt)
.setCancelable(true) .setCancelable(true)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.delete, (dialog, i) -> clearHistory()) .setPositiveButton(R.string.delete_all, (dialog, i) -> clearHistory())
.show(); .show();
} }

View File

@ -3,23 +3,25 @@ package org.schabi.newpipe.history;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.AppDatabase; import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.dao.StreamDAO; import org.schabi.newpipe.database.stream.dao.StreamDAO;
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -34,6 +36,7 @@ public class HistoryRecordManager {
private final StreamDAO streamTable; private final StreamDAO streamTable;
private final StreamHistoryDAO streamHistoryTable; private final StreamHistoryDAO streamHistoryTable;
private final SearchHistoryDAO searchHistoryTable; private final SearchHistoryDAO searchHistoryTable;
private final StreamStateDAO streamStateTable;
private final SharedPreferences sharedPreferences; private final SharedPreferences sharedPreferences;
private final String searchHistoryKey; private final String searchHistoryKey;
private final String streamHistoryKey; private final String streamHistoryKey;
@ -43,15 +46,12 @@ public class HistoryRecordManager {
streamTable = database.streamDAO(); streamTable = database.streamDAO();
streamHistoryTable = database.streamHistoryDAO(); streamHistoryTable = database.streamHistoryDAO();
searchHistoryTable = database.searchHistoryDAO(); searchHistoryTable = database.searchHistoryDAO();
streamStateTable = database.streamStateDAO();
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
searchHistoryKey = context.getString(R.string.enable_search_history_key); searchHistoryKey = context.getString(R.string.enable_search_history_key);
streamHistoryKey = context.getString(R.string.enable_watch_history_key); streamHistoryKey = context.getString(R.string.enable_watch_history_key);
} }
public Single<Integer> removeOrphanedRecords() {
return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io());
}
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
// Watch History // Watch History
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
@ -161,4 +161,31 @@ public class HistoryRecordManager {
private boolean isSearchHistoryEnabled() { private boolean isSearchHistoryEnabled() {
return sharedPreferences.getBoolean(searchHistoryKey, false); return sharedPreferences.getBoolean(searchHistoryKey, false);
} }
///////////////////////////////////////////////////////
// Stream State History
///////////////////////////////////////////////////////
@SuppressWarnings("unused")
public Maybe<StreamStateEntity> 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)))
.subscribeOn(Schedulers.io());
}
public Maybe<Long> saveStreamState(@NonNull final StreamInfo info, final long progressTime) {
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
return streamStateTable.upsert(new StreamStateEntity(streamId, progressTime));
})).subscribeOn(Schedulers.io());
}
///////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////
public Single<Integer> removeOrphanedRecords() {
return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io());
}
} }

View File

@ -581,6 +581,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
errorToast = null; errorToast = null;
} }
savePlaybackState();
switch (error.type) { switch (error.type) {
case ExoPlaybackException.TYPE_SOURCE: case ExoPlaybackException.TYPE_SOURCE:
if (simpleExoPlayer.getCurrentPosition() < if (simpleExoPlayer.getCurrentPosition() <
@ -758,6 +760,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
if (simpleExoPlayer == null || playQueue == null) return; if (simpleExoPlayer == null || playQueue == null) return;
if (DEBUG) Log.d(TAG, "onPlayPrevious() called"); if (DEBUG) Log.d(TAG, "onPlayPrevious() called");
savePlaybackState();
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, restart current track. /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, restart current track.
* Also restart the track if the current track is the first in a queue.*/ * Also restart the track if the current track is the first in a queue.*/
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || playQueue.getIndex() == 0) { if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || playQueue.getIndex() == 0) {
@ -772,6 +776,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
if (playQueue == null) return; if (playQueue == null) return;
if (DEBUG) Log.d(TAG, "onPlayNext() called"); if (DEBUG) Log.d(TAG, "onPlayNext() called");
savePlaybackState();
playQueue.offsetIndex(+1); playQueue.offsetIndex(+1);
} }
@ -833,6 +839,24 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
); );
} }
protected void savePlaybackState(final StreamInfo info, final long progress) {
if (context == null || info == null || databaseUpdateReactor == null) return;
final Disposable stateSaver = recordManager.saveStreamState(info, progress)
.observeOn(AndroidSchedulers.mainThread())
.onErrorComplete()
.subscribe();
databaseUpdateReactor.add(stateSaver);
}
private void savePlaybackState() {
if (simpleExoPlayer == null || currentInfo == null) return;
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD &&
simpleExoPlayer.getCurrentPosition() <
simpleExoPlayer.getDuration() - RECOVERY_SKIP_THRESHOLD) {
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Getters and Setters // Getters and Setters
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/