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

Improve codec unsupported detection #1711

Merged
merged 8 commits into from
Oct 13, 2019

Conversation

hicom150
Copy link
Contributor

What is the purpose of this pull request? (put an "X" next to item)

[ ] Documentation update
[x] Bug fix
[ ] New feature
[ ] Other, please explain:

What changes did you make? (Give an overview)
It seems that from time to time we get some "codec unsupported" false positive. This PR changes unsupported codec detection to use HTMLMediaElement.play() Promise based on what it is explained in this article.

Is there anything you'd like reviewers to focus on?
Check that we don't get false positives caused by timing issues.

@feross
Copy link
Member

feross commented Oct 1, 2019

Wow, can it really be this simple? If this actually works better, let's merge it. Haven't had time to test it.

@hicom150 hicom150 requested a review from Borewit October 6, 2019 17:51
Copy link
Member

@Borewit Borewit left a comment

Choose a reason for hiding this comment

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

Playing back a movie with just an AC3-audio (AC3 is not supported by the Chromium engine) track:

master branch:
image

PR #1711 :
image

Do we still get any false positives as indicated in issue #1668?:

master branch:
image

PR #1711:
image

Could no longer reproduce the false positive. 👍

@Borewit
Copy link
Member

Borewit commented Oct 6, 2019

Great work @hicom150.

Copy link
Contributor Author

@hicom150 hicom150 left a comment

Choose a reason for hiding this comment

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

