Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local Playlist and Database Normalization #1004

Merged
merged 36 commits into from
Feb 11, 2018

Conversation

karyogamy
Copy link
Contributor

@karyogamy karyogamy commented Jan 17, 2018

IMPORTANT: Please test this on an emulator since this PR includes a database migration script (that normalizes the watch history table) which is currently irreversible.

  • This PR aims at implementing Add local playlist feature #870 and is a work in progress.
  • Along with local playlists, this PR also tries to normalize the database schema so stream metadata can be reused.
  • This allows us to create smart playlists, such as latest unique watch history (add options of "add to the queue" in the history video list #998 is also implemented as part of this) and most watched streams. In the future, we can use this data to experiment with video recommendation using related videos and tags.

Here are some screenshots, which is probably better at explaining the new features. These UI are not final, please feel free provide ideas:

Here's an overview of the new database schemas:

  • Streams: contains extracted stream metadata
  • Stream History: [FK on Streams] contains timestamps of every replay
  • Stream State: [FK on Streams] contains user states of a stream, such as saved progress of streams
  • Playlist: contains name of user created playlist
  • Playlist Stream Join: [FK on Streams and Playlist] contains what streams are in each playlist

Todos (not in order):

  • Bulk add playlist from queues (saving ~2000 videos takes about just under a second on emulator)
  • Integrate normalized database to display on history activity
  • Simpify history activity to use dialog instead of gesture for deleting entries
  • Allow modifiable playlist (i.e. reordering and deleting streams, rename playlist and change thumbnail)
  • Add query for wiping stream entries which have no joins with other tables
  • Refactor/Improve info InfoItem holders to show additional items
  • UI improvements
  • Add timestamp recording when streams changes midway (FR: Resume position of a video in backstack #537), no resuming in this PR

Future PR:

  • Searchable playlist dialog.
  • Add UI for resuming past videos
  • Add query for list by services, currently throws unsupported exceptions
  • Video recommendations using history statistics and related video

Please feel free to give recommendations and report bugs, though please don't test this on a real device yet.

@avently
Copy link
Contributor

avently commented Jan 18, 2018

Nice work! Awesome PR. Waiting until it gets merged with your future improvements. Are the timestamps in git log real? I mean, did you really write all this code in 3-4 days?

Anyway here is what i think about improvements:

  • possibility to edit playlist name.
  • check before adding something to playlist because now possible to add the same video twice.
  • possibility to add to playlist from suggested videos.
  • icon in a video that already added to playlist should be different. Now it's "plus" but if video already in playlist it should be "minus" (or something else if you want to support one video -> multiple playlists).

Also app crashes with this:

java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference\n\tat android.widget.Toast.<init>(Toast.java:103)\n\tat android.widget.Toast.makeText(Toast.java:256)\n\tat org.schabi.newpipe.fragments.local.PlaylistCreationDialog.lambda$null$0$PlaylistCreationDialog(PlaylistCreationDialog.java:84)\n\tat org.schabi.newpipe.fragments.local.PlaylistCreationDialog$$Lambda$1.accept(Unknown Source)\n\tat io.reactivex.internal.operators.maybe.MaybeCallbackObserver.onSuccess(MaybeCallbackObserver.java:72)\n\tat io.reactivex.internal.operators.maybe.MaybeObserveOn$ObserveOnMaybeObserver.run(MaybeObserveOn.java:104)\n\tat

@theScrabi
Copy link
Member

Awsome PR !!!
Is it possible to put videos from different services into one playlist?

@karyogamy
Copy link
Contributor Author

karyogamy commented Jan 18, 2018

Thanks guys.

@avently It actually took me a week to get the first commit in since I needed to think through the design. But the rest are just CRUD work and UI, which I mostly just piggybacked on the existing InfoItem UIs. Funny though, I thought about the same when you were able to merge and refactor the popup player so quickly =D

possibility to edit playlist name.

Yep, updated the todos.

check before adding something to playlist because now possible to add the same video twice.
icon in a video that already added to playlist should be different. Now it's "plus" but if video already in playlist it should be "minus" (or something else if you want to support one video -> multiple playlists).

These are probably stretch goals though, since I want to minimize the impact on VideoDetailFragment before your PR is merged.

possibility to add to playlist from suggested videos.

I've thought about adding an option to add to playlist on every stream info item as well, but in #810 there was an agreement that the dialog controls do not include functionality that is easily accessible after the fragment is opened. @theScrabi what do you think about this?

Exception should be fixed, thanks again.

@theScrabi

Is it possible to put videos from different services into one playlist?

Yep, currently each playlist just keep track of the items on the streams table, which contains of the service id. In the future, we can even do queries to filter playlist by service ids, if someone fancies that.

@avently
Copy link
Contributor

avently commented Jan 18, 2018

@karyogamy

here was an agreement that the dialog controls do not include functionality that is easily accessible after the fragment is opened.

In some places of the world an internet connection is slow or costs much money. And if user searches, for example, he maybe want to watch videos later from playlist/bookmarks. That's a use case. There is an option in settings which named autoplay. So every load of fragment will trigger video downloading. Also landscape orientation will trigger to do it too.
We can remove three menu items because there will be two much buttons. This items are: Play in popup and Play in background, and Play. In popup player I already support next/previous buttons so this three menu items unneeded. "Play" button is reeeealy useless since play action will be called after user selects a video with tap

@karyogamy
Copy link
Contributor Author

karyogamy commented Jan 19, 2018

In some places of the world an internet connection is slow or costs much money. And if user searches, for example, he maybe want to watch videos later from playlist/bookmarks. That's a use case. There is an option in settings which named autoplay. So every load of fragment will trigger video downloading. Also landscape orientation will trigger to do it too.

Not arguing with you here, I want to be able to do that as well, especially this feature is available on the official Youtube app. I'll will add this at the last stages when the PR is feature complete and we are just working on the UI. The code to do this is already there, we just need to add an item to the dialog.

We can remove three menu items because there will be two much buttons. This items are: Play in popup and Play in background, and Play. In popup player I already support next/previous buttons so this three menu items unneeded. "Play" button is reeeealy useless since play action will be called after user selects a video with tap

I think this would better be done under a separate PR after yours is merged though, since this is a low hanging fruit on UI upgrade and is not really related to the core functions of both your PR and mine.

@TobiGr
Copy link
Member

TobiGr commented Jan 19, 2018

I'm also about to write my exams, so I just took a quick look and it looks great!
I'd suggest to replace BOOKMARKS on the main page by the bookmark icon.
Maybe we should discuss what to do with the video information and quick actions on smaller phones as they are quite crowed:

screenshot_20180119-200817

@karyogamy
Copy link
Contributor Author

Haha, yeah =P The UI are a mess right now as I'm just aiming for feature completion at the moment. The icon for add to doesn't even line up with the icons to its right.

Please feel free to suggest a new UI design and bookmark icon though, I'll implement it towards the end of the PR.

@theScrabi
Copy link
Member

I tought about this problem. Maybe we could add a dropdown menu when there is not enogh space, but Im not yet convinced of that idea.

@karyogamy
Copy link
Contributor Author

@theScrabi What about swapping the line for uploader with the addTo+background+popup buttons. This way we can pull another button from the top and make a row of 4 buttons similar to the Youtube app.

@theScrabi
Copy link
Member

Yea I think it was good.

@ghost
Copy link

ghost commented Jan 22, 2018 via email

@theScrabi
Copy link
Member

Yea I'd prefer the bookmark icon.

@avently
Copy link
Contributor

avently commented Jan 27, 2018

@karyogamy as a suggestion for the future. If user opens a playlist (not just single video) from a channel or a search the "plus" button in the fragment should save the whole playlist as a local playlist. Maybe with a switch in the UI. Something like that:
Save all videos from playlist [×]

@karyogamy
Copy link
Contributor Author

I was thinking about that too, though I'm still trying to choose between saving the immediate partial playlist or downloading the full playlist (which might be a problem when the whole thing has few thousand videos) before saving.

@karyogamy
Copy link
Contributor Author

Local playlist is now feature complete and below are some updated screenshots:

Some things to note:

  • The "add to playlist" option is added only to base list stream items (search, trending fragments, etc.). Tell me if it's better to have this option across other stream items (e.g. on channel and playlist fragments).
  • I've opted to only put 3 icons in the control row on the new detail fragment UI. This is mostly because pulling down any icons from the top breaks consistency with other fragments.

Some cleaning up and bug hunting have already been done but there are likely some areas I have missed. So please feel free to take a look at the code, provide suggestions or ask questions.

@justanidea
Copy link
Contributor

Thank you so much for this amazing work!!

@ghost
Copy link

ghost commented Jan 31, 2018

I am very anxious to see this work in newpipe! many thanks!!!

later next could add a fourth button, along with add to the playlist, background and popup, the button to see the comments
but that would be another topic thread

@TobiGr
Copy link
Member

TobiGr commented Jan 31, 2018

Great work!
I just noticed when changing the player while playing a video the video is counted again and the number of views in the watch history increases.
Maybe we should also re-think the watch and search history since you added the most watch section. I was a little confused that I am not able to delete entries in your new watch history but only in the old one. IMO we should combine both and offer two modes: a normal mode which opens the "Play & Enqueue" dialog and another which allows the user to delete entries.
But we don't have to do this now.

@karyogamy
Copy link
Contributor Author

karyogamy commented Jan 31, 2018

I agree, the old history panel should really be part of the fragments rather than a new management activity. The problem is that the new history uses aggregate entries and the old history shows entries like a broswer does (finer control over which entries to delete), and a switch would show two different list on the same fragment. IMHO, this should probably be done in another PR to simplify history, since it can easily introduce bugs and other unexpected behaviors.

However, deleting aggregate entries straight from the new history fragment can be easily done, as the same db queries are already used by the old history management. This is not what I intend how the aggregate history to be used, but such feature should be easy to add in this PR. What do you think?

@mauriciocolli
Copy link
Contributor

mauriciocolli commented Feb 3, 2018

@karyogamy what about an option which the user can "save" an online playlist (just url, name and thumbnail), similar to what YouTube does? I think this option would be better suited in the place of the current "Add to playlist".

It would be extremely useful for me, as I have some musics playlist which would be saved and it'd be in-sync when I want to play it (I imagine this is a very common use case).

So the current alternatives to this option ("Add to playlist"):

  • We create an option to "Add all to local playlist" (which @avently suggested)
  • Let the user select what they want to add (mass-adding) with an option to select all
    • I found this useful too, but I think we could let it for another PR
Also, I'll try to see this PR more in-depth soon.

@karyogamy
Copy link
Contributor Author

The first approach might be more feasible. We can add another table which only keeps remote playlists. The only problem I see is that the video count or thumbnails will be outdated until every time the user accesses the playlist. On such remote playlist, the user would open playlist fragment where everything is unmodifiable, including the name, thumbnail and video list.
Also, I wonder if we should create a subscription page for remote playlist rather than lumping them together with the local playlist.

My problem with the second approach (adding all to playlist) is how we deal with the UI. Currently, you can manually load the playlist to its bottom and save it as a local playlist. Since the video list can be very long, meaning that when saving the playlist, we need to send out a bunch of request in sequence before we can save it. During this time, we either need a blocking UI, which is discouraged. Or we can either keep the request going in the background, which may lag the app and potentially the database. Or we kill the request when user switches out, which would also be undesirable.

Let me know what you think about the options. Right now I'm trying to get a proof of concept out for subtitles and aspect ratio support on the video player, and will get back to this soon.

@oceanwaves90
Copy link

oceanwaves90 commented Feb 6, 2018

Hi. Thank you for your work!

By the way, it throws NPE on the latest commit(actually on some recent commits), only when I try to open this video. Other videos seem working right except for this video.

[Edit] It happens when playing videos of the specific channel MBCkpop. Is it because of a regional restriction problem in getting available subtitle information?

4380/org.schabi.newpipe.debug E/class org.schabi.newpipe.report.ErrorActivity: java.lang.NullPointerException: Attempt to invoke virtual method 'int com.grack.nanojson.JsonArray.size()' on a null object reference at org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor.getAvailableSubtitlesInfo(YoutubeStreamExtractor.java:743) at org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor.onFetchPage(YoutubeStreamExtractor.java:558) at org.schabi.newpipe.extractor.Extractor.fetchPage(Extractor.java:61) at org.schabi.newpipe.extractor.stream.StreamInfo.getInfo(StreamInfo.java:248) at org.schabi.newpipe.extractor.stream.StreamInfo.getInfo(StreamInfo.java:240) at org.schabi.newpipe.util.ExtractorHelper.lambda$getStreamInfo$3$ExtractorHelper(ExtractorHelper.java:109) at org.schabi.newpipe.util.ExtractorHelper$$Lambda$3.call(Unknown Source) at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:45) at io.reactivex.Single.subscribe(Single.java:2801) at io.reactivex.internal.operators.single.SingleDoOnSuccess.subscribeActual(SingleDoOnSuccess.java:35) at io.reactivex.Single.subscribe(Single.java:2801) at io.reactivex.internal.operators.maybe.MaybeFromSingle.subscribeActual(MaybeFromSingle.java:41) at io.reactivex.Maybe.subscribe(Maybe.java:3749) at io.reactivex.internal.operators.maybe.MaybeConcatArray$ConcatMaybeObserver.drain(MaybeConcatArray.java:153) at io.reactivex.internal.operators.maybe.MaybeConcatArray$ConcatMaybeObserver.request(MaybeConcatArray.java:78) at io.reactivex.internal.operators.flowable.FlowableElementAtMaybe$ElementAtSubscriber.onSubscribe(FlowableElementAtMaybe.java:66) at io.reactivex.internal.operators.maybe.MaybeConcatArray.subscribeActual(MaybeConcatArray.java:42) at io.reactivex.Flowable.subscribe(Flowable.java:13068) at io.reactivex.internal.operators.flowable.FlowableElementAtMaybe.subscribeActual(FlowableElementAtMaybe.java:36) at io.reactivex.Maybe.subscribe(Maybe.java:3749) at io.reactivex.internal.operators.maybe.MaybeToSingle.subscribeActual(MaybeToSingle.java:46) at io.reactivex.Single.subscribe(Single.java:2801) at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89) at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:452) at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66) at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:154) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) at java.lang.Thread.run(Thread.java:818)

