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

onReady not called on iOS Safari #340

Closed
rijk opened this issue Feb 26, 2018 · 15 comments
Closed

onReady not called on iOS Safari #340

rijk opened this issue Feb 26, 2018 · 15 comments

Comments

@rijk
Copy link
Contributor

rijk commented Feb 26, 2018

I am loading an .mp4 file from AWS. On OSX Safari it is working fine, no problems, but on iOS the onReady function is never called. This happens both on v0.25.3, and the latest (v1.2.1) so it's not a recent bug. I did not find any info about this in the "Mobile considerations" section, however.

Is this a known issue? Are there any workarounds?

@rijk
Copy link
Contributor Author

rijk commented Feb 26, 2018

I notice the same issue on http://cookpete.com/react-player/. To reproduce:

  1. Press Pause
  2. Press Files > mp4
  3. Press Play

Expected (from OS X Safari):

screen shot 2018-02-26 at 16 50 58

Actual (iOS):

screen shot 2018-02-26 at 16 51 30

Note that onReady isn't called until after playback has started. This is a problem for me, because I use the onReady callback to deactivate a loading overlay (so as long as that is not called, the user cannot start playback).

@Haemp
Copy link

Haemp commented Feb 27, 2018

Yup, can confirm I have the same issue on Mobile Chrome as well - the onReady event is not called. @rijk Have you tested on Android?

@cookpete
Copy link
Owner

onReady is simply bound to the canplay event when playing video files:

https://github.com/CookPete/react-player/blob/c94f512974e564ef88047b597cb5e62190b35da0/src/players/FilePlayer.js#L57

My guess is that because iOS tends to prevent any loading etc before the user has interacted, the play event is fired as a result of the user action, and the canplay event catches up and gets fired later.

I guess a possible fix would be to manually call onReady before onPlay when the onplay event fires on iOS only? And then prevent onReady from firing again when canplay fires shortly after.

@rijk
Copy link
Contributor Author

rijk commented Feb 28, 2018

Yup, this seems to be the problem. In my case, this created a serious usability issue, as I display a loading spinner overlay until I get the onReady. So when I never get this, the user can never interact with the video :)

I worked around it now by checking the user agent for iOS, and disabling the page's loading state manually in that case.

If we can't do anything to fix this, I do think it would be a good idea to add this to the documentation, under onReady or at least the mobile considerations.

PS: The problem was not related to the order of the calls — at least not for me. So, personally, I see no need to apply a fix there.

@cookpete
Copy link
Owner

PS: The problem was not related to the order of the calls — at least not for me. So, personally, I see no need to apply a fix there.

So what was the problem in the end?

@rijk
Copy link
Contributor Author

rijk commented Feb 28, 2018

Right, so the problem was that the page just kept 'loading' when you opened it on an iPhone. Because the loading overlay blocked the user from interacting with the video. I think it would be good to add this to the onReady docs, that on iOS it is not called until the user interacts with (clicks) the video.

@cookpete
Copy link
Owner

Ah I see. Your loader was preventing user interaction, which was preventing play and canplay events, which was prevent onReady, which was preventing the loader from being removed... etc.

There must be a combination that works. I wonder if load, loadeddata or progress events would be better? I also wonder if adding autoPlay to the <video> would change things (it may fire some useful events without user interaction, even if it doesn't actually autoplay). I'll do some tests when I have some time.

@rijk
Copy link
Contributor Author

rijk commented Feb 28, 2018

You got it.

There must be a combination that works.

Maybe, but I'm not sure. When I test with your demo, no handler is called until you actually tap a button. Autoplay doesn't do anything either; iOS just doesn't allow playing unless the user has explicitly requested it (to save data, I suppose).

@cookpete
Copy link
Owner

Autoplay doesn't do anything either; iOS just doesn't allow playing unless the user has explicitly requested it (to save data, I suppose).

Yep, this is a popular issue that people are constantly battling against.

@GianlucaGuarini
Copy link

GianlucaGuarini commented Mar 20, 2018

I hope my patch could help anyone landing here:

componentDidMount() {
   // on iOS we need to force the load event
   // to get the onReady event firing properly
   // where of course this.refs.video is so rendered: <ReactPlayer ref='video'>
   const videoElement = this.refs.video.getInternalPlayer()
   videoElement.load()
}

@rijk
Copy link
Contributor Author

rijk commented Mar 23, 2018

Awesome!

david-hub024 pushed a commit to david-hub024/React_VideoPlayer that referenced this issue Dec 23, 2018
Calling `load()` manually fires onReady before `onPlay`, instead of after
Fixes cookpete/react-player#340
@scottmcdonnell
Copy link

This is great, but the IOS 13 on tablets has changed the useragent for Safari.
On iphone it says (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)
but on iPad and iPad Pro its (Macintosh; Intel Mac OS X 10_15) just like on Safari desktop. Infuriating!

I have rolled a quick fix which is to test for Safari and also touch device and call the player.load() something like:

/**
* Modernizr's way of checking it
https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
*/

function testTouchDevice() {
    var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
    var mq = function(query) {
    return window.matchMedia(query).matches;
    }
    if (('ontouchstart' in window) || window.DocumentTouch) {
       return true;
    }
    // include the 'heartz' as a way to have a non matching MQ to help terminate the join
    // https://git.io/vznFH
    var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
    return mq(query);
}
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var isTouchDevice = testTouchDevice();

var isIOS = IOS  || (isSafari && isTouchDevice);

...

 if (isIOS){
       this.player.load();
 }

There is probably a better way of doing it a touch detection is not too reliable.

david-hub024 added a commit to david-hub024/React_VideoPlayer that referenced this issue May 23, 2020
Calling `load()` manually fires onReady before `onPlay`, instead of after
Fixes cookpete/react-player#340
@brauliodiez
Copy link

Checking on Safari, it works fine for the initial load, but not when you seek (OnSeek) it does not work, should it be monkey patched elsewhere for IOS?

@taitems
Copy link

taitems commented Sep 2, 2020

@scottmcdonnell this is probably no longer relevant to you but #1004 and the fix in #1005 may be of interest.

albanqoku added a commit to albanqoku/react-player that referenced this issue Feb 24, 2021
Calling `load()` manually fires onReady before `onPlay`, instead of after
Fixes cookpete/react-player#340
Webmaster1116 added a commit to Webmaster1116/video-player that referenced this issue May 20, 2021
Calling `load()` manually fires onReady before `onPlay`, instead of after
Fixes cookpete/react-player#340
@iamdhrooov
Copy link

I hope my patch could help anyone landing here:

componentDidMount() {
   // on iOS we need to force the load event
   // to get the onReady event firing properly
   // where of course this.refs.video is so rendered: <ReactPlayer ref='video'>
   const videoElement = this.refs.video.getInternalPlayer()
   videoElement.load()
}

This worked well. Adding some more cases to avoid multiple calls.

<ReactPlayer
    ref={(p) => {
       if (p) {
         setPlayerRef(p as any);
         const vElement = p.getInternalPlayer() as HTMLVideoElement;
         vElement && vElement.readyState !== 4 && vElement?.load();
       }
     }}
{...props}
/>

webmiraclepro added a commit to webmiraclepro/video-player that referenced this issue Sep 9, 2022
Calling `load()` manually fires onReady before `onPlay`, instead of after
Fixes cookpete/react-player#340
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

No branches or pull requests

8 participants