-Added schema for local playlist and stream statistics.
-Added normalized schema for stream history. -Added managers for specialized database access for stream and local playlist.
This commit is contained in:
parent
960fd9be38
commit
f71242a036
21 changed files with 913 additions and 23 deletions
|
@ -28,4 +28,10 @@ public final class NewPipeDatabase {
|
||||||
|
|
||||||
return databaseInstance;
|
return databaseInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static AppDatabase getInstance(Context context) {
|
||||||
|
if (databaseInstance == null) init(context);
|
||||||
|
return databaseInstance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,31 @@ import android.arch.persistence.room.Database;
|
||||||
import android.arch.persistence.room.RoomDatabase;
|
import android.arch.persistence.room.RoomDatabase;
|
||||||
import android.arch.persistence.room.TypeConverters;
|
import android.arch.persistence.room.TypeConverters;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.history.Converters;
|
|
||||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||||
import org.schabi.newpipe.database.history.dao.WatchHistoryDAO;
|
import org.schabi.newpipe.database.history.dao.WatchHistoryDAO;
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.WatchHistoryEntry;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamHistoryDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
|
|
||||||
@TypeConverters({Converters.class})
|
@TypeConverters({Converters.class})
|
||||||
@Database(entities = {SubscriptionEntity.class, WatchHistoryEntry.class, SearchHistoryEntry.class}, version = 1, exportSchema = false)
|
@Database(
|
||||||
|
entities = {
|
||||||
|
SubscriptionEntity.class, WatchHistoryEntry.class, SearchHistoryEntry.class,
|
||||||
|
StreamEntity.class, StreamHistoryEntity.class, PlaylistEntity.class,
|
||||||
|
PlaylistStreamEntity.class
|
||||||
|
},
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public static final String DATABASE_NAME = "newpipe.db";
|
public static final String DATABASE_NAME = "newpipe.db";
|
||||||
|
@ -23,4 +38,12 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||||
public abstract WatchHistoryDAO watchHistoryDAO();
|
public abstract WatchHistoryDAO watchHistoryDAO();
|
||||||
|
|
||||||
public abstract SearchHistoryDAO searchHistoryDAO();
|
public abstract SearchHistoryDAO searchHistoryDAO();
|
||||||
|
|
||||||
|
public abstract StreamDAO streamDAO();
|
||||||
|
|
||||||
|
public abstract StreamHistoryDAO streamHistoryDAO();
|
||||||
|
|
||||||
|
public abstract PlaylistDAO playlistDAO();
|
||||||
|
|
||||||
|
public abstract PlaylistStreamDAO playlistStreamDAO();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,6 @@ public interface BasicDAO<Entity> {
|
||||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||||
List<Long> insertAll(final Collection<Entity> entities);
|
List<Long> insertAll(final Collection<Entity> entities);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
long upsert(final Entity entity);
|
|
||||||
|
|
||||||
/* Searches */
|
/* Searches */
|
||||||
Flowable<List<Entity>> getAll();
|
Flowable<List<Entity>> getAll();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package org.schabi.newpipe.database.history;
|
package org.schabi.newpipe.database;
|
||||||
|
|
||||||
import android.arch.persistence.room.TypeConverter;
|
import android.arch.persistence.room.TypeConverter;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class Converters {
|
public class Converters {
|
||||||
|
@ -25,4 +27,14 @@ public class Converters {
|
||||||
public static Long dateToTimestamp(Date date) {
|
public static Long dateToTimestamp(Date date) {
|
||||||
return date == null ? null : date.getTime();
|
return date == null ? null : date.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static StreamType streamTypeOf(String value) {
|
||||||
|
return StreamType.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String stringOf(StreamType streamType) {
|
||||||
|
return streamType.name();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.schabi.newpipe.database.playlist;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
|
||||||
|
|
||||||
|
public class PlaylistMetadataEntry {
|
||||||
|
final public static String PLAYLIST_STREAM_COUNT = "streamCount";
|
||||||
|
|
||||||
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
|
final public long uid;
|
||||||
|
@ColumnInfo(name = PLAYLIST_NAME)
|
||||||
|
final public String name;
|
||||||
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
||||||
|
final public String thumbnailUrl;
|
||||||
|
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
|
||||||
|
final public long streamCount;
|
||||||
|
|
||||||
|
public PlaylistMetadataEntry(long uid, String name, String thumbnailUrl, long streamCount) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.name = name;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.streamCount = streamCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalPlaylistInfoItem toStoredPlaylistInfoItem() {
|
||||||
|
LocalPlaylistInfoItem storedPlaylistInfoItem = new LocalPlaylistInfoItem(uid, name);
|
||||||
|
storedPlaylistInfoItem.setThumbnailUrl(thumbnailUrl);
|
||||||
|
storedPlaylistInfoItem.setStreamCount(streamCount);
|
||||||
|
return storedPlaylistInfoItem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.schabi.newpipe.database.playlist.dao;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Dao;
|
||||||
|
import android.arch.persistence.room.Query;
|
||||||
|
import android.arch.persistence.room.Transaction;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.BasicDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + PLAYLIST_TABLE)
|
||||||
|
public abstract Flowable<List<PlaylistEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<PlaylistEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract Flowable<List<PlaylistEntity>> getPlaylist(final long playlistId);
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.schabi.newpipe.database.playlist.dao;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Dao;
|
||||||
|
import android.arch.persistence.room.Query;
|
||||||
|
import android.arch.persistence.room.Transaction;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.BasicDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.*;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.*;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.*;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + PLAYLIST_STREAM_JOIN_TABLE)
|
||||||
|
public abstract Flowable<List<PlaylistStreamEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<PlaylistStreamEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract void deleteBatch(final long playlistId);
|
||||||
|
|
||||||
|
@Query("SELECT MAX(" + JOIN_INDEX + ")" +
|
||||||
|
" FROM " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
|
||||||
|
public abstract Flowable<Integer> getMaximumIndexOf(final long playlistId);
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT " + STREAM_ID + ", " + STREAM_SERVICE_ID + ", " + STREAM_URL + ", " +
|
||||||
|
STREAM_TITLE + ", " + STREAM_TYPE + ", " + STREAM_UPLOADER + ", " +
|
||||||
|
STREAM_DURATION + ", " + STREAM_THUMBNAIL_URL +
|
||||||
|
|
||||||
|
" FROM " + STREAM_TABLE + " INNER JOIN " +
|
||||||
|
// get ids of streams of the given playlist
|
||||||
|
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX +
|
||||||
|
" FROM " + PLAYLIST_STREAM_JOIN_TABLE + " WHERE "
|
||||||
|
+ JOIN_PLAYLIST_ID + " = :playlistId)" +
|
||||||
|
|
||||||
|
// then merge with the stream metadata
|
||||||
|
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
||||||
|
" ORDER BY " + JOIN_INDEX + " ASC")
|
||||||
|
public abstract Flowable<List<StreamEntity>> getOrderedStreamsOf(long playlistId);
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " +
|
||||||
|
PLAYLIST_THUMBNAIL_URL + ", COUNT(*) AS " + PLAYLIST_STREAM_COUNT +
|
||||||
|
|
||||||
|
" FROM " + PLAYLIST_TABLE + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE +
|
||||||
|
" ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + PLAYLIST_STREAM_JOIN_TABLE + "." + JOIN_PLAYLIST_ID +
|
||||||
|
" GROUP BY " + JOIN_PLAYLIST_ID)
|
||||||
|
public abstract Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.schabi.newpipe.database.playlist.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
import android.arch.persistence.room.PrimaryKey;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||||
|
|
||||||
|
@Entity(tableName = PLAYLIST_TABLE,
|
||||||
|
indices = {@Index(value = {PLAYLIST_NAME})})
|
||||||
|
public class PlaylistEntity {
|
||||||
|
final public static String PLAYLIST_TABLE = "playlists";
|
||||||
|
final public static String PLAYLIST_ID = "uid";
|
||||||
|
final public static String PLAYLIST_NAME = "name";
|
||||||
|
final public static String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = PLAYLIST_ID)
|
||||||
|
private long uid = 0;
|
||||||
|
|
||||||
|
@ColumnInfo(name = PLAYLIST_NAME)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
|
||||||
|
private String thumbnailUrl;
|
||||||
|
|
||||||
|
public PlaylistEntity(String name, String thumbnailUrl) {
|
||||||
|
this.name = name;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUid(long uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThumbnailUrl(String thumbnailUrl) {
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.schabi.newpipe.database.playlist.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.ForeignKey;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
|
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
|
||||||
|
|
||||||
|
@Entity(tableName = PLAYLIST_STREAM_JOIN_TABLE,
|
||||||
|
primaryKeys = {JOIN_PLAYLIST_ID, JOIN_STREAM_ID, JOIN_INDEX},
|
||||||
|
indices = {
|
||||||
|
@Index(value = {JOIN_PLAYLIST_ID, JOIN_INDEX}, unique = true),
|
||||||
|
@Index(value = {JOIN_STREAM_ID})
|
||||||
|
},
|
||||||
|
foreignKeys = {
|
||||||
|
@ForeignKey(entity = PlaylistEntity.class,
|
||||||
|
parentColumns = PlaylistEntity.PLAYLIST_ID,
|
||||||
|
childColumns = JOIN_PLAYLIST_ID,
|
||||||
|
onDelete = CASCADE, onUpdate = CASCADE, deferred = true),
|
||||||
|
@ForeignKey(entity = StreamEntity.class,
|
||||||
|
parentColumns = StreamEntity.STREAM_ID,
|
||||||
|
childColumns = JOIN_STREAM_ID,
|
||||||
|
onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
|
||||||
|
})
|
||||||
|
public class PlaylistStreamEntity {
|
||||||
|
|
||||||
|
final public static String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
|
||||||
|
final public static String JOIN_PLAYLIST_ID = "playlist_id";
|
||||||
|
final public static String JOIN_STREAM_ID = "stream_id";
|
||||||
|
final public static String JOIN_INDEX = "join_index";
|
||||||
|
|
||||||
|
@ColumnInfo(name = JOIN_PLAYLIST_ID)
|
||||||
|
private long playlistUid;
|
||||||
|
|
||||||
|
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||||
|
private long streamUid;
|
||||||
|
|
||||||
|
@ColumnInfo(name = JOIN_INDEX)
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public PlaylistStreamEntity(final long playlistUid, final long streamUid, final int index) {
|
||||||
|
this.playlistUid = playlistUid;
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPlaylistUid() {
|
||||||
|
return playlistUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamUid() {
|
||||||
|
return streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaylistUid(long playlistUid) {
|
||||||
|
this.playlistUid = playlistUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamUid(long streamUid) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndex(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.schabi.newpipe.database.stream;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class StreamStatisticsEntry {
|
||||||
|
final public static String STREAM_LATEST_DATE = "latestAccess";
|
||||||
|
final public static String STREAM_WATCH_COUNT = "watchCount";
|
||||||
|
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_ID)
|
||||||
|
final public long uid;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_SERVICE_ID)
|
||||||
|
final public int serviceId;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_URL)
|
||||||
|
final public String url;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_TITLE)
|
||||||
|
final public String title;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||||
|
final public StreamType streamType;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||||
|
final public long duration;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_UPLOADER)
|
||||||
|
final public String uploader;
|
||||||
|
@ColumnInfo(name = StreamEntity.STREAM_THUMBNAIL_URL)
|
||||||
|
final public String thumbnailUrl;
|
||||||
|
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||||
|
final public long streamId;
|
||||||
|
@ColumnInfo(name = StreamStatisticsEntry.STREAM_LATEST_DATE)
|
||||||
|
final public Date latestAccessDate;
|
||||||
|
@ColumnInfo(name = StreamStatisticsEntry.STREAM_WATCH_COUNT)
|
||||||
|
final public long watchCount;
|
||||||
|
|
||||||
|
public StreamStatisticsEntry(long uid, int serviceId, String url, String title,
|
||||||
|
StreamType streamType, long duration, String uploader,
|
||||||
|
String thumbnailUrl, long streamId, Date latestAccessDate,
|
||||||
|
long watchCount) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.url = url;
|
||||||
|
this.title = title;
|
||||||
|
this.streamType = streamType;
|
||||||
|
this.duration = duration;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.streamId = streamId;
|
||||||
|
this.latestAccessDate = latestAccessDate;
|
||||||
|
this.watchCount = watchCount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.schabi.newpipe.database.stream.dao;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Dao;
|
||||||
|
import android.arch.persistence.room.Query;
|
||||||
|
import android.arch.persistence.room.Transaction;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.BasicDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVICE_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class StreamDAO implements BasicDAO<StreamEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE)
|
||||||
|
public abstract Flowable<List<StreamEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + STREAM_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE + " WHERE " + STREAM_SERVICE_ID + " = :serviceId")
|
||||||
|
public abstract Flowable<List<StreamEntity>> listByService(int serviceId);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE + " WHERE " +
|
||||||
|
STREAM_URL + " LIKE :url AND " +
|
||||||
|
STREAM_SERVICE_ID + " = :serviceId")
|
||||||
|
public abstract Flowable<List<StreamEntity>> getStream(long serviceId, String url);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE + " WHERE " +
|
||||||
|
STREAM_URL + " LIKE :url AND " +
|
||||||
|
STREAM_SERVICE_ID + " = :serviceId")
|
||||||
|
abstract List<StreamEntity> getStreamInternal(long serviceId, String url);
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
public long upsert(StreamEntity stream) {
|
||||||
|
final List<StreamEntity> streams = getStreamInternal(stream.getServiceId(), stream.getUrl());
|
||||||
|
|
||||||
|
final long uid;
|
||||||
|
if (streams.isEmpty()) {
|
||||||
|
uid = insert(stream);
|
||||||
|
} else {
|
||||||
|
uid = streams.get(0).getUid();
|
||||||
|
stream.setUid(uid);
|
||||||
|
update(stream);
|
||||||
|
}
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.schabi.newpipe.database.stream.dao;
|
||||||
|
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Dao;
|
||||||
|
import android.arch.persistence.room.Query;
|
||||||
|
import android.arch.persistence.room.Transaction;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.BasicDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Flowable;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
|
||||||
|
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class StreamHistoryDAO implements BasicDAO<StreamHistoryEntity> {
|
||||||
|
@Override
|
||||||
|
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE)
|
||||||
|
public abstract Flowable<List<StreamHistoryEntity>> getAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Query("DELETE FROM " + STREAM_HISTORY_TABLE)
|
||||||
|
public abstract int deleteAll();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flowable<List<StreamHistoryEntity>> listByService(int serviceId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||||
|
public abstract int deleteHistory(final long streamId);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM " + STREAM_TABLE +
|
||||||
|
|
||||||
|
// Select the latest entry and watch count for each stream id on history table
|
||||||
|
" INNER JOIN " +
|
||||||
|
"(SELECT " + JOIN_STREAM_ID + ", " +
|
||||||
|
" MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", " +
|
||||||
|
" COUNT(*) AS " + STREAM_WATCH_COUNT +
|
||||||
|
" FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")" +
|
||||||
|
|
||||||
|
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID +
|
||||||
|
" ORDER BY " + STREAM_ACCESS_DATE + " DESC")
|
||||||
|
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package org.schabi.newpipe.database.stream.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.Ignore;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
import android.arch.persistence.room.PrimaryKey;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
import org.schabi.newpipe.util.Constants;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVICE_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
|
||||||
|
|
||||||
|
@Entity(tableName = STREAM_TABLE,
|
||||||
|
indices = {@Index(value = {STREAM_SERVICE_ID, STREAM_URL}, unique = true)})
|
||||||
|
public class StreamEntity {
|
||||||
|
|
||||||
|
final public static String STREAM_TABLE = "streams";
|
||||||
|
final public static String STREAM_ID = "uid";
|
||||||
|
final public static String STREAM_SERVICE_ID = "service_id";
|
||||||
|
final public static String STREAM_URL = "url";
|
||||||
|
final public static String STREAM_TITLE = "title";
|
||||||
|
final public static String STREAM_TYPE = "streamType";
|
||||||
|
final public static String STREAM_UPLOADER = "uploader";
|
||||||
|
final public static String STREAM_DURATION = "duration";
|
||||||
|
final public static String STREAM_THUMBNAIL_URL = "thumbnail_url";
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = STREAM_ID)
|
||||||
|
private long uid = 0;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_SERVICE_ID)
|
||||||
|
private int serviceId = Constants.NO_SERVICE_ID;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_URL)
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_TITLE)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_TYPE)
|
||||||
|
private StreamType streamType;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_DURATION)
|
||||||
|
private Long duration;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_UPLOADER)
|
||||||
|
private String uploader;
|
||||||
|
|
||||||
|
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
||||||
|
private String thumbnailUrl;
|
||||||
|
|
||||||
|
public StreamEntity(final int serviceId, final String title, final String url,
|
||||||
|
final StreamType streamType, final String thumbnailUrl, final String uploader,
|
||||||
|
final long duration) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.title = title;
|
||||||
|
this.url = url;
|
||||||
|
this.streamType = streamType;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public StreamEntity(final StreamInfoItem item) {
|
||||||
|
this(item.service_id, item.name, item.url, item.stream_type, item.thumbnail_url,
|
||||||
|
item.uploader_name, item.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public StreamEntity(final StreamInfo info) {
|
||||||
|
this(info.service_id, info.name, info.url, info.stream_type, info.thumbnail_url,
|
||||||
|
info.uploader_name, info.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public StreamInfoItem toStreamInfoItem() throws IllegalArgumentException {
|
||||||
|
StreamInfoItem item = new StreamInfoItem(
|
||||||
|
getServiceId(), getUrl(), getTitle(), getStreamType());
|
||||||
|
item.setThumbnailUrl(getThumbnailUrl());
|
||||||
|
item.setUploaderName(getUploader());
|
||||||
|
item.setDuration(getDuration());
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUid(long uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceId(int serviceId) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamType getStreamType() {
|
||||||
|
return streamType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamType(StreamType type) {
|
||||||
|
this.streamType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(Long duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUploader() {
|
||||||
|
return uploader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUploader(String uploader) {
|
||||||
|
this.uploader = uploader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThumbnailUrl(String thumbnailUrl) {
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.schabi.newpipe.database.stream.model;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.ForeignKey;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||||
|
|
||||||
|
@Entity(tableName = STREAM_HISTORY_TABLE,
|
||||||
|
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
|
||||||
|
// No need to index for timestamp as they will almost always be unique
|
||||||
|
indices = {@Index(value = {JOIN_STREAM_ID})},
|
||||||
|
foreignKeys = {
|
||||||
|
@ForeignKey(entity = StreamEntity.class,
|
||||||
|
parentColumns = StreamEntity.STREAM_ID,
|
||||||
|
childColumns = JOIN_STREAM_ID,
|
||||||
|
onDelete = CASCADE, onUpdate = CASCADE)
|
||||||
|
})
|
||||||
|
public class StreamHistoryEntity {
|
||||||
|
final public static String STREAM_HISTORY_TABLE = "stream_history";
|
||||||
|
final public static String JOIN_STREAM_ID = "stream_id";
|
||||||
|
final public static String STREAM_ACCESS_DATE = "access_date";
|
||||||
|
|
||||||
|
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||||
|
private long streamUid;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@ColumnInfo(name = STREAM_ACCESS_DATE)
|
||||||
|
private Date accessDate;
|
||||||
|
|
||||||
|
public StreamHistoryEntity(long streamUid, @NonNull Date accessDate) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
this.accessDate = accessDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamUid() {
|
||||||
|
return streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamUid(long streamUid) {
|
||||||
|
this.streamUid = streamUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getAccessDate() {
|
||||||
|
return accessDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessDate(@NonNull Date accessDate) {
|
||||||
|
this.accessDate = accessDate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,8 +50,7 @@ public class SubscriptionEntity {
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Keep this package-private since UID should always be auto generated by Room impl */
|
public void setUid(long uid) {
|
||||||
void setUid(long uid) {
|
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.schabi.newpipe.fragments.playlist;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||||
|
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.Maybe;
|
||||||
|
|
||||||
|
public class LocalPlaylistManager {
|
||||||
|
|
||||||
|
private final AppDatabase database;
|
||||||
|
private final StreamDAO streamTable;
|
||||||
|
private final PlaylistDAO playlistTable;
|
||||||
|
private final PlaylistStreamDAO playlistStreamTable;
|
||||||
|
|
||||||
|
public LocalPlaylistManager(final AppDatabase db) {
|
||||||
|
database = db;
|
||||||
|
streamTable = db.streamDAO();
|
||||||
|
playlistTable = db.playlistDAO();
|
||||||
|
playlistStreamTable = db.playlistStreamDAO();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Maybe<List<Long>> createPlaylist(final String name, final List<StreamEntity> streams) {
|
||||||
|
// Disallow creation of empty playlists until user is able to select thumbnail
|
||||||
|
if (streams.isEmpty()) return Maybe.empty();
|
||||||
|
final StreamEntity defaultStream = streams.get(0);
|
||||||
|
final PlaylistEntity newPlaylist = new PlaylistEntity(name, defaultStream.getThumbnailUrl());
|
||||||
|
|
||||||
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
|
final long playlistId = playlistTable.insert(newPlaylist);
|
||||||
|
|
||||||
|
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
||||||
|
for (int index = 0; index < streams.size(); index++) {
|
||||||
|
// Upsert streams and get their ids
|
||||||
|
final long streamId = streamTable.upsert(streams.get(index));
|
||||||
|
joinEntities.add(new PlaylistStreamEntity(playlistId, streamId, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlistStreamTable.insertAll(joinEntities);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Maybe<Long> appendToPlaylist(final long playlistId, final StreamEntity stream) {
|
||||||
|
final Maybe<Long> streamIdFuture = Maybe.fromCallable(() -> streamTable.upsert(stream));
|
||||||
|
final Maybe<Integer> joinIndexFuture =
|
||||||
|
playlistStreamTable.getMaximumIndexOf(playlistId).firstElement();
|
||||||
|
|
||||||
|
return Maybe.zip(streamIdFuture, joinIndexFuture, (streamId, currentMaxJoinIndex) ->
|
||||||
|
playlistStreamTable.insert(new PlaylistStreamEntity(playlistId,
|
||||||
|
streamId, currentMaxJoinIndex + 1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable updateJoin(final long playlistId, final List<Long> streamIds) {
|
||||||
|
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||||
|
for (int i = 0; i < streamIds.size(); i++) {
|
||||||
|
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(i), i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Completable.fromRunnable(() -> database.runInTransaction(() -> {
|
||||||
|
playlistStreamTable.deleteBatch(playlistId);
|
||||||
|
playlistStreamTable.insertAll(joinEntities);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Maybe<List<PlaylistMetadataEntry>> getPlaylists() {
|
||||||
|
return playlistStreamTable.getPlaylistMetadata().firstElement();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.schabi.newpipe.fragments.playlist;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.AppDatabase;
|
||||||
|
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.dao.StreamHistoryDAO;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamHistoryEntity;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.MaybeObserver;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class StreamRecordManager {
|
||||||
|
|
||||||
|
private final AppDatabase database;
|
||||||
|
private final StreamDAO streamTable;
|
||||||
|
private final StreamHistoryDAO historyTable;
|
||||||
|
|
||||||
|
public StreamRecordManager(final AppDatabase db) {
|
||||||
|
database = db;
|
||||||
|
streamTable = db.streamDAO();
|
||||||
|
historyTable = db.streamHistoryDAO();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onChanged(final StreamInfoItem infoItem) {
|
||||||
|
// Only existing streams are updated
|
||||||
|
return streamTable.update(new StreamEntity(infoItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<Long> onViewed(final StreamInfo info) {
|
||||||
|
return Single.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
|
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||||
|
return historyTable.insert(new StreamHistoryEntity(streamId, new Date()));
|
||||||
|
})).subscribeOn(Schedulers.io());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int removeHistory(final long streamId) {
|
||||||
|
return historyTable.deleteHistory(streamId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.schabi.newpipe.info_list.stored;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.Constants.NO_SERVICE_ID;
|
||||||
|
import static org.schabi.newpipe.util.Constants.NO_URL;
|
||||||
|
|
||||||
|
public class LocalPlaylistInfoItem extends InfoItem {
|
||||||
|
private final long playlistId;
|
||||||
|
private long streamCount;
|
||||||
|
|
||||||
|
public LocalPlaylistInfoItem(final long playlistId, final String name) {
|
||||||
|
super(InfoType.PLAYLIST, NO_SERVICE_ID, NO_URL, name);
|
||||||
|
|
||||||
|
this.playlistId = playlistId;
|
||||||
|
this.streamCount = streamCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPlaylistId() {
|
||||||
|
return playlistId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamCount() {
|
||||||
|
return streamCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreamCount(long streamCount) {
|
||||||
|
this.streamCount = streamCount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.schabi.newpipe.info_list.stored;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class StreamStatisticsInfoItem extends InfoItem {
|
||||||
|
private final long streamId;
|
||||||
|
|
||||||
|
private Date latestAccessDate;
|
||||||
|
private long watchCount;
|
||||||
|
|
||||||
|
public StreamStatisticsInfoItem(final long streamId, final int serviceId,
|
||||||
|
final String url, final String name) {
|
||||||
|
super(InfoType.STREAM, serviceId, url, name);
|
||||||
|
this.streamId = streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStreamId() {
|
||||||
|
return streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLatestAccessDate() {
|
||||||
|
return latestAccessDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLatestAccessDate(Date latestAccessDate) {
|
||||||
|
this.latestAccessDate = latestAccessDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWatchCount() {
|
||||||
|
return watchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWatchCount(long watchCount) {
|
||||||
|
this.watchCount = watchCount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,8 +61,10 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
import org.schabi.newpipe.fragments.playlist.StreamRecordManager;
|
||||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||||
import org.schabi.newpipe.player.helper.CacheFactory;
|
import org.schabi.newpipe.player.helper.CacheFactory;
|
||||||
import org.schabi.newpipe.player.helper.LoadController;
|
import org.schabi.newpipe.player.helper.LoadController;
|
||||||
|
@ -77,9 +79,9 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import io.reactivex.functions.Predicate;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
||||||
|
|
||||||
|
@ -147,6 +149,9 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
protected DefaultExtractorsFactory extractorsFactory;
|
protected DefaultExtractorsFactory extractorsFactory;
|
||||||
|
|
||||||
protected Disposable progressUpdateReactor;
|
protected Disposable progressUpdateReactor;
|
||||||
|
protected CompositeDisposable databaseUpdateReactor;
|
||||||
|
|
||||||
|
protected StreamRecordManager recordManager;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@ -172,6 +177,12 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
public void initPlayer() {
|
public void initPlayer() {
|
||||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||||
|
|
||||||
|
if (recordManager == null) {
|
||||||
|
recordManager = new StreamRecordManager(NewPipeDatabase.getInstance(context));
|
||||||
|
}
|
||||||
|
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||||
|
databaseUpdateReactor = new CompositeDisposable();
|
||||||
|
|
||||||
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||||
final AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
final AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
||||||
final LoadControl loadControl = new LoadController(context);
|
final LoadControl loadControl = new LoadController(context);
|
||||||
|
@ -193,18 +204,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
private Disposable getProgressReactor() {
|
private Disposable getProgressReactor() {
|
||||||
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
|
return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.filter(new Predicate<Long>() {
|
.filter(ignored -> isProgressLoopRunning())
|
||||||
@Override
|
.subscribe(ignored -> triggerProgressUpdate());
|
||||||
public boolean test(Long aLong) throws Exception {
|
|
||||||
return isProgressLoopRunning();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribe(new Consumer<Long>() {
|
|
||||||
@Override
|
|
||||||
public void accept(Long aLong) throws Exception {
|
|
||||||
triggerProgressUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleIntent(Intent intent) {
|
public void handleIntent(Intent intent) {
|
||||||
|
@ -281,6 +282,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
if (playQueue != null) playQueue.dispose();
|
if (playQueue != null) playQueue.dispose();
|
||||||
if (playbackManager != null) playbackManager.dispose();
|
if (playbackManager != null) playbackManager.dispose();
|
||||||
if (audioReactor != null) audioReactor.abandonAudioFocus();
|
if (audioReactor != null) audioReactor.abandonAudioFocus();
|
||||||
|
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
|
@ -291,6 +293,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
|
|
||||||
trackSelector = null;
|
trackSelector = null;
|
||||||
simpleExoPlayer = null;
|
simpleExoPlayer = null;
|
||||||
|
recordManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaSource buildMediaSource(String url, String overrideExtension) {
|
public MediaSource buildMediaSource(String url, String overrideExtension) {
|
||||||
|
@ -668,10 +671,13 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
||||||
"], queue index=[" + playQueue.getIndex() + "]");
|
"], queue index=[" + playQueue.getIndex() + "]");
|
||||||
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
|
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
|
||||||
final long startPos = info != null ? info.start_position : 0;
|
final long startPos = info != null ? info.start_position : 0;
|
||||||
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
|
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex +
|
||||||
|
" at: " + getTimeString((int)startPos));
|
||||||
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
|
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).subscribe());
|
||||||
|
recordManager.removeRecord();
|
||||||
initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
|
initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,5 @@ public class Constants {
|
||||||
public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change";
|
public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change";
|
||||||
|
|
||||||
public static final int NO_SERVICE_ID = -1;
|
public static final int NO_SERVICE_ID = -1;
|
||||||
|
public static final String NO_URL = "";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue