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

Load full stream info when enqueuing a stream #7036

Merged
merged 10 commits into from
Jan 7, 2022

Conversation

Douile
Copy link
Contributor

@Douile Douile commented Sep 1, 2021

Please feel free to criticize the code here, I am not sure if I am using workers correctly or if there is a better way to do this. I essentially copied the code from

private void runWorker(final boolean forceLoad, final boolean addToBackStack) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
isLoading.set(false);
hideMainPlayer();
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
getString(R.string.show_age_restricted_content), false)) {
hideAgeRestrictedContent();
} else {
handleResult(result);
showContent();
if (addToBackStack) {
if (playQueue == null) {
playQueue = new SinglePlayQueue(result);
}
if (stack.isEmpty() || !stack.peek().getPlayQueue().equals(playQueue)) {
stack.push(new StackItem(serviceId, url, title, playQueue));
}
}
if (isAutoplayEnabled()) {
openVideoPlayer();
}
}
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
url == null ? "no url" : url, serviceId)));
}

I decided loading the information when adding to the queue is better than loading when the video starts playing as it allows the queue progress text to be correct as well.

What is it?

  • Bugfix (user facing)
  • Feature (user facing)
  • Codebase improvement (dev facing)
  • Meta improvement to the project (dev facing)

Description of the changes in your PR

This PR calls getStreamInfo causing a full network fetch of stream info (I believe only if required) when adding a stream item to the queue. This should prevent UI issues of missing metadata when queuing videos that have been fast-loaded and are missing metadata.

Fixes the following issue(s)

APK testing

The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR.

Due diligence

@ktprograms
Copy link
Contributor

I think how you're doing it now will always cause a full network fetch. The best way would probably be to check if (for example) the item.getDuration() is null, and only then doing the fetch. If it isn't null, you can just keep the old code (so have an if / else statement).

Here's the before and after of a similar case of adding the network fetch I recently implemented:

Before:

show_channel_details(R.string.show_channel_details, (fragment, item) ->
// For some reason `getParentFragmentManager()` doesn't work, but this does.
NavigationHelper.openChannelFragment(
fragment.requireActivity().getSupportFragmentManager(),
item.getServiceId(), item.getUploaderUrl(), item.getUploaderName())
),

After:

show_channel_details(R.string.show_channel_details, (fragment, item) -> {
if (isNullOrEmpty(item.getUploaderUrl())) {
final int serviceId = item.getServiceId();
final String url = item.getUrl();
Toast.makeText(fragment.getContext(), R.string.loading_channel_details,
Toast.LENGTH_SHORT).show();
ExtractorHelper.getStreamInfo(serviceId, url, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
NewPipeDatabase.getInstance(fragment.getContext()).streamDAO()
.setUploaderUrl(serviceId, url, result.getUploaderUrl())
.subscribeOn(Schedulers.io()).subscribe();
openChannelFragment(fragment, item, result.getUploaderUrl());
}, throwable -> Toast.makeText(
// TODO: Open the Error Activity
fragment.getContext(),
R.string.error_show_channel_details,
Toast.LENGTH_SHORT
).show());
} else {
openChannelFragment(fragment, item, item.getUploaderUrl());
}
}),


(In the after, openChannelFragment is a helper method that calls NavigationHelper.openChannelFragment):

private static void openChannelFragment(final Fragment fragment,
final StreamInfoItem item,
final String uploaderUrl) {
// For some reason `getParentFragmentManager()` doesn't work, but this does.
NavigationHelper.openChannelFragment(
fragment.requireActivity().getSupportFragmentManager(),
item.getServiceId(), uploaderUrl, item.getUploaderName());
}

@GneHHHc233
Copy link

Hey, brother, thank you for solving the problem that my device YouTube can't get

@Stypox
Copy link
Member

Stypox commented Sep 1, 2021

The best way would probably be to check if (for example) the item.getDuration() is null, and only then doing the fetch. If it isn't null, you can just keep the old code (so have an if / else statement).

Yes, good idea. The stream info would anyway be fetched when the stream is loaded and played, but not fetching it beforehand is a good idea nonetheless.

@Douile
Copy link
Contributor Author

Douile commented Sep 1, 2021

getStreamInfo does check the cache but it is simple enough to check whether we need to call it so I will add that check.

@Douile
Copy link
Contributor Author

Douile commented Sep 1, 2021

Changes made in #6872 will conflict with this PR so I will wait for it to be merged before continuing here.

@Stypox
Copy link
Member

Stypox commented Sep 1, 2021

getStreamInfo does check the cache

There are "three" "stages" of stream loading:

  1. stream info item loaded with fast feed, with some metadata fields, thus missing duration field
  2. stream info item loaded with any other method (search, trending, ... including slow feed), with all metadata fields
  3. stream info with all of the data needed to play

Stream info items are those loaded alongside other stream info items (i.e. when fetching a list, e.g. search results, trending, feed, ...), while stream info is loaded e.g. when a video is opened or played.

If when calling getStreamInfo the 3rd type of stream loading was already done before, then the cache kicks in, otherwise it doesn't, since getStreamInfo aims to collect all of the data needed to play a video. So preventing an early call to getStreamInfo would be nice, as the cache would not kick in for most stream info items, unless they have already been opened.

@Stypox Stypox closed this Sep 1, 2021
@Stypox Stypox reopened this Sep 1, 2021
@Stypox
Copy link
Member

Stypox commented Sep 1, 2021

GitHub added some keybinding to fast close PRs, and I seem to accidentally keep to press it

Changes made in #6872 will conflict with this PR so I will wait for it to be merged before continuing here.

That's not a problem, git rebase is your friend ;-) (and I can help if needed)

This commit calls getStreamInfo causing a full network fetch of stream
info (I believe only if required) when adding a stream item to the
queue. This should prevent UI issues of missing metadata when queueing
videos that have been fast-loaded and are missing metadata.

Fixes TeamNewPipe#7035
@Douile
Copy link
Contributor Author

Douile commented Sep 18, 2021

When new info is fetched the feed item still doesn't update, I'm not sure where to trigger this update.

@Douile
Copy link
Contributor Author

Douile commented Sep 19, 2021

Ayyy found it, https://github.com/TeamNewPipe/NewPipe/blob/dev/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java#L975

@Douile
Copy link
Contributor Author

Douile commented Sep 20, 2021

Nvm that was the wrong thing...

@Douile
Copy link
Contributor Author

Douile commented Oct 28, 2021

Working now :)

@Douile
Copy link
Contributor Author

Douile commented Nov 4, 2021

lmk if this needs a refactor because I'm not sure if it the best way of doing.

Copy link
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

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

I can confirm it works when enqueueing a stream whose duration is unknown, thank you!

I get this crash when I long press an item in the fast feed, press on "Start playing in the background" and just after that I long press on another item. (both items are without duration before long pressing).

Exception

  • User Action: ui error
  • Request: ACRA report
  • Content Language: en
  • Service: none
  • Version: 0.21.13
  • OS: Linux Android 10 - 29
Crash log

java.lang.NullPointerException: Attempt to invoke virtual method 'int org.schabi.newpipe.player.playqueue.PlayQueue.size()' on a null object reference
	at org.schabi.newpipe.player.helper.PlayerHolder.getQueueSize(PlayerHolder.java:74)
	at org.schabi.newpipe.local.feed.FeedFragment.showStreamDialog(FeedFragment.kt:336)
	at org.schabi.newpipe.local.feed.FeedFragment.access$showStreamDialog(FeedFragment.kt:80)
	at org.schabi.newpipe.local.feed.FeedFragment$listenerStreamItem$1.onItemLongClick(FeedFragment.kt:395)
	at com.xwray.groupie.GroupieViewHolder$2.onLongClick(GroupieViewHolder.java:32)
	at android.view.View.performLongClickInternal(View.java:7339)
	at android.view.View.performLongClick(View.java:7297)
	at android.view.View.performLongClick(View.java:7315)
	at android.view.View$CheckForLongPress.run(View.java:27850)
	at android.os.Handler.handleCallback(Handler.java:883)
	at android.os.Handler.dispatchMessage(Handler.java:100)
	at android.os.Looper.loop(Looper.java:214)
	at android.app.ActivityThread.main(ActivityThread.java:7356)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)


Comment on lines 64 to 74
enqueue(R.string.enqueue_stream, (fragment, item) -> {
NavigationHelper.enqueueOnPlayer(fragment.getContext(), new SinglePlayQueue(item));
fetchItemInfoIfSparse(fragment, item,
fullItem -> NavigationHelper.enqueueOnPlayer(fragment.getContext(), fullItem)
);
}),

enqueue_next(R.string.enqueue_next_stream, (fragment, item) -> {
NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), new SinglePlayQueue(item));
fetchItemInfoIfSparse(fragment, item,
fullItem -> NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), fullItem)
);
}),
Copy link
Member

Choose a reason for hiding this comment

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

Why did you only do it for enqueue? I also get the problem of the missing duration field in the play queue when I press on "Start playing on ..."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, for some reason I neglected to check those options :)

@Stypox
Copy link
Member

Stypox commented Dec 7, 2021

In order to fix the exception I posted above I think you just need to alter the PlayerHolder.getQueueSize() method to something along these lines

if (!isPlayerOpen()) return 0;
final PlayQueue playQueue = player.getPlayQueue();
return playQueue == null ? 0 : playQueue.size();

Copy link
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

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

Code now looks good, the formatting just needs to be fixed

Douile and others added 2 commits December 12, 2021 12:51
Co-authored-by: Stypox <stypox@pm.me>
Co-authored-by: Stypox <stypox@pm.me>
Co-authored-by: Stypox <stypox@pm.me>

Replace the unecessary callback interface InfoCallback in favour of the
standard type Consumer<SinglePlayQueue>
@sonarcloud
Copy link

sonarcloud bot commented Jan 3, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 3 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

Copy link
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

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

Works perfectly, thank you!

@Stypox Stypox merged commit 7907182 into TeamNewPipe:dev Jan 7, 2022
Copy link
Member

@TobiGr TobiGr left a comment

Choose a reason for hiding this comment

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

While rebasing #7570 I ran into a few smaller things which I would like to get an answer / opinion on.

// helper functions //
/////////////////////////////////////////////

private static void fetchItemInfoIfSparse(final Fragment fragment,
Copy link
Member

Choose a reason for hiding this comment

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

Why is the fragment used as param here? The only usage for fragment is fragment.getContext() in this method.

Comment on lines +238 to +243
.doOnError(throwable -> Log.e("StreamDialogEntry",
throwable.toString()))
.subscribe();

callback.accept(new SinglePlayQueue(result));
}, throwable -> Log.e("StreamDialogEntry", throwable.toString()));
Copy link
Member

Choose a reason for hiding this comment

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

I think we should report these error via the UI. Otherwise the user gets no feedback on his action if something fails. However, I am not sure how to handle network related errors here. @Stypox Any idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we toast the user and fallback to returning the sparse info item then, although that still has its issues.

Copy link
Member

Choose a reason for hiding this comment

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

@Douile Douile mentioned this pull request Feb 4, 2022
5 tasks
This was referenced Feb 11, 2022
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.

Queued videos metadata not properly updating
5 participants