@karyogamy
Copy link
Contributor Author

Yikes, didn't even notice this one. Fixed, thanks.

-Fixed local playlist header margins.
@karyogamy
Copy link
Contributor Author

karyogamy commented Feb 8, 2018

I've decided to drop the dependency on LeakCanary in this PR, since it is hogging the app way too much. I'll create another PR to solely introduce LeakCanary as well as adding leak detection of fragments to it.

@mauriciocolli
Copy link
Contributor

@karyogamy I don't think having two menu items is a good idea, because is inefficient and as you may have noticed, there's a blinking when the buttons are repositioning (appearing/disappearing).

Instead, use the setIcon and setTitle for updating the menu buttons, something like this snippet:

private void updateBookmarkButtonsVisibility() {
    if (playlistBookmarkButton == null) return;

    int iconAttr = playlistEntity == null ? R.attr.ic_playlist_add : R.attr.ic_playlist_check;
    int titleRes = playlistEntity == null ? R.string.bookmark_playlist : R.string.unbookmark_playlist;

    playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
    playlistBookmarkButton.setTitle(titleRes);
}

/** Determines if the fragment is part of the main fragment view pager.
* If so, then this method must be overriden to return true
* in order to show the hamburger menu. */
protected boolean isPartOfFrontPager() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to just drop this function and this call to setDisplayHomeAsUpEnabled, and just let the already implemented function in the activity handle it.

This check should be used for other things such as: don't inflate menus or change the toolbar too much.

-Fixed bookmarking disposable not part of playlist fragment lifecycle.
-Rearranged local fragment directory structure.
-Fixed bookmark button flickering on visibility toggling.
-Removed toolbar up button control from local fragments, delegating functionality back to main fragment.
-Updated extractor to latest.
@karyogamy
Copy link
Contributor Author

there's a blinking when the buttons are repositioning ... use the setIcon and setTitle for updating the menu buttons

Totally forgot about the new ThemeHelper, thanks for pointing that out. Fixed.
The visibility change/flicker is actually used prevent users from clicking to soon when the query is still being run. I changed this to use an atomic boolean for signalling if the button is ready, so the flickering should be gone now.

I think it's better to just drop this function and this call to setDisplayHomeAsUpEnabled, and just let the already implemented function in the activity handle it.

Ahh, I see what you saying. Fixed/removed.

…rom search page.

-Fixed NPE when playlist fragment is destroyed while renaming.
-Fixed remote playlist thumbnail to use uploader avatar when thumbnail url is unavailable.
-Added dispose on exit to all database requests in local playlist fragment.
@karyogamy
Copy link
Contributor Author

Found a quirk in the Soundcloud playlist extractor. Where the url for playlist (info item) in search result uses https, the returned playlist info uses the permalink for its url, which is in http.

