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

Calling Player#unload followed by Player#load allows loading to proceed even though Player#mediaSource_ and Player#mediaSourceOpen_ are null #612

Closed
chrisfillmore opened this issue Nov 30, 2016 · 7 comments
Assignees
Labels
status: archived Archived and locked; will not be updated type: bug Something isn't working correctly
Milestone

Comments

@chrisfillmore
Copy link
Contributor

chrisfillmore commented Nov 30, 2016

Hello!

As part of our application's preparation process before playback, we call Player.prototype.unload, then perform some other work before calling Player.prototype.load. We have observed this produces a race condition when you attempt to load content while content is currently being played. Since load also calls unload, it is possible for the player to get into a state where both this.mediaSource_ and this.mediaSourceOpen_ are null, but the loading process is allowed to proceed. This is because when load calls unload, which in turn calls resetStreaming_ it observes that nothing is being played and simply returns Promise.resolve().

I believe we exclusively see this problem with protected content. I was able to produce it in the Shaka demo app with this content:

  • Sintel 4k (multicode, Widevine)
  • Tears of Steel (Widevine)

Simply add a call to unload before load in shakaDemo.load:

player.unload();
// Load the manifest.
player.load(asset.manifestUri).then(function() {
...

Steps to reproduce in the demo app (with the above change):

  • Select content for playback
  • Press Load
  • Observe video is playing
  • Press Load again

Observed behaviour: Assertion failed, and Cannot read property 'readyState' of null. (The MediaSource object being passed into the MediaSourceEngine constructor is null.)

Expected behaviour: Player should load video normally.

The proposed fix is to store the unloading process on the player as a Promise. When unload is called, check if the player is already unloading. If so, do not initiate a new unloading process, but instead continue with the existing one.

I have submitted PR #613

I wrote a test in the player_integration suite which correctly fails, though it is not included in the PR. Truthfully, I am not aware of the prevailing wisdom around testing for race conditions, and at any rate the test would need some more work before being included.

The PR is accepted by the compiler, however it nukes a few player_unit tests. I am unsure about how to proceed with regard to these now-failing tests and am open to feedback/suggestions.

@chrisfillmore chrisfillmore changed the title Calling Player#unload followed by Player#load causes Player#mediaSource_ to be null Calling Player#unload followed by Player#load allows loading to proceed even though Player#mediaSource_ and Player#mediaSourceOpen_ are null Nov 30, 2016
@joeyparrish joeyparrish added the type: bug Something isn't working correctly label Nov 30, 2016
@joeyparrish
Copy link
Member

Testing race conditions is tricky. I recommend a unit test rather than an integration test, because it will be easier to control the environment. Everything other than the object under test (Player) should be mocked in a unit test.

@chrisfillmore
Copy link
Contributor Author

Hi Joey, I need some advice on how to proceed.

I think it's important that the test load real content. If I just write this:

it('handles load interrupting unload', function(done) {
  player.load('', 0, factory1).then(function() {
    player.unload();
    return player.load('', 0, factory1);
  }).then(done).catch(fail);
});

...then the test does not fail when it should. The original test I wrote connected to a development server in our office to load content, and that correctly failed. I've been trying to understand the bootstrapping process that the Shaka integration suite uses to create manifests for the local test content (I've been looking at player_integration.js and test/util/test_scheme.js). This seems to me like the way to go with this test, but I also get the feeling I'm just missing something. Is it possible to mock content?

Thanks for your help.

@joeyparrish
Copy link
Member

If you can only reproduce the bug with real content, we have several simulated streams in test/test/util/test_scheme.js that can be used in an integration test (player_integration.js).

Alternately, if you can show me how to reproduce your error in our demo app, I would be happy to investigate and try to fix it myself.

@chrisfillmore
Copy link
Contributor Author

Hi Joey, sorry to be out of touch. Yes it looks like circumstances only align correctly to produce the bug when the player goes through a real unloading process. You can reproduce it in the demo app with the steps I listed in the issue above (you just have to add the call to player.unload() before calling load).

I'm happy to write the test myself. We rely on Shaka extensively so my familiarity with the code base and test suite is of value to me and my employer. We have an unresolved ticket to return to the main Shaka branch once various PR's are merged, so, eventually I'll write this test, but I have a few other pressing things right now.

@joeyparrish
Copy link
Member

@chrisfillmore, I added player.unload(); in demo/assets.js right before player.load(...) and I am unable to reproduce any error.

@joeyparrish joeyparrish added status: unable to reproduce Issue could not be reproduced by the team and removed status: unable to reproduce Issue could not be reproduced by the team labels Dec 19, 2016
@joeyparrish
Copy link
Member

I take it back. I kept toying with it, and after several tries it finally blew up. For me, it fails if I select "Dig the Uke", click "Load", wait a second or two, then click "Load" again.

@joeyparrish joeyparrish added this to the v2.1.0 milestone Dec 19, 2016
shaka-bot pushed a commit that referenced this issue Dec 20, 2016
This adds a test to reproduce the error described in the Issue #612.
The test is disabled until the bug is fixed.

Change-Id: Idcd9fd2b284b1152b37e43c5058255f3c432c63c
joeyparrish pushed a commit that referenced this issue Jan 7, 2017
This adds a test to reproduce the error described in the Issue #612.
The test is disabled until the bug is fixed.

Change-Id: Idcd9fd2b284b1152b37e43c5058255f3c432c63c
joeyparrish pushed a commit that referenced this issue Jan 7, 2017
Based on PR #613

There was a bug where calling unload() right before calling load()
would cause a race that would sometimes cause a failure.  This is
because the fields are reset before the unload is complete so the
second call to unload() (from inside load) will complete immediately.

So now we store the unload Promise chain while it is in progress so
we can wait on it from load().

Closes #612

Change-Id: I6c0cdd931827d709fc41322edd51fe10e4aa87ae
@joeyparrish
Copy link
Member

joeyparrish commented Jan 9, 2017

The fix for this has just been released in v2.0.3.

@shaka-project shaka-project locked and limited conversation to collaborators Mar 22, 2018
@shaka-bot shaka-bot added the status: archived Archived and locked; will not be updated label Apr 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
status: archived Archived and locked; will not be updated type: bug Something isn't working correctly
Projects
None yet
Development

No branches or pull requests

4 participants