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

[Android] youtube fullscreen in landscape and pip #23933

Closed
wants to merge 5 commits into from

Conversation

tapanmodh
Copy link
Contributor

@tapanmodh tapanmodh commented May 31, 2024

Resolves brave/brave-browser#37267

Resolves brave/brave-browser#32846 (Closed in favor of brave/brave-browser#37267)
Resolves brave/brave-browser#26670 (Closed in favor of brave/brave-browser#37267)

⚠️ Note: superseded by #26355

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
    https://github.com/brave/reviews/issues/1607
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

Brave should launch the full screen youtube player when it detects a user has rotated their device.
Brave should launch the pip mode when youtube video is playing


String pattern =
"^(?:https?:)?(?:\\/\\/)?(?:youtu\\.be\\/|(?:www\\.|m\\.)?youtube\\.com\\/(?:watch|v|embed)(?:\\.php)?(?:\\?.*v=|\\/))([a-zA-Z0-9\\_-]{7,15})(?:[\\?&][a-zA-Z0-9\\_-]+=[a-zA-Z0-9\\_-]+)*(?:[&\\/\\#].*)?$";
Pattern compiledPattern = Pattern.compile(pattern);
Copy link
Contributor

Choose a reason for hiding this comment

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

Android Chromium has url/android/java/src/org/chromium/url/GURL.java, I think we should use that instead of regexing. youtu.be links are redirected to youtube.com, that eTLD+1 is all we need to check regarding the domain. Please check this native implementation of YT video URL matching here https://github.com/brave/brave-core/pull/20730/files#diff-21b9f0db07f562d309edd861da6dea61508a287c552ac00f08bb70aa121c5b09R34

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you @stoletheminerals. I'll update it

@tapanmodh tapanmodh force-pushed the youtube_fullscreen_landscape branch from 32e7eb9 to 7c66332 Compare June 3, 2024 19:23
Copy link
Member

@SergeyZhukovsky SergeyZhukovsky left a comment

Choose a reason for hiding this comment

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

++

}
}

private boolean isYTVideoUrl(GURL url) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@tapanmodh can we place this function into some util class ? i think we are already using similar function at other places

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are using this function in two places in BraveActivity class. we can place into util class but it is not being used by any another class. I think we should move after it being used by another class.

Copy link
Collaborator

@bridiver bridiver Aug 5, 2024

Choose a reason for hiding this comment

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

we need some kind of test for this so would it be easier to unit test if we put it someplace else? Or actually maybe it's better to pass the url and isInPictureInPictureMode to BackgroundVideoPlaybackTabHelper and change it to maybeToggleFullscreen so we can test it in a normal browser test and also encapsulate the logic for handling this in a single place.

interface Natives {
void sendOrientationChangeEvent(WebContents webContents, boolean isFullScreen);

boolean isPlayingMedia(WebContents webContents);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to expose this function ? can we check idf the media is being played before we inject the script ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are not injecting the script in this case. we are checking only when user press the home button. we have to display PIP on portrait mode if video is playing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

doesn't FullscreenVideoPictureInPictureController already handle this? Also isPictureInPictureAllowedForFullscreenVideo in webcontents?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have to display PIP from portrait mode and in that case FullscreenVideoPictureInPictureController is not handling this.

Copy link
Contributor

@deeppandya deeppandya left a comment

Choose a reason for hiding this comment

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

LGTM

@deeppandya
Copy link
Contributor

@stoletheminerals do you have any concerns with changes ?

Copy link
Contributor

@stoletheminerals stoletheminerals left a comment

Choose a reason for hiding this comment

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

lgtm

std::string script;
if (is_full_screen) {
script =
"if(!document.fullscreenElement) {"
Copy link
Collaborator

Choose a reason for hiding this comment

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

If YT updates their site and these classes/ids no longer match then Brave is going to look broken.

Copy link
Collaborator

Choose a reason for hiding this comment

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

also please assign this to constexpr const char16_t and the if/else will just decide which one to run

Copy link
Collaborator

Choose a reason for hiding this comment

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

use uR"js( ... )js" for multiline scripts in general, but I think we need to source this script from a component to handle YT site changes

Copy link
Collaborator

Choose a reason for hiding this comment

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

actually do we even need this script at all? Can't we just call Browser::EnterFullscreenModeForTab?

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry, can't use Browser here for android. On desktop setting it to fullscreen isn't the same as changing the video to fullscreen so not sure if we can use native methods here or not, but we definitely can below to exit

Copy link
Collaborator

Choose a reason for hiding this comment

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

looks like FullscreenManager is the equivalent in java, but not sure if it has the desired behavior here or not

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FullscreenManager does not have any function for desired behavior.

rfh->GetRemoteAssociatedInterfaces()->GetInterface(&script_injector_remote);
return script_injector_remote;
}
void JNI_BackgroundVideoPlaybackTabHelper_SendOrientationChangeEvent(
Copy link
Collaborator

Choose a reason for hiding this comment

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

this method name doesn't make sense to me, it's not sending an orientation change, it's toggling YT fullscreen

Copy link
Collaborator

Choose a reason for hiding this comment

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

also why are we putting this inside background_video_playback_tab_helper.cc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are already doing background audio playback for youtube videos in this class. I'll change the class name.

"}";
}
GetRemote(web_contents->GetPrimaryMainFrame())
->RequestAsyncExecuteScript(
Copy link
Collaborator

Choose a reason for hiding this comment

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

the use of RequestAsyncExecuteScript is not needed here because we're not executing anything that returns a result. web_contents()->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld is fine here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

web_contents()->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld gives Failed to execute 'requestFullscreen' on 'Element': API can only be initiated by a user gesture. error. Can we execute script with user gesture in ExecuteJavaScriptInIsolatedWorld?

" if (moviePlayer) {"
" moviePlayer.click();"
" }"
" setTimeout(() => {"
Copy link
Collaborator

Choose a reason for hiding this comment

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

why does this use setTimeout?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Youtube is not loading controllers initially for this usecase and because of that we are using setTimeout.

namespace chrome {
namespace android {

mojo::AssociatedRemote<script_injector::mojom::ScriptInjector> GetRemote(
Copy link
Collaborator

Choose a reason for hiding this comment

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

helper methods should go in anonymous namespaces

@bridiver
Copy link
Collaborator

This needs tests. Please use a simple test page with a media element with a fullscreen button

" }"
"}";
} else {
script =
Copy link
Collaborator

Choose a reason for hiding this comment

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

WebContents::HasActiveEffectivelyFullscreenVideo
WebContents::ExitFullscreen

" document._addEventListener(a,b,c);"
" }"
" };"
" if(a != 'visibilitychange') {"
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is this script even doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is handling background audio playback if we put app in background while video is playing

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see why you need to keep a reference to the original function in document._addEventListener and it could potentially cause namespace collision problems. Also the timing of calling it in DidFinishNavigation does not guarantee that it will happen before any visibility change so you could end up in a place where it gets permanently set to not visible. This needs to be injected in the renderer and should not keep the original in document._addEventListener. You don't need to worry about calling it more than once if you inject it only at the right time.

Copy link
Collaborator

Choose a reason for hiding this comment

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

also there should be comments explaining what this is doing

const base::android::JavaParamRef<jobject>& jweb_contents) {
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents);
content::MediaSessionImpl* media_session_impl =
Copy link
Collaborator

Choose a reason for hiding this comment

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

you cannot access MediaSessionImpl outside of content. The only reason this compiles is because #include "content/browser/web_contents/web_contents_impl.h" is already incorrectly included and should be changed to #include "content/public/browser/web_contents.h" and check_includes = false should be removed from the target

Copy link
Collaborator

Choose a reason for hiding this comment

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

the correct way to get the status of media playing is to implement WebContentsObserver::MediaStartedPlaying/WebContentsObserver::MediaStoppedPlaying` with a tab helper, but as mentioned in the comment above it doesn't seem like this method is even necessary

Copy link
Collaborator

Choose a reason for hiding this comment

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

diff --git a/browser/android/preferences/BUILD.gn b/browser/android/preferences/BUILD.gn
index 17a3651be37..20369a29f0c 100644
--- a/browser/android/preferences/BUILD.gn
+++ b/browser/android/preferences/BUILD.gn
@@ -7,8 +7,6 @@ import("//brave/components/ai_chat/core/common/buildflags/buildflags.gn>
 import("//build/config/android/rules.gni")
 
 source_set("preferences") {
-  # Remove when https://github.com/brave/brave-browser/issues/10657 is resolved
-  check_includes = false
   sources = [
     "background_video_playback_tab_helper.cc",
     "background_video_playback_tab_helper.h",
diff --git a/browser/android/preferences/background_video_playback_tab_helper.cc b/brow>
index fdbdcd95c11..e2a15155ab2 100644
--- a/browser/android/preferences/background_video_playback_tab_helper.cc
+++ b/browser/android/preferences/background_video_playback_tab_helper.cc
@@ -7,14 +7,10 @@
 
 #include <string>
 
-#include "base/strings/utf_string_conversions.h"
 #include "brave/browser/android/preferences/features.h"
 #include "brave/components/brave_shields/content/browser/brave_shields_util.h"
-#include "brave/components/constants/pref_names.h"
-#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/prefs/pref_service.h"
-#include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have changed it to implement WebContentsObserver::MediaStartedPlaying/WebContentsObserver::MediaStoppedPlaying

@tapanmodh tapanmodh force-pushed the youtube_fullscreen_landscape branch from 7c66332 to 14a4d00 Compare June 24, 2024 08:21
->RequestAsyncExecuteScript(
ISOLATED_WORLD_ID_BRAVE_INTERNAL, script,
blink::mojom::UserActivationOption::kActivate,
blink::mojom::PromiseResultOption::kAwait, base::NullCallback());
Copy link
Contributor

Choose a reason for hiding this comment

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

reported by reviewdog 🐶
[semgrep] RequestAsyncExecuteScript usages should be vet by the security-team.

References:
- https://github.com/brave/brave-browser/wiki/Security-reviews (point 13)


Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml


Cc @thypon @diracdeltas @bridiver

} )js";
GetRemote(web_contents->GetPrimaryMainFrame())
->RequestAsyncExecuteScript(
ISOLATED_WORLD_ID_BRAVE_INTERNAL, script,
Copy link
Contributor

Choose a reason for hiding this comment

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

reported by reviewdog 🐶
[semgrep] Security hotspot found (ISOLATED_WORLD). A security-team member should analyze the code security for possible vulnerabilities.

Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-isolated-world.yaml


Cc @thypon @diracdeltas @bridiver

@kjozwiak
Copy link
Member

Removing from the 1.68.x milestone as the above wasn't even merged into master yet.

@kjozwiak kjozwiak removed this from the 1.68.x - Release milestone Jul 24, 2024
@bsclifton
Copy link
Member

Closing in favor of #26355

@bsclifton bsclifton closed this Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants