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: content can play too early after contentupdate #318

Closed

Conversation

alex-barstow
Copy link
Contributor

@alex-barstow alex-barstow commented Jan 19, 2018

Description

This addresses a couple of issues surrounding contentupdates:

  1. Content can start playing immediately after a source change because we don't proactively pause the player if it is playing following a contentupdate
  2. There is no contentupdate event handler in the 'preroll?' state, so if one occurs we remain in ‘preroll?’ through the source change. And because the ad timeout isn’t cleared, the eventual adtimeout event causes a transition directly into ‘content-playback’, bypassing 'content-set' / 'ads-ready?' / 'ads-ready'

EDIT: We are going to wait to address no.2 in the upcoming refactor

Specific Additions

  1. If we are in 'content-playback' and a contentupdate occurs, pause the player if it is not already paused.
  2. Add a contentupdate() handler in 'preroll?' that changes state to 'content-set'

As mentioned above: we'll hold off on no.2 until the refactor

…e handler in preroll?, reset _pausedOnContentupdate flag on playing
@@ -666,6 +670,10 @@ const contribAdsPlugin = function(options) {
if (player.paused()) {
this.state = 'content-set';
} else {
// If we get here we missed the 'play' event, so pause the player so
// that content doesn't start playing immediately after the source change
player.pause();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may want to pause the player if it's not paused yet and then go to the same state content-set.

If I understand correctly, the reason this else block will go to ads-ready? is that there is an assumption that the play event has been missed. So once the player is paused, we should be in the same state that causes the if clause to change state to content-set

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense to me

Copy link
Contributor

Choose a reason for hiding this comment

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

From an offline conversation we came to the conclusion that the state transition to ads-ready? is needed still.

Copy link
Contributor

@ldayananda ldayananda left a comment

Choose a reason for hiding this comment

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

I think my main uncertainty is around postroll behavior. If the contentupdate event happens after a postroll, do we have the correct behavior?

Also, this should add some unit tests! 😄

Once that's out of the way I'll be happy to re-review

test/test.ads.js Outdated
@@ -729,6 +729,45 @@ QUnit.test('adsready in content-playback triggers readyforpreroll', function(ass
assert.strictEqual(spy.getCall(0).args[0].type, 'readyforpreroll', 'readyforpreroll should have been triggered.');
});

QUnit.test('contentupdate in preroll? transitions to content-set', function(assert) {
assert.expect(2);

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe all these tests should trigger loadstart before play as well, to make sure the player is setup correctly

test/test.ads.js Outdated
assert.expect(2);

this.player.trigger('play');
this.player.trigger('adtimeout');
Copy link
Contributor

Choose a reason for hiding this comment

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

I would just do this.players.ads.skipLinearAdMode() here, or explicitly do startLinearAdMode() and endLinearAdMode() right after.

test/test.ads.js Outdated
assert.expect(4);

this.player.trigger('play');
this.player.trigger('adtimeout');
Copy link
Contributor

Choose a reason for hiding this comment

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

same feedback for this line as the above test

src/plugin.js Outdated
@@ -435,6 +436,9 @@ const contribAdsPlugin = function(options) {
},
nopreroll() {
this.state = 'content-playback';
},
contentupdate() {
this.state = 'content-set';
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a loadstart/contentupdate for the initial source. Is there any guarantee that this isn't that event? Specifically, if the play / adsready came in before loadstart.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I'm not sure there is any such guarantee. We could add a safeguard, something like:

contentupdate() {
  // only change states if this is not the first loadstart/contentupdate
  if (player.ads._hasThereBeenALoadStartDuringPlayerLife) {
    this.state = 'content-set';
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

You'll need to test it to make sure _hasThereBeenALoadStartDuringPlayerLife is set, it's also set on loadstart so the order of event handlers matters here.

Copy link
Contributor Author

@alex-barstow alex-barstow Jan 30, 2018

Choose a reason for hiding this comment

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

In hindsight, this check won't have any value for subsequent videos in a playlist since _hasThereBeenALoadStartDuringPlayerLife will be true for the life of the player after the first loadstart, as the name suggests :P. Will need to find another way

test/test.ads.js Outdated
this.player.trigger('adsready');
assert.strictEqual(this.player.ads.state, 'preroll?');
this.player.trigger('contentupdate');
assert.strictEqual(this.player.ads.state, 'content-set');
Copy link
Contributor

Choose a reason for hiding this comment

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

You should avoid tests that assert the state. Firstly, they will break very shortly. Secondly, it is preferable to test things that indicate that the player is functioning correctly. For example, making sure it is paused if it should be paused.

Copy link
Contributor Author

@alex-barstow alex-barstow Jan 26, 2018

Choose a reason for hiding this comment

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

If that's the case, I'll remove these first two tests in their entirety since they are just testing that certain state transitions occur under the proper conditions. Is that what you would recommend in this case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense, it does seem like the third test covers the functionality you've implemented here.

test/test.ads.js Outdated
@@ -729,6 +729,46 @@ QUnit.test('adsready in content-playback triggers readyforpreroll', function(ass
assert.strictEqual(spy.getCall(0).args[0].type, 'readyforpreroll', 'readyforpreroll should have been triggered.');
});

QUnit.test('contentupdate in preroll? transitions to content-set', function(assert) {
assert.expect(2);
Copy link
Contributor

Choose a reason for hiding this comment

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

I recommend avoiding using expect unless your code has assertions in a loop. For simple procedural tests, the expect doesn't add value and requires maintenance if the test is updated.

test/test.ads.js Outdated
sinon.spy(this.player, 'pause');
this.player.trigger('contentupdate');
assert.strictEqual(this.player.ads.state, 'ads-ready?');
assert.ok(this.player.pause.calledOnce, 'player was paused');
Copy link
Contributor

Choose a reason for hiding this comment

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

The assertions on pause.calledOnce and _pausedOnContentUpdate are good, you can remove the assertions on the state. The test remains just as valuable and would survive the refactor.

@@ -729,6 +729,18 @@ QUnit.test('adsready in content-playback triggers readyforpreroll', function(ass
assert.strictEqual(spy.getCall(0).args[0].type, 'readyforpreroll', 'readyforpreroll should have been triggered.');
});

QUnit.test('contentupdate in content-playback transitions to ads-ready? and pauses player if not already paused', function(assert) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: remove state reference from test name

sinon.spy(this.player, 'pause');
this.player.trigger('contentupdate');
assert.ok(this.player.pause.calledOnce, 'player was paused');
assert.ok(this.player.ads._pausedOnContentupdate, '_pausedOnContentupdate is true');
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also test that the play happens?

@@ -666,6 +667,10 @@ const contribAdsPlugin = function(options) {
if (player.paused()) {
this.state = 'content-set';
} else {
// If we get here we missed the 'play' event, so pause the player so
// that content doesn't start playing immediately after the source change
player.pause();
Copy link
Contributor

Choose a reason for hiding this comment

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

What about if contentupdate for the initial source happens during content-playback? This can happen with autoplay.

@alex-barstow
Copy link
Contributor Author

This fix has been moved into the refactor. Closing.

@alex-barstow alex-barstow deleted the fix-pause-on-contentupdate branch February 5, 2018 18:55
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.

3 participants