Skip to content

Commit

Permalink
Cleanup cast idle state
Browse files Browse the repository at this point in the history
This simplifies the logic for idle state, fixes some buggy idle state
transitions, and moves the idle logic into CastReceiver (with a little
support from Player).

Issue #261

Change-Id: Ic2729a4235c746ad46353bdf5dc7b605ab31f3ef
  • Loading branch information
joeyparrish committed Jul 7, 2016
1 parent b2fd140 commit 3def381
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 38 deletions.
33 changes: 6 additions & 27 deletions demo/receiver_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,6 @@ ShakaReceiver.prototype.init = function() {
this.video_.addEventListener(
'emptied', this.onPlayStateChange_.bind(this));

this.video_.addEventListener(
'canplay', this.checkIdle_.bind(this));
this.video_.addEventListener(
'emptied', this.checkIdle_.bind(this));
this.video_.addEventListener(
'ended', this.checkIdle_.bind(this));

this.receiver_ = new shaka.cast.CastReceiver(
this.video_, this.player_, this.appDataCallback_.bind(this));
this.receiver_.addEventListener(
Expand Down Expand Up @@ -121,29 +114,15 @@ ShakaReceiver.prototype.appDataCallback_ = function(appData) {

/** @private */
ShakaReceiver.prototype.checkIdle_ = function() {
var connected = this.receiver_.isConnected();
var loaded = this.video_.readyState > 0;
var ended = this.video_.ended;

var idle = !loaded || (!connected && ended);

console.debug('status changed',
'connected=', connected,
'loaded=', loaded,
'ended=', ended,
'idle=', idle);

// If something is loaded, but we've just gone idle, unload the content, show
// the idle card, and set a timer to close the app.
if (idle && loaded) {
this.player_.unload();
'idle=', this.receiver_.isIdle());

// If the app is idle, show the idle card and set a timer to close the app.
// Otherwise, hide the idle card and cancel the timer.
if (this.receiver_.isIdle()) {
this.idle_.style.display = 'block';
this.startIdleTimer_();
}

// If we are no longer idle, hide the idle card, and make sure we cancel any
// timers that would close the app.
if (!idle) {
} else {
this.idle_.style.display = 'none';
this.cancelIdleTimer_();
}
Expand Down
43 changes: 41 additions & 2 deletions lib/cast/cast_receiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ shaka.cast.CastReceiver = function(video, player, opt_appDataCallback) {
/** @private {boolean} */
this.isConnected_ = false;

/** @private {boolean} */
this.isIdle_ = true;

/** @private {cast.receiver.CastMessageBus} */
this.bus_ = null;

Expand All @@ -81,6 +84,16 @@ shaka.cast.CastReceiver.prototype.isConnected = function() {
};


/**
* @return {boolean} True if the receiver is not currently doing loading or
* playing anything.
* @export
*/
shaka.cast.CastReceiver.prototype.isIdle = function() {
return this.isIdle_;
};


/**
* Destroys the underlying Player, then terminates the cast receiver app.
*
Expand All @@ -99,6 +112,7 @@ shaka.cast.CastReceiver.prototype.destroy = function() {
this.targets_ = null;
this.appDataCallback_ = null;
this.isConnected_ = false;
this.isIdle_ = true;
this.bus_ = null;
this.pollTimerId_ = null;

Expand Down Expand Up @@ -146,6 +160,26 @@ shaka.cast.CastReceiver.prototype.init_ = function() {
// higher res anyway, given that the device only outputs 1080p to begin with.
this.player_.setMaxHardwareResolution(1920, 1080);

// Maintain idle state.
this.player_.addEventListener('loading', function() {
this.isIdle_ = false;
this.onCastStatusChanged_();
}.bind(this));
this.player_.addEventListener('unloading', function() {
this.isIdle_ = true;
this.onCastStatusChanged_();
}.bind(this));
this.video_.addEventListener('ended', function() {
// Go idle 5 seconds after 'ended', assuming we haven't started again or
// been destroyed.
window.setTimeout(function() {
if (this.video_ && this.video_.ended) {
this.isIdle_ = true;
this.onCastStatusChanged_();
}
}.bind(this), 5000);
}.bind(this));

// Do not start polling until after the sender's 'init' message is handled.
};

Expand All @@ -163,8 +197,13 @@ shaka.cast.CastReceiver.prototype.onSendersChanged_ = function() {
* @private
*/
shaka.cast.CastReceiver.prototype.onCastStatusChanged_ = function() {
var event = new shaka.util.FakeEvent('caststatuschanged');
this.dispatchEvent(event);
// Do this asynchronously so that synchronous changes to idle state (such as
// Player calling unload() as part of load()) are coalesced before the event
// goes out.
Promise.resolve().then(function() {
var event = new shaka.util.FakeEvent('caststatuschanged');
this.dispatchEvent(event);
}.bind(this));
};


Expand Down
23 changes: 23 additions & 0 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,26 @@ shaka.Player.version = GIT_VERSION;
*/


/**
* @event shaka.Player.LoadingEvent
* @description Fired when the player begins loading.
* Used by the Cast receiver to determine idle state.
* @property {string} type
* 'loading'
* @exportDoc
*/


/**
* @event shaka.Player.UnloadingEvent
* @description Fired when the player unloads or fails to load.
* Used by the Cast receiver to determine idle state.
* @property {string} type
* 'unloading'
* @exportDoc
*/


/**
* @event shaka.Player.TextTrackVisibilityEvent
* @description Fired when text track visibility changes.
Expand Down Expand Up @@ -345,6 +365,7 @@ shaka.Player.prototype.load = function(manifestUri, opt_startTime,
var unloadPromise = this.unload();
var loadChain = new shaka.util.CancelableChain();
this.loadChain_ = loadChain;
this.dispatchEvent(new shaka.util.FakeEvent('loading'));

return loadChain.then(function() {
return unloadPromise;
Expand Down Expand Up @@ -415,6 +436,7 @@ shaka.Player.prototype.load = function(manifestUri, opt_startTime,
// If we haven't started another load, clear the loadChain_ member.
if (this.loadChain_ == loadChain) {
this.loadChain_ = null;
this.dispatchEvent(new shaka.util.FakeEvent('unloading'));
}
return Promise.reject(error);
}.bind(this));
Expand Down Expand Up @@ -736,6 +758,7 @@ shaka.Player.prototype.isBuffering = function() {
*/
shaka.Player.prototype.unload = function() {
if (this.destroyed_) return Promise.resolve();
this.dispatchEvent(new shaka.util.FakeEvent('unloading'));

if (this.loadChain_) {
// A load is in progress. Cancel it, then reset the streaming system.
Expand Down
50 changes: 41 additions & 9 deletions test/cast/cast_receiver_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,53 @@ describe('CastReceiver', function() {
});

describe('"caststatuschanged" event', function() {
it('is triggered when senders connect or disconnect', function() {
it('is triggered when senders connect or disconnect', function(done) {
checkChromeOrChromecast();
fakeConnectedSenders(0);
var listener = jasmine.createSpy('listener');
receiver.addEventListener('caststatuschanged', listener);

shaka.test.Util.delay(0.2).then(function() {
expect(listener).not.toHaveBeenCalled();
fakeConnectedSenders(1);
return shaka.test.Util.delay(0.2);
}).then(function() {
expect(listener).toHaveBeenCalled();
listener.calls.reset();
mockReceiverManager.onSenderDisconnected();
return shaka.test.Util.delay(0.2);
}).then(function() {
expect(listener).toHaveBeenCalled();
}).catch(fail).then(done);
});

it('is triggered when idle state changes', function(done) {
checkChromeOrChromecast();
var listener = jasmine.createSpy('listener');
receiver.addEventListener('caststatuschanged', listener);
expect(listener).not.toHaveBeenCalled();

mockReceiverManager.onSenderConnected();
expect(listener).toHaveBeenCalled();
var fakeLoadingEvent = {type: 'loading'};
var fakeUnloadingEvent = {type: 'unloading'};
var fakeEndedEvent = {type: 'ended'};

listener.calls.reset();
mockReceiverManager.onSenderDisconnected();
expect(listener).toHaveBeenCalled();
});
shaka.test.Util.delay(0.2).then(function() {
expect(listener).not.toHaveBeenCalled();
mockPlayer.listeners['loading'](fakeLoadingEvent);
return shaka.test.Util.delay(0.2);
}).then(function() {
expect(listener).toHaveBeenCalled();
listener.calls.reset();
mockPlayer.listeners['unloading'](fakeUnloadingEvent);
return shaka.test.Util.delay(0.2);
}).then(function() {
expect(listener).toHaveBeenCalled();
listener.calls.reset();
mockVideo.ended = true;
mockVideo.listeners['ended'](fakeEndedEvent);
return shaka.test.Util.delay(5.2); // There is a long delay for 'ended'
}).then(function() {
expect(listener).toHaveBeenCalled();
}).catch(fail).then(done);
}, /* timeout ms */ 8000);
});

describe('local events', function() {
Expand Down

0 comments on commit 3def381

Please sign in to comment.