All the changes done! 👍

})
.then(
() => dispatch('mediaSuccess'),
() => dispatch('mediaError', 'Codec unsupported')
Copy link
Member

Choose a reason for hiding this comment

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

Maybe?

        .then(
          () => dispatch('mediaSuccess'),
          err => dispatch('mediaError', err.name === 'NotSupportedError' ? 'Codec unsupported' : `${err.name}: ${err.message}`)
        )

I put it on a branch: https://github.com/webtorrent/webtorrent-desktop/tree/issue-1711-improve-error-handling

For some reason I would send PR to your clone, I think select your fork as the base. Maybe a bug in GitHub, to many forks of this repo.

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 think that we should dispatch mediaError when error type is NotSupportedError (original code) or when any error case (like the latest code change).

Given that the possible exceptions are NotSupportedError or NotAllowedError I wouldn't show this last one to the final user 😅

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't show this last one to the final user

why not?

Copy link
Member

@Borewit Borewit Oct 7, 2019

Choose a reason for hiding this comment

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

It also says other exceptions may be reported,. I suppose these are not very likely to be thrown, but it may be good user gets an error if something odd happens.

Copy link
Member

Choose a reason for hiding this comment

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

With the current code, in the unlikely event of an error named NotAllowedError, or any other exception, it will prompt the user with 'Codec unsupported' right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With the current code, in the unlikely event of an error named NotAllowedError, or any other exception, it will prompt the user with 'Codec unsupported' right?

Yes that is right 😅 Although it is not 100% correct for the final user, the action should be the same: open an external player to be able to play the file, otherwise it would see the message The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission. which I found a bit confusing for the user as it doesn't give any "call to action" to solve the problem.

On the other hand, it is true that we should log the proper error code, so we can debug future problems easily 😉

What do you think @Borewit ?

@RecoX
Copy link
Contributor

RecoX commented Oct 8, 2019

I believe the current implementation using the audio/video api for manage this flow is ok, I tried in the past use this approach but I ended up leaving as is because it seems wiser anyways this pr looks simple.

I would like to add some details I found.

I put a console.log, to see that element as a lot of times is throwing me a false positive

  function onCanPlay (e) {
    const elem = e.target
    console.log(666, elem.webkitAudioDecodedByteCount)
    
    if (elem.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) return
    if (state.playing.type === 'video' &&
      elem.webkitVideoDecodedByteCount === 0) {
      dispatch('mediaError', 'Video codec unsupported')
    } else if (elem.webkitAudioDecodedByteCount === 0) {
      dispatch('mediaError', 'Audio codec unsupported')
    } else {
      dispatch('mediaSuccess')
      elem.play()
    }
  }

Screen Shot 2019-10-08 at 2 37 04 PM

Both are mp3's so im not sure if this webkitAudioDecodedByteCount is really trustable

@Borewit
Copy link
Member

Borewit commented Oct 8, 2019

@RecoX

I believe the current implementation using the audio/video api for manage this flow is ok,

With the current implementation we get false positives as described in #1668

I tried in the past use this approach but I ended up leaving

Why? If you had an issue with PR approach, is that still valid? Can you test the PR @RecoX ? Or do you advice to merge, it is not clear to me.

anyways this pr looks simple

Looks very clean and simple indeed.

@Borewit
Copy link
Member

Borewit commented Oct 8, 2019

As there is some discussion, I suggest to keep the PR open for 2 day. Please use the review process fellow contributors.

Sorry for the delay @hicom150.

@hicom150
Copy link
Contributor Author

hicom150 commented Oct 8, 2019

Sorry for the delay @hicom150.

Don't worry @Borewit 😉

I find it super helpful to get feedback from other developers, I think that the code gets better when we discus and combine different point of views.

BTW, thank you for your review and great feedback @Borewit 💪

@RecoX
Copy link
Contributor

RecoX commented Oct 8, 2019

@RecoX

I believe the current implementation using the audio/video api for manage this flow is ok,

With the current implementation we get false positives as described in #1668

I tried in the past use this approach but I ended up leaving

Why? If you had an issue with PR approach, is that still valid? Can you test the PR @RecoX ? Or do you advice to merge, it is not clear to me.

anyways this pr looks simple

Looks very clean and simple indeed.

I tested it and it works ok, but I found a problem.

When I tried to reproduce a wma it throws the modal with the unsupported codec and that is ok but as you see in the console is doing that in an infinite loop so the program freezes.

https://github.com/refreex/refreex-webtorrent-desktop/blob/master/src/renderer/pages/audio-player.js

here the link of the file where i implement it just in case you want to take a look.

Screen Shot 2019-10-09 at 9 33 43 AM

btw im using this patch in the meantime.

  function onCanPlay (e) {
    const elem = e.target

    if (elem.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) return
    if (state.playing.type === 'video' &&
      elem.webkitVideoDecodedByteCount === 0) {
      dispatch('mediaError', 'Video codec unsupported')
    } else if (elem.webkitAudioDecodedByteCount === 0) {

      // Nasty patch to avoid entering here for valid files, we need to address it better.
      const audioExtensions = ['.aac', '.amr', '.alac', '.flac', '.mp3', '.oga', '.ogg', '.opus', '.wav', '.webm'];
      if (audioExtensions.includes(elem.src)) {
        dispatch('mediaError', 'Audio codec unsupported')
      }
      
    } else {
      dispatch('mediaSuccess')
      elem.play()
    }
  }

@hicom150
Copy link
Contributor Author

hicom150 commented Oct 8, 2019

@RecoX I can't reproduce your error case 😓 but I noticed that you didn't delete onCanPlay and onError as it is in this PR code 😅

@RecoX
Copy link
Contributor

RecoX commented Oct 9, 2019

@RecoX I can't reproduce your error case 😓 but I noticed that you didn't delete onCanPlay and onError as it is in this PR code 😅

You don't see them because i didn't push the changes, probably is because the player in my fork is in the main window instead of the player window. Anyways is strange that occurs to me with the new code and with the older no.

@mathiasvr
Copy link
Contributor

Video with AC3 audio plays silently (like Chrome), and does not give the user the option to open in an external player.
I also had a false positive (saw the Codec unsupported error on a H264 file), which worked fine all other times I tried.

@hicom150
Copy link
Contributor Author

hicom150 commented Oct 9, 2019

Video with AC3 audio plays silently (like Chrome), and does not give the user the option to open in an external player.
I also had a false positive (saw the Codec unsupported error on a H264 file), which worked fine all other times I tried.

Ups! I didn't expect that result 😓

In order to help the debugging process can we share the test files where we get the unexpected behaviors? @mathiasvr @RecoX

Here I share the some files a what should be the expected result:

@mathiasvr
Copy link
Contributor

mathiasvr commented Oct 10, 2019

@hicom150 I just went ahead and generated a bunch of different samples with various video/audio codecs, because why not? 😃

https://drive.google.com/open?id=1ACw6hOooQNfc-oniyL0Gp-z4OSq-5Mc7

Before this PR, a supported video file (such as h264) with AC3 audio codec displays 'Audio codec not supported', and after this PR it plays the video with audio muted. It would be nice if we could still detect unsupported audio.

@hicom150
Copy link
Contributor Author

hicom150 commented Oct 10, 2019

After some further testing with the awesome @mathiasvr video samples 😉 it seems that HTMLMediaElement.play() Promise is not as fine grained as the previous implementation 😭

So unless someone comes with a better solution, I think that the only proper option is to try to optimize the previous onCanPlay function 😅

@RecoX
Copy link
Contributor

RecoX commented Oct 10, 2019

After some further testing with the awesome @mathiasvr video samples 😉 it seems that HTMLMediaElement.play() Promise is not as fine grained as the previous implementation 😭

So unless someone comes with a better solution, I think that the only proper option is to try to optimize the previous onCanPlay function 😅

Agreed :) +1

@mathiasvr
Copy link
Contributor

mathiasvr commented Oct 10, 2019

@hicom150 Did you see other problems than muted audio? Otherwise I think the play() Promise could still be a good solution, if we can detect the audio problem (maybe using onCanPlay) as well.

@hicom150
Copy link
Contributor Author

I changed the approach and instead of using HTMLMediaElement.play() Promise I try with experimental videoTracks and audioTracks which describe the number of available (correctly decoded) tracks.

Having this, I see two options:

  • If we use the same approach we use before, we can say that for a video element we expect at least one video track and one audio track and for an audio element we expect at least one audio track. (this has the caveat that videos with no audio track are treated like error WebTorrent Desktop can't play media files that don't contain audio #759)

  • The other option could be to allow to play videos without any audio track either because it has not any audio stream or because the audio stream codec is not supported. (this has the caveat that we directly play videos without supported audio without giving the option to open in VLC)

To sum up, the advantage of this method is that we can check available tracks early in onLoadedMetadata event and that we don't rely on webkitVideoDecodedByteCount and webkitAudioDecodedByteCount that sometimes have timing issues.

@mathiasvr
Copy link
Contributor

Nice work @hicom150!
I haven’t tested it yet, but definitely sounds more stable than the current approach.
I think not supporting video with no audio is fine for now, we could consider adding a “play without audio” button on the “Audio codec not supported” error dialog? But maybe it’s a little too hacky 😉

@RecoX
Copy link
Contributor

RecoX commented Oct 13, 2019

Nice work @hicom150!
I haven’t tested it yet, but definitely sounds more stable than the current approach.
I think not supporting video with no audio is fine for now, we could consider adding a “play without audio” button on the “Audio codec not supported” error dialog? But maybe it’s a little too hacky 😉

I feel its too hacky too. If we can't play the video as we should we simply can offer to open it with a an external player and that's it. Keep it simple.

@hicom150
Copy link
Contributor Author

After some back and forth I think that finally I'm going to merge this PR a go forward 😉
Thank you for your awesome help in this issue guys! 💪

@hicom150 hicom150 merged commit c33acd0 into webtorrent:master Oct 13, 2019
@hicom150 hicom150 deleted the fix_codec_unsupported_detection branch October 13, 2019 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants