mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	-Added bulk playlist creation and append.
-Added UI to create playlist from service player activity. -Added state saving to playlist dialogs. -Removed access to history activity on service player activity. -Made StreamEntity serializable.
This commit is contained in:
		| @@ -19,7 +19,7 @@ public final class NewPipeDatabase { | |||||||
|  |  | ||||||
|     public static void init(Context context) { |     public static void init(Context context) { | ||||||
|         databaseInstance = Room |         databaseInstance = Room | ||||||
|                 .databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME) |                 .databaseBuilder(context, AppDatabase.class, DATABASE_NAME) | ||||||
|                 .addMigrations(MIGRATION_11_12) |                 .addMigrations(MIGRATION_11_12) | ||||||
|                 .fallbackToDestructiveMigration() |                 .fallbackToDestructiveMigration() | ||||||
|                 .build(); |                 .build(); | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ public class Migrations { | |||||||
|             /* |             /* | ||||||
|             * Unfortunately these queries must be hardcoded due to the possibility of |             * Unfortunately these queries must be hardcoded due to the possibility of | ||||||
|             * schema and names changing at a later date, thus invalidating the older migration |             * schema and names changing at a later date, thus invalidating the older migration | ||||||
|             * scripts if names are not hardcoded. |             * scripts if they are not hardcoded. | ||||||
|             * */ |             * */ | ||||||
|  |  | ||||||
|             // Not much we can do about this, since room doesn't create tables before migration. |             // Not much we can do about this, since room doesn't create tables before migration. | ||||||
|   | |||||||
| @@ -9,15 +9,18 @@ import android.arch.persistence.room.PrimaryKey; | |||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamType; | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
|  | import org.schabi.newpipe.playlist.PlayQueueItem; | ||||||
| import org.schabi.newpipe.util.Constants; | import org.schabi.newpipe.util.Constants; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  |  | ||||||
| import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVICE_ID; | 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_TABLE; | ||||||
| import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL; | import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL; | ||||||
|  |  | ||||||
| @Entity(tableName = STREAM_TABLE, | @Entity(tableName = STREAM_TABLE, | ||||||
|         indices = {@Index(value = {STREAM_SERVICE_ID, STREAM_URL}, unique = true)}) |         indices = {@Index(value = {STREAM_SERVICE_ID, STREAM_URL}, unique = true)}) | ||||||
| public class StreamEntity { | public class StreamEntity implements Serializable { | ||||||
|  |  | ||||||
|     final public static String STREAM_TABLE             = "streams"; |     final public static String STREAM_TABLE             = "streams"; | ||||||
|     final public static String STREAM_ID                = "uid"; |     final public static String STREAM_ID                = "uid"; | ||||||
| @@ -78,6 +81,12 @@ public class StreamEntity { | |||||||
|                 info.uploader_name, info.duration); |                 info.uploader_name, info.duration); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Ignore | ||||||
|  |     public StreamEntity(final PlayQueueItem item) { | ||||||
|  |         this(item.getServiceId(), item.getTitle(), item.getUrl(), item.getStreamType(), | ||||||
|  |                 item.getThumbnailUrl(), item.getUploader(), item.getDuration()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Ignore |     @Ignore | ||||||
|     public StreamInfoItem toStreamInfoItem() throws IllegalArgumentException { |     public StreamInfoItem toStreamInfoItem() throws IllegalArgumentException { | ||||||
|         StreamInfoItem item = new StreamInfoItem( |         StreamInfoItem item = new StreamInfoItem( | ||||||
|   | |||||||
| @@ -331,7 +331,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement | |||||||
|                 break; |                 break; | ||||||
|             case R.id.detail_controls_playlist_append: |             case R.id.detail_controls_playlist_append: | ||||||
|                 if (getFragmentManager() != null && currentInfo != null) { |                 if (getFragmentManager() != null && currentInfo != null) { | ||||||
|                     PlaylistAppendDialog.newInstance(currentInfo).show(getFragmentManager(), TAG); |                     PlaylistAppendDialog.fromStreamInfo(currentInfo) | ||||||
|  |                             .show(getFragmentManager(), TAG); | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case R.id.detail_uploader_root_layout: |             case R.id.detail_uploader_root_layout: | ||||||
|   | |||||||
| @@ -33,34 +33,38 @@ public class LocalPlaylistManager { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Maybe<List<Long>> createPlaylist(final String name, final List<StreamEntity> streams) { |     public Maybe<List<Long>> createPlaylist(final String name, final List<StreamEntity> streams) { | ||||||
|         // Disallow creation of empty playlists until user is able to select thumbnail |         // Disallow creation of empty playlists | ||||||
|         if (streams.isEmpty()) return Maybe.empty(); |         if (streams.isEmpty()) return Maybe.empty(); | ||||||
|         final StreamEntity defaultStream = streams.get(0); |         final StreamEntity defaultStream = streams.get(0); | ||||||
|         final PlaylistEntity newPlaylist = new PlaylistEntity(name, defaultStream.getThumbnailUrl()); |         final PlaylistEntity newPlaylist = | ||||||
|  |                 new PlaylistEntity(name, defaultStream.getThumbnailUrl()); | ||||||
|  |  | ||||||
|         return Maybe.fromCallable(() -> database.runInTransaction(() -> { |         return Maybe.fromCallable(() -> database.runInTransaction(() -> | ||||||
|             final long playlistId = playlistTable.insert(newPlaylist); |                 upsertStreams(playlistTable.insert(newPlaylist), streams, 0)) | ||||||
|  |         ).subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Maybe<List<Long>> appendToPlaylist(final long playlistId, | ||||||
|  |                                               final List<StreamEntity> streams) { | ||||||
|  |         return playlistStreamTable.getMaximumIndexOf(playlistId) | ||||||
|  |                 .firstElement() | ||||||
|  |                 .map(maxJoinIndex -> database.runInTransaction(() -> | ||||||
|  |                         upsertStreams(playlistId, streams, maxJoinIndex + 1)) | ||||||
|  |                 ).subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private List<Long> upsertStreams(final long playlistId, | ||||||
|  |                                      final List<StreamEntity> streams, | ||||||
|  |                                      final int indexOffset) { | ||||||
|  |  | ||||||
|         List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size()); |         List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size()); | ||||||
|         for (int index = 0; index < streams.size(); index++) { |         for (int index = 0; index < streams.size(); index++) { | ||||||
|             // Upsert streams and get their ids |             // Upsert streams and get their ids | ||||||
|             final long streamId = streamTable.upsert(streams.get(index)); |             final long streamId = streamTable.upsert(streams.get(index)); | ||||||
|                 joinEntities.add(new PlaylistStreamEntity(playlistId, streamId, index)); |             joinEntities.add(new PlaylistStreamEntity(playlistId, streamId, | ||||||
|  |                     index + indexOffset)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return playlistStreamTable.insertAll(joinEntities); |         return playlistStreamTable.insertAll(joinEntities); | ||||||
|         })).subscribeOn(Schedulers.io()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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)) |  | ||||||
|         ).subscribeOn(Schedulers.io()); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Completable updateJoin(final long playlistId, final List<Long> streamIds) { |     public Completable updateJoin(final long playlistId, final List<Long> streamIds) { | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import android.content.Context; | |||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import android.support.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||||
| import android.support.v4.app.DialogFragment; |  | ||||||
| import android.support.v7.widget.LinearLayoutManager; | import android.support.v7.widget.LinearLayoutManager; | ||||||
| import android.support.v7.widget.RecyclerView; | import android.support.v7.widget.RecyclerView; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -19,34 +18,48 @@ import org.schabi.newpipe.database.stream.model.StreamEntity; | |||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
|  | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
| import org.schabi.newpipe.info_list.InfoListAdapter; | import org.schabi.newpipe.info_list.InfoListAdapter; | ||||||
| import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem; | import org.schabi.newpipe.info_list.stored.LocalPlaylistInfoItem; | ||||||
|  | import org.schabi.newpipe.playlist.PlayQueueItem; | ||||||
|  |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
|  |  | ||||||
| public class PlaylistAppendDialog extends DialogFragment { | public final class PlaylistAppendDialog extends PlaylistDialog { | ||||||
|     private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); |     private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); | ||||||
|     private static final String INFO_KEY = "info_key"; |  | ||||||
|  |  | ||||||
|     private StreamInfo streamInfo; |  | ||||||
|  |  | ||||||
|     private View newPlaylistButton; |  | ||||||
|     private RecyclerView playlistRecyclerView; |     private RecyclerView playlistRecyclerView; | ||||||
|     private InfoListAdapter playlistAdapter; |     private InfoListAdapter playlistAdapter; | ||||||
|  |  | ||||||
|     public static PlaylistAppendDialog newInstance(final StreamInfo info) { |     public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) { | ||||||
|         PlaylistAppendDialog dialog = new PlaylistAppendDialog(); |         PlaylistAppendDialog dialog = new PlaylistAppendDialog(); | ||||||
|         dialog.setInfo(info); |         dialog.setInfo(Collections.singletonList(new StreamEntity(info))); | ||||||
|         return dialog; |         return dialog; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void setInfo(StreamInfo info) { |     public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) { | ||||||
|         this.streamInfo = info; |         PlaylistAppendDialog dialog = new PlaylistAppendDialog(); | ||||||
|  |         List<StreamEntity> entities = new ArrayList<>(items.size()); | ||||||
|  |         for (final StreamInfoItem item : items) { | ||||||
|  |             entities.add(new StreamEntity(item)); | ||||||
|  |         } | ||||||
|  |         dialog.setInfo(entities); | ||||||
|  |         return dialog; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) { | ||||||
|  |         PlaylistAppendDialog dialog = new PlaylistAppendDialog(); | ||||||
|  |         List<StreamEntity> entities = new ArrayList<>(items.size()); | ||||||
|  |         for (final PlayQueueItem item : items) { | ||||||
|  |             entities.add(new StreamEntity(item)); | ||||||
|  |         } | ||||||
|  |         dialog.setInfo(entities); | ||||||
|  |         return dialog; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -60,14 +73,9 @@ public class PlaylistAppendDialog extends DialogFragment { | |||||||
|         playlistAdapter.useMiniItemVariants(true); |         playlistAdapter.useMiniItemVariants(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     public void onCreate(@Nullable Bundle savedInstanceState) { |     // Views | ||||||
|         super.onCreate(savedInstanceState); |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|         if (savedInstanceState != null) { |  | ||||||
|             Serializable serial = savedInstanceState.getSerializable(INFO_KEY); |  | ||||||
|             if (serial instanceof StreamInfo) streamInfo = (StreamInfo) serial; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, |     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, | ||||||
| @@ -79,7 +87,7 @@ public class PlaylistAppendDialog extends DialogFragment { | |||||||
|     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { |     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { | ||||||
|         super.onViewCreated(view, savedInstanceState); |         super.onViewCreated(view, savedInstanceState); | ||||||
|  |  | ||||||
|         newPlaylistButton = view.findViewById(R.id.newPlaylist); |         final View newPlaylistButton = view.findViewById(R.id.newPlaylist); | ||||||
|         playlistRecyclerView = view.findViewById(R.id.playlist_list); |         playlistRecyclerView = view.findViewById(R.id.playlist_list); | ||||||
|         playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); |         playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); | ||||||
|         playlistRecyclerView.setAdapter(playlistAdapter); |         playlistRecyclerView.setAdapter(playlistAdapter); | ||||||
| @@ -92,12 +100,14 @@ public class PlaylistAppendDialog extends DialogFragment { | |||||||
|         playlistAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() { |         playlistAdapter.setOnPlaylistSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener<PlaylistInfoItem>() { | ||||||
|             @Override |             @Override | ||||||
|             public void selected(PlaylistInfoItem selectedItem) { |             public void selected(PlaylistInfoItem selectedItem) { | ||||||
|                 if (!(selectedItem instanceof LocalPlaylistInfoItem)) return; |                 if (!(selectedItem instanceof LocalPlaylistInfoItem) || getStreams() == null) | ||||||
|  |                     return; | ||||||
|  |  | ||||||
|                 final long playlistId = ((LocalPlaylistInfoItem) selectedItem).getPlaylistId(); |                 final long playlistId = ((LocalPlaylistInfoItem) selectedItem).getPlaylistId(); | ||||||
|                 final Toast successToast = |                 final Toast successToast = | ||||||
|                         Toast.makeText(getContext(), "Added", Toast.LENGTH_SHORT); |                         Toast.makeText(getContext(), "Added", Toast.LENGTH_SHORT); | ||||||
|  |  | ||||||
|                 playlistManager.appendToPlaylist(playlistId, new StreamEntity(streamInfo)) |                 playlistManager.appendToPlaylist(playlistId, getStreams()) | ||||||
|                         .observeOn(AndroidSchedulers.mainThread()) |                         .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                         .subscribe(ignored -> successToast.show()); |                         .subscribe(ignored -> successToast.show()); | ||||||
|  |  | ||||||
| @@ -127,20 +137,14 @@ public class PlaylistAppendDialog extends DialogFragment { | |||||||
|                 }); |                 }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onSaveInstanceState(Bundle outState) { |  | ||||||
|         super.onSaveInstanceState(outState); |  | ||||||
|         outState.putSerializable(INFO_KEY, streamInfo); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Helper |     // Helper | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     public void openCreatePlaylistDialog() { |     public void openCreatePlaylistDialog() { | ||||||
|         if (streamInfo == null || getFragmentManager() == null) return; |         if (getStreams() == null || getFragmentManager() == null) return; | ||||||
|  |  | ||||||
|         PlaylistCreationDialog.newInstance(streamInfo).show(getFragmentManager(), TAG); |         PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG); | ||||||
|         getDialog().dismiss(); |         getDialog().dismiss(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,63 +5,35 @@ import android.app.Dialog; | |||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import android.support.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||||
| import android.support.v4.app.DialogFragment; |  | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.MainActivity; |  | ||||||
| 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.stream.model.StreamEntity; | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; |  | ||||||
|  |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
|  |  | ||||||
| public class PlaylistCreationDialog extends DialogFragment { | public final class PlaylistCreationDialog extends PlaylistDialog { | ||||||
|     private static final String TAG = PlaylistCreationDialog.class.getCanonicalName(); |     private static final String TAG = PlaylistCreationDialog.class.getCanonicalName(); | ||||||
|     private static final boolean DEBUG = MainActivity.DEBUG; |  | ||||||
|  |  | ||||||
|     private static final String INFO_KEY = "info_key"; |     public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) { | ||||||
|  |  | ||||||
|     private StreamInfo streamInfo; |  | ||||||
|  |  | ||||||
|     public static PlaylistCreationDialog newInstance(final StreamInfo info) { |  | ||||||
|         PlaylistCreationDialog dialog = new PlaylistCreationDialog(); |         PlaylistCreationDialog dialog = new PlaylistCreationDialog(); | ||||||
|         dialog.setInfo(info); |         dialog.setInfo(streams); | ||||||
|         return dialog; |         return dialog; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void setInfo(final StreamInfo info) { |  | ||||||
|         this.streamInfo = info; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // LifeCycle |     // Dialog | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onSaveInstanceState(Bundle outState) { |  | ||||||
|         super.onSaveInstanceState(outState); |  | ||||||
|         if (streamInfo != null) { |  | ||||||
|             outState.putSerializable(INFO_KEY, streamInfo); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     @Override |     @Override | ||||||
|     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { |     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { | ||||||
|         if (savedInstanceState != null && streamInfo == null) { |         if (getStreams() == null) return super.onCreateDialog(savedInstanceState); | ||||||
|             final Object infoCandidate = savedInstanceState.getSerializable(INFO_KEY); |  | ||||||
|             if (infoCandidate != null && infoCandidate instanceof StreamInfo) { |  | ||||||
|                 streamInfo = (StreamInfo) infoCandidate; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (streamInfo == null) return super.onCreateDialog(savedInstanceState); |  | ||||||
|  |  | ||||||
|         View dialogView = View.inflate(getContext(), |         View dialogView = View.inflate(getContext(), | ||||||
|                 R.layout.dialog_create_playlist, null); |                 R.layout.dialog_create_playlist, null); | ||||||
| @@ -76,13 +48,11 @@ public class PlaylistCreationDialog extends DialogFragment { | |||||||
|                     final String name = nameInput.getText().toString(); |                     final String name = nameInput.getText().toString(); | ||||||
|                     final LocalPlaylistManager playlistManager = |                     final LocalPlaylistManager playlistManager = | ||||||
|                             new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); |                             new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); | ||||||
|                     final List<StreamEntity> streams = |  | ||||||
|                             Collections.singletonList(new StreamEntity(streamInfo)); |  | ||||||
|                     final Toast successToast = Toast.makeText(getActivity(), |                     final Toast successToast = Toast.makeText(getActivity(), | ||||||
|                             "Playlist " + name + " successfully created", |                             "Playlist successfully created", | ||||||
|                             Toast.LENGTH_SHORT); |                             Toast.LENGTH_SHORT); | ||||||
|  |  | ||||||
|                     playlistManager.createPlaylist(name, streams) |                     playlistManager.createPlaylist(name, getStreams()) | ||||||
|                             .observeOn(AndroidSchedulers.mainThread()) |                             .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                             .subscribe(longs -> successToast.show()); |                             .subscribe(longs -> successToast.show()); | ||||||
|                 }); |                 }); | ||||||
|   | |||||||
| @@ -0,0 +1,73 @@ | |||||||
|  | package org.schabi.newpipe.fragments.local; | ||||||
|  |  | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.support.annotation.NonNull; | ||||||
|  | import android.support.annotation.Nullable; | ||||||
|  | import android.support.v4.app.DialogFragment; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
|  | import org.schabi.newpipe.util.StateSaver; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Queue; | ||||||
|  |  | ||||||
|  | public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead { | ||||||
|  |  | ||||||
|  |     private List<StreamEntity> streamEntities; | ||||||
|  |  | ||||||
|  |     private StateSaver.SavedState savedState; | ||||||
|  |  | ||||||
|  |     protected void setInfo(final List<StreamEntity> entities) { | ||||||
|  |         this.streamEntities = entities; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected List<StreamEntity> getStreams() { | ||||||
|  |         return streamEntities; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|  |     // LifeCycle | ||||||
|  |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onCreate(@Nullable Bundle savedInstanceState) { | ||||||
|  |         super.onCreate(savedInstanceState); | ||||||
|  |         savedState = StateSaver.tryToRestore(savedInstanceState, this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onDestroy() { | ||||||
|  |         super.onDestroy(); | ||||||
|  |         StateSaver.onDestroy(savedState); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|  |     // State Saving | ||||||
|  |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String generateSuffix() { | ||||||
|  |         final int size = streamEntities == null ? 0 : streamEntities.size(); | ||||||
|  |         return "." + size + ".list"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void writeTo(Queue<Object> objectsToSave) { | ||||||
|  |         objectsToSave.add(streamEntities); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     @SuppressWarnings("unchecked") | ||||||
|  |     public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception { | ||||||
|  |         streamEntities = (List<StreamEntity>) savedObjects.poll(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onSaveInstanceState(Bundle outState) { | ||||||
|  |         super.onSaveInstanceState(outState); | ||||||
|  |         if (getActivity() != null) { | ||||||
|  |             savedState = StateSaver.tryToSave(getActivity().isChangingConfigurations(), | ||||||
|  |                     savedState, outState, this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -675,6 +675,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen | |||||||
|             simpleExoPlayer.seekTo(currentSourceIndex, startPos); |             simpleExoPlayer.seekTo(currentSourceIndex, startPos); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // TODO: update exoplayer to 2.6.x in order to register view count on repeated streams | ||||||
|         databaseUpdateReactor.add(recordManager.onViewed(currentInfo).subscribe()); |         databaseUpdateReactor.add(recordManager.onViewed(currentInfo).subscribe()); | ||||||
|         initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); |         initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.Player; | |||||||
| 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.OnScrollBelowItemsListener; | import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; | ||||||
|  | import org.schabi.newpipe.fragments.local.PlaylistAppendDialog; | ||||||
| import org.schabi.newpipe.player.event.PlayerEventListener; | import org.schabi.newpipe.player.event.PlayerEventListener; | ||||||
| import org.schabi.newpipe.playlist.PlayQueueItem; | import org.schabi.newpipe.playlist.PlayQueueItem; | ||||||
| import org.schabi.newpipe.playlist.PlayQueueItemBuilder; | import org.schabi.newpipe.playlist.PlayQueueItemBuilder; | ||||||
| @@ -149,8 +150,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|             case android.R.id.home: |             case android.R.id.home: | ||||||
|                 finish(); |                 finish(); | ||||||
|                 return true; |                 return true; | ||||||
|             case R.id.action_history: |             case R.id.action_append_playlist: | ||||||
|                 NavigationHelper.openHistory(this); |                 appendToPlaylist(); | ||||||
|                 return true; |                 return true; | ||||||
|             case R.id.action_settings: |             case R.id.action_settings: | ||||||
|                 NavigationHelper.openSettings(this); |                 NavigationHelper.openSettings(this); | ||||||
| @@ -185,6 +186,14 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|                 null |                 null | ||||||
|         ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |         ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void appendToPlaylist() { | ||||||
|  |         if (this.player != null && this.player.getPlayQueue() != null) { | ||||||
|  |             PlaylistAppendDialog.fromPlayQueueItems(this.player.getPlayQueue().getStreams()) | ||||||
|  |                     .show(getSupportFragmentManager(), getTag()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     //////////////////////////////////////////////////////////////////////////// |     //////////////////////////////////////////////////////////////////////////// | ||||||
|     // Service Connection |     // Service Connection | ||||||
|     //////////////////////////////////////////////////////////////////////////// |     //////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import android.support.annotation.Nullable; | |||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
|  | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.util.ExtractorHelper; | import org.schabi.newpipe.util.ExtractorHelper; | ||||||
|  |  | ||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| @@ -23,6 +24,7 @@ public class PlayQueueItem implements Serializable { | |||||||
|     final private long duration; |     final private long duration; | ||||||
|     final private String thumbnailUrl; |     final private String thumbnailUrl; | ||||||
|     final private String uploader; |     final private String uploader; | ||||||
|  |     final private StreamType streamType; | ||||||
|  |  | ||||||
|     private long recoveryPosition; |     private long recoveryPosition; | ||||||
|     private Throwable error; |     private Throwable error; | ||||||
| @@ -30,22 +32,26 @@ public class PlayQueueItem implements Serializable { | |||||||
|     private transient Single<StreamInfo> stream; |     private transient Single<StreamInfo> stream; | ||||||
|  |  | ||||||
|     PlayQueueItem(@NonNull final StreamInfo info) { |     PlayQueueItem(@NonNull final StreamInfo info) { | ||||||
|         this(info.getName(), info.getUrl(), info.getServiceId(), info.duration, info.thumbnail_url, info.uploader_name); |         this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(), | ||||||
|  |                 info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType()); | ||||||
|         this.stream = Single.just(info); |         this.stream = Single.just(info); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     PlayQueueItem(@NonNull final StreamInfoItem item) { |     PlayQueueItem(@NonNull final StreamInfoItem item) { | ||||||
|         this(item.getName(), item.getUrl(), item.getServiceId(), item.duration, item.thumbnail_url, item.uploader_name); |         this(item.getName(), item.getUrl(), item.getServiceId(), item.getDuration(), | ||||||
|  |                 item.getThumbnailUrl(), item.getUploaderName(), item.getStreamType()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private PlayQueueItem(final String name, final String url, final int serviceId, |     private PlayQueueItem(final String name, final String url, final int serviceId, | ||||||
|                           final long duration, final String thumbnailUrl, final String uploader) { |                           final long duration, final String thumbnailUrl, final String uploader, | ||||||
|  |                           final StreamType streamType) { | ||||||
|         this.title = name; |         this.title = name; | ||||||
|         this.url = url; |         this.url = url; | ||||||
|         this.serviceId = serviceId; |         this.serviceId = serviceId; | ||||||
|         this.duration = duration; |         this.duration = duration; | ||||||
|         this.thumbnailUrl = thumbnailUrl; |         this.thumbnailUrl = thumbnailUrl; | ||||||
|         this.uploader = uploader; |         this.uploader = uploader; | ||||||
|  |         this.streamType = streamType; | ||||||
|  |  | ||||||
|         this.recoveryPosition = RECOVERY_UNSET; |         this.recoveryPosition = RECOVERY_UNSET; | ||||||
|     } |     } | ||||||
| @@ -78,6 +84,10 @@ public class PlayQueueItem implements Serializable { | |||||||
|         return uploader; |         return uploader; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public StreamType getStreamType() { | ||||||
|  |         return streamType; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public long getRecoveryPosition() { |     public long getRecoveryPosition() { | ||||||
|         return recoveryPosition; |         return recoveryPosition; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|     xmlns:tools="http://schemas.android.com/tools" |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     tools:context="org.schabi.newpipe.history.HistoryActivity"> |     tools:context=".player.BackgroundPlayerActivity"> | ||||||
|  |  | ||||||
|     <item android:id="@+id/action_history" |     <item android:id="@+id/action_append_playlist" | ||||||
|         android:orderInCategory="981" |         android:orderInCategory="981" | ||||||
|         android:title="@string/action_history" |         android:title="@string/append_playlist" | ||||||
|         app:showAsAction="never"/> |         app:showAsAction="never"/> | ||||||
|  |  | ||||||
|     <item android:id="@+id/action_settings" |     <item android:id="@+id/action_settings" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 John Zhen Mo
					John Zhen Mo