This discrepency initially causes the bookmark button to stop working properly on Soundcloud when the playlist fragment is opened from a search page, but is now fixed.

However, this makes me think that we should store links in the database using URN (excluding the http and https protocols) rather than URL. What do you guys think about this, please let me know.

@theScrabi
Copy link
Member

I just saw, in the new menu where we put popup playlist, etc we could also put the download icon into. Like the real yt app.

@karyogamy
Copy link
Contributor Author

karyogamy commented Feb 11, 2018

Can you clarify which popup playlist you are referring to? I don't have Youtube Red so no download button anywhere here.

Edit: Though I think playlist downloading would probably be best if done in another PR, after we introduce a better downloader.

@theScrabi
Copy link
Member

theScrabi commented Feb 11, 2018

Basicly I mean this:
screenshot_20180211-023953

@theScrabi
Copy link
Member

theScrabi commented Feb 11, 2018

However, this makes me think that we should store links in the database using URN (excluding the http and https protocols) rather than URL

Yea why not. Maybe we could embed a method somewhere that uprise the protocol from http to https.

@theScrabi theScrabi merged commit cb41afb into TeamNewPipe:dev Feb 11, 2018
@karyogamy
Copy link
Contributor Author

download icon ... Like the real yt app.

Done.

However, this makes me think that we should store links in the database using URN (excluding the http and https protocols) rather than URL

Yea why not. Maybe we could embed a method somewhere that uprise the protocol from http to https.

Sorry, I didn't think this through yesterday and have just realized that it is possible for future streaming services to be http only. So for now, we should still let the extractor manage the url but we need to keep the protocol consistent. In later times, we can create an api in the extractor to provide all the possible protocols and create a migration script to get rid of the prefix in the urls in the database.

@karyogamy
Copy link
Contributor Author

Yikes, few seconds too late =P

@theScrabi
Copy link
Member

theScrabi commented Feb 11, 2018

download icon ... Like the real yt app.
Done.

I did do it as well now xD
Se here

@avently
Copy link
Contributor

avently commented Feb 11, 2018

@theScrabi what this line do?
https://github.com/TeamNewPipe/NewPipe/blame/dev/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java#L550

With it openPopupPlayer and openBackgroundPlayer called 4 times. Without it everything is fine

@avently
Copy link
Contributor

avently commented Feb 12, 2018

@karyogamy it is possible to add the same video into one playlist multiple times.

P.S. I merged your changes. Let me know if I broke something. And thanks for this PR

@theScrabi
Copy link
Member

@avently you are right, i fixed the line.

@karyogamy
Copy link
Contributor Author

@avently

it is possible to add the same video into one playlist multiple times.

Yes, though I haven't added the ability to see duplicate entries when the add dialog is open (which can get annoying), this functionality should be fairly easy to implement now if anyone wants to give it a shot =D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants