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

Fix double tap issue on Mobile #90

Merged
merged 1 commit into from
Sep 11, 2022
Merged

Conversation

dantovbein
Copy link

What is done

Fix double tap issue on Mobile
Currently, you must to double tab a video and is very annoying for the users.

Fetch YouTube API script on demand
The client fetch the YouTube API Library only when the user click a placeholder video by the first time.

It doesn't use iframes directly
iframes are using internally through the YouTube API Library

Keep the performance stunning
It doesn't impact to the performance because the library is not being fetched at the same time that the page is being loaded. You can validate against the two attached reports below.

Lighthouse

The reports are almost the same, in fact, you can see on the first screenshot that the video placeholder is being shown more faster than the previous implementation.

This is the audit related to the new implementation /variants/solo:

Screenshot 2021-07-14 at 08 07 47

This is the audit related to the previous implementation /variants/solo:
Screenshot 2021-07-14 at 08 11 22

playerVars: { 'autoplay': 1, 'playsinline': 1 },
events: {
'onReady': (event) => {
event.target.mute();

Choose a reason for hiding this comment

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

Why mute?

Choose a reason for hiding this comment

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

Removed. It was used for testing.
Thanks!

Currently, you must to double tab a video and is very annoying for the users.

Fetch YouTube API script on demand
The client fetch the YouTube API Library only when the user click a placeholder video by the first time.

It doesn't use iframes directly
iframes are using internally through the YouTube API Library

Keep the performance stunning
It doesn't impact to the perfomance because the library is not being fetched at the same time that the page is being loaded
@oxyc
Copy link

oxyc commented Jul 21, 2021

Tested it out and it's not working with safari on iOS

Edit: seems it worked while you had it muted

@dantovbein
Copy link
Author

dantovbein commented Jul 22, 2021 via email

@oxyc
Copy link

oxyc commented Jul 22, 2021

iOS 14.6

@GP-Dan-Tovbein
Copy link

GP-Dan-Tovbein commented Jul 29, 2021

iOS 14.6

@oxyc I tested on IOs 14.6 Safari and works fine 🤔

Screenshot 2021-07-29 at 14 26 34

@@ -40,6 +39,19 @@ class LiteYTEmbed extends HTMLElement {
// TODO: support dynamically setting the attribute via attributeChangedCallback
}

fetchYoutubeAPI(cb) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not make this a promise?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it could be.
But honestly, I don't see any blocker in this case.

onYouTubeIframeAPIReady() {
const videoWrapper = document.createElement('div')
videoWrapper.id = this.videoId;
document.body.appendChild(videoWrapper);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto on only be manipulating the custom element structure. Not the main body. Please use 'this.appendChild' or reference a node within the element to bind to.

Copy link
Author

Choose a reason for hiding this comment

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

Good point 👍

var tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
tag.async = true;
var firstScriptTag = document.getElementsByTagName('script')[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't be manipulating things outside of the element itself. Please just add the script within the element's scope.

Copy link
Author

Choose a reason for hiding this comment

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

I don't agree in this case, because you're fetching the library on demand

Copy link

@kevinfarrugia kevinfarrugia left a comment

Choose a reason for hiding this comment

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

Looking forward to this PR. 👏

I tested on Chrome on Android and it works well, however on iOS Simulator (iPhone 5s 10.3.1 and iPhone X 14.4) and it doesn't work - video still plays but requires double tap.

Also there is an issue that it doesn't work if there are multiple embeds on the same page. I am not sure if it will be solved after addressing @Garbee 's comments. Perhaps a page should be added to the repo with two videos to cover it during testing.

You may want to fetch the latest changes from upstream. Left you some minor comments. Please let me know if you would like a hand tackling the requested changes, happy to assist.

this.insertAdjacentHTML('beforeend', iframeHTML);
onYouTubeIframeAPIReady() {
const videoWrapper = document.createElement('div')
videoWrapper.id = this.videoId;

Choose a reason for hiding this comment

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

From what I understand this line is not needed.

@@ -40,6 +39,19 @@ class LiteYTEmbed extends HTMLElement {
// TODO: support dynamically setting the attribute via attributeChangedCallback
}

fetchYoutubeAPI(cb) {
var tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';

Choose a reason for hiding this comment

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

There is a preconnect to https://www.youtube-nocookie.com that is no longer relevant and should possible be replaced by:

LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com');

Heads up that following this change, it will connect to youtube.com not the privacy-enhanced youtube-nocookie.com.

@vucurovicmarko
Copy link

Fixed dobule tap issue on mobile with this PR, but now multiple videos won't play on the same page.

@philwolstenholme
Copy link
Contributor

philwolstenholme commented Sep 21, 2021

Does this definitely work in iOS and for non-muted videos? I've tried a similar approach to this in the past and didn't get anywhere close to it working. I've not tried this PR but it doesn't look different from the code I tried that didn't work on mobile devices.

If we look at https://developers.google.com/youtube/iframe_api_reference#Autoplay_and_scripted_playback we can see this quote:

The HTML5 <video> element, in certain mobile browsers (such as Chrome and Safari), only allows playback to take place if it's initiated by a user interaction (such as tapping on the player). Here's an excerpt from Apple's documentation:

"Warning: To prevent unsolicited downloads over cellular networks at the user’s expense, embedded media cannot be played automatically in Safari on iOS — the user always initiates playback."

Due to this restriction, functions and parameters such as autoplay, playVideo(), loadVideoById() won't work in all mobile environments.

This PR uses playVideo(); and in my experience, that isn't enough to reliably avoid the double-tap issue for unmuted videos on iOS.


The only hack I found to get around this issue on mobile was to have the iframe element always on the page but set to be pretty much invisible (opacity: 0.01 with CSS) with a custom cover image behind it. We also had to load the iframe API JS, then have an onStateChange event listener that made the iframe fully visible (opacity: 1) once the iframe has been clicked on and the video was playing. This allowed us to use a custom cover image and avoid double taps on mobile, but it didn't have any of the performance benefits as the iframe element had to exist on the page and the YouTube iframe API JS also had to be loaded before the video could be interacted with. It was also very buggy, resulted in a video playing with audio but no picture if the JS failed, and if the user clicked part of the 'invisible' iframe that didn't result in playback (e.g. the bottom part of the video where the volume, fullscreen, link to YouTube, Chromecast etc buttons are) then weird things happened that the users weren't expecting 👎

@legendofmi
Copy link

Does this definitely work in iOS and for non-muted videos? I've tried a similar approach to this in the past and didn't get anywhere close to it working. I've not tried this PR but it doesn't look different from the code I tried that didn't work on mobile devices.

If we look at https://developers.google.com/youtube/iframe_api_reference#Autoplay_and_scripted_playback we can see this quote:

The HTML5 <video> element, in certain mobile browsers (such as Chrome and Safari), only allows playback to take place if it's initiated by a user interaction (such as tapping on the player). Here's an excerpt from Apple's documentation:

"Warning: To prevent unsolicited downloads over cellular networks at the user’s expense, embedded media cannot be played automatically in Safari on iOS — the user always initiates playback."

Due to this restriction, functions and parameters such as autoplay, playVideo(), loadVideoById() won't work in all mobile environments.

This PR uses playVideo(); and in my experience, that isn't enough to reliably avoid the double-tap issue for unmuted videos on iOS.

The only hack I found to get around this issue on mobile was to have the iframe element always on the page but set to be pretty much invisible (opacity: 0.01 with CSS) with a custom cover image behind it. We also had to load the iframe API JS, then have an onStateChange event listener that made the iframe fully visible (opacity: 1) once the iframe has been clicked on and the video was playing. This allowed us to use a custom cover image and avoid double taps on mobile, but it didn't have any of the performance benefits as the iframe element had to exist on the page and the YouTube iframe API JS also had to be loaded before the video could be interacted with. It was also very buggy, resulted in a video playing with audio but no picture if the JS failed, and if the user clicked part of the 'invisible' iframe that didn't result in playback (e.g. the bottom part of the video where the volume, fullscreen, link to YouTube, Chromecast etc buttons are) then weird things happened that the users weren't expecting 👎

You are right. I tested this solution on iPadOS Safari and it still requires double tapping. However, it does work on MacOS Safari which wasn't working before, so I would say it's still an improvement.

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.

None yet

10 participants