Skip to content

Commit

Permalink
Store and re-use session_ in CastSender.
Browse files Browse the repository at this point in the history
The cast sender API doesn't seem to be able to re-use an existing session
unless you reload the page. This stores the old session, so that it can be
re-used without reloading the page.

In order to enable this, CastProxy.destroy no longer leaves the current session;
I figure if you want to leave the session, you'll call forceDisconnect.
That part I am not fully sure about; perhaps it would be better to have a
separate optional argument about whether or not to leave, or make it a part of
the forceDisconnect argument.

Issue #768

Change-Id: Ie648372cea4b106ff85df3d0dcc563fca5d10d8c
  • Loading branch information
theodab authored and joeyparrish committed Oct 16, 2017
1 parent fb741b1 commit d962d89
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 27 deletions.
8 changes: 8 additions & 0 deletions externs/chromecast.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ chrome.cast = {};
chrome.cast.isAvailable;


/** @const */
chrome.cast.SessionStatus = {};


/** @type {string} */
chrome.cast.SessionStatus.STOPPED;


/**
* @param {chrome.cast.ApiConfig} apiConfig
* @param {Function} successCallback
Expand Down
59 changes: 37 additions & 22 deletions lib/cast/cast_sender.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@ shaka.cast.CastSender =
/** @private {Object} */
this.appData_ = null;

/** @private {chrome.cast.Session} */
this.session_ = null;

/** @private {?function()} */
this.onConnectionStatusChangedBound_ =
this.onConnectionStatusChanged_.bind(this);
Expand Down Expand Up @@ -111,13 +108,17 @@ shaka.cast.CastSender =
shaka.cast.CastSender.hasReceivers_ = false;


/** @private {chrome.cast.Session} */
shaka.cast.CastSender.session_ = null;


/** @override */
shaka.cast.CastSender.prototype.destroy = function() {
this.rejectAllPromises_();
if (this.session_) {
if (shaka.cast.CastSender.session_) {
this.removeListeners_();
this.session_.leave(function() {}, function() {});
this.session_ = null;
// Don't leave the session, so that this session can be re-used later if
// necessary.
}

this.onStatusChanged_ = null;
Expand Down Expand Up @@ -214,6 +215,16 @@ shaka.cast.CastSender.prototype.init = function() {
// listeners.
setTimeout(this.onStatusChanged_.bind(this), 20);
}

var oldSession = shaka.cast.CastSender.session_;
if (oldSession && oldSession.status != chrome.cast.SessionStatus.STOPPED) {
// The old session still exists, so re-use it.
shaka.log.debug('CastSender: re-using existing connection');
this.onExistingSessionJoined_(oldSession);
} else {
// The session has been canceled in the meantime, so ignore it.
shaka.cast.CastSender.session_ = null;
}
};


Expand Down Expand Up @@ -294,10 +305,10 @@ shaka.cast.CastSender.prototype.forceDisconnect = function() {
}

this.rejectAllPromises_();
if (this.session_) {
if (shaka.cast.CastSender.session_) {
this.removeListeners_();
this.session_.stop(function() {}, function() {});
this.session_ = null;
shaka.cast.CastSender.session_.stop(function() {}, function() {});
shaka.cast.CastSender.session_ = null;
}
};

Expand Down Expand Up @@ -505,10 +516,9 @@ shaka.cast.CastSender.prototype.onReceiverStatusChanged_ =
* @private
*/
shaka.cast.CastSender.prototype.onSessionCreated_ = function(session) {
this.session_ = session;
this.session_.addUpdateListener(this.onConnectionStatusChangedBound_);
this.session_.addMessageListener(
shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE,
shaka.cast.CastSender.session_ = session;
session.addUpdateListener(this.onConnectionStatusChangedBound_);
session.addMessageListener(shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE,
this.onMessageReceivedBound_);
this.onConnectionStatusChanged_();
};
Expand All @@ -518,9 +528,9 @@ shaka.cast.CastSender.prototype.onSessionCreated_ = function(session) {
* @private
*/
shaka.cast.CastSender.prototype.removeListeners_ = function() {
this.session_.removeUpdateListener(this.onConnectionStatusChangedBound_);
this.session_.removeMessageListener(
shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE,
var session = shaka.cast.CastSender.session_;
session.removeUpdateListener(this.onConnectionStatusChangedBound_);
session.removeMessageListener(shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE,
this.onMessageReceivedBound_);
};

Expand All @@ -529,7 +539,9 @@ shaka.cast.CastSender.prototype.removeListeners_ = function() {
* @private
*/
shaka.cast.CastSender.prototype.onConnectionStatusChanged_ = function() {
var connected = this.session_ ? this.session_.status == 'connected' : false;
var connected = shaka.cast.CastSender.session_ ?
shaka.cast.CastSender.session_.status == 'connected' :
false;
shaka.log.debug('CastSender: connection status', connected);
if (this.isCasting_ && !connected) {
// Tell CastProxy to transfer state back to local player.
Expand All @@ -544,7 +556,9 @@ shaka.cast.CastSender.prototype.onConnectionStatusChanged_ = function() {
}

this.isCasting_ = connected;
this.receiverName_ = connected ? this.session_.receiver.friendlyName : '';
this.receiverName_ = connected ?
shaka.cast.CastSender.session_.receiver.friendlyName :
'';
this.onStatusChanged_();
};

Expand Down Expand Up @@ -635,8 +649,9 @@ shaka.cast.CastSender.prototype.onMessageReceived_ =
shaka.cast.CastSender.prototype.sendMessage_ = function(message) {
var serialized = shaka.cast.CastUtils.serialize(message);
// TODO: have never seen this fail. When would it and how should we react?
this.session_.sendMessage(shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE,
serialized,
function() {}, // success callback
shaka.log.error); // error callback
var session = shaka.cast.CastSender.session_;
session.sendMessage(shaka.cast.CastUtils.SHAKA_MESSAGE_NAMESPACE,
serialized,
function() {}, // success callback
shaka.log.error); // error callback
};
72 changes: 67 additions & 5 deletions test/cast/cast_sender_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('CastSender', function() {
fakeAppId, Util.spyFunc(onStatusChanged),
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
resetHasReceivers();
resetClassVariables();
});

afterEach(function(done) {
Expand Down Expand Up @@ -248,6 +248,67 @@ describe('CastSender', function() {
});
});

it('re-uses old sessions', function(done) {
sender.init();
fakeReceiverAvailability(true);
var p = sender.cast(fakeInitState);
fakeSessionConnection();
var oldMockSession = mockSession;
p.then(function() {
return sender.destroy();
}).then(function() {
// Reset tracking variables.
mockCastApi.ApiConfig.calls.reset();
onStatusChanged.calls.reset();
oldMockSession.messages = [];

// Make a new session, to ensure that the sender is correctly using
// the previous mock session.
mockSession = createMockCastSession();

sender = new CastSender(
fakeAppId, Util.spyFunc(onStatusChanged),
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
sender.init();

// The sender should automatically rejoin the session, without needing
// to be told to cast.
expect(onStatusChanged).toHaveBeenCalled();
expect(sender.isCasting()).toBe(true);

// The message should be on the old session, instead of the new one.
expect(mockSession.messages.length).toBe(0);
expect(oldMockSession.messages).toContain(jasmine.objectContaining({
type: 'init',
initState: fakeInitState
}));
}).catch(fail).then(done);
});

it('doesn\'t re-use stopped sessions', function(done) {
sender.init();
fakeReceiverAvailability(true);
var p = sender.cast(fakeInitState);
fakeSessionConnection();
p.then(function() {
return sender.destroy();
}).then(function() {
mockCastApi.ApiConfig.calls.reset();

// The session is stopped in the meantime.
mockSession.status = chrome.cast.SessionStatus.STOPPED;

sender = new CastSender(
fakeAppId, Util.spyFunc(onStatusChanged),
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
sender.init();

expect(sender.isCasting()).toBe(false);
}).catch(fail).then(done);
});

it('joins existing sessions automatically', function(done) {
sender.init();
fakeReceiverAvailability(true);
Expand Down Expand Up @@ -706,12 +767,11 @@ describe('CastSender', function() {
});

describe('destroy', function() {
it('leaves the session and cancels all async operations', function(done) {
it('cancels all async operations', function(done) {
sender.init();
fakeReceiverAvailability(true);
sender.cast(fakeInitState).then(function() {
expect(sender.isCasting()).toBe(true);
expect(mockSession.leave).not.toHaveBeenCalled();
expect(mockSession.stop).not.toHaveBeenCalled();
expect(mockSession.removeUpdateListener).not.toHaveBeenCalled();
expect(mockSession.removeMessageListener).not.toHaveBeenCalled();
Expand All @@ -724,7 +784,7 @@ describe('CastSender', function() {
return Util.delay(0.1).then(function() {
expect(p.status).toBe('pending');
sender.destroy().catch(fail);
expect(mockSession.leave).toHaveBeenCalled();
expect(mockSession.leave).not.toHaveBeenCalled();
expect(mockSession.stop).not.toHaveBeenCalled();
expect(mockSession.removeUpdateListener).toHaveBeenCalled();
expect(mockSession.removeMessageListener).toHaveBeenCalled();
Expand All @@ -749,6 +809,7 @@ describe('CastSender', function() {
return {
isAvailable: true,
SessionRequest: jasmine.createSpy('chrome.cast.SessionRequest'),
SessionStatus: {STOPPED: 'stopped'},
ApiConfig: jasmine.createSpy('chrome.cast.ApiConfig'),
initialize: jasmine.createSpy('chrome.cast.initialize'),
requestSession: jasmine.createSpy('chrome.cast.requestSession')
Expand Down Expand Up @@ -848,9 +909,10 @@ describe('CastSender', function() {
/**
* @suppress {visibility}
*/
function resetHasReceivers() {
function resetClassVariables() {
// @suppress visibility has function scope, so this is a mini-function that
// exists solely to suppress visibility for this call.
CastSender.hasReceivers_ = false;
CastSender.session_ = null;
}
});

0 comments on commit d962d89

Please sign in to comment.