Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
216 lines
5.4 KiB
JavaScript
Executable file
216 lines
5.4 KiB
JavaScript
Executable file
/* global evt, castingMessage, mDBG */
|
|
(function(exports) {
|
|
'use strict';
|
|
|
|
/**
|
|
* This class handles the connection to controller via the presentation API
|
|
* The events of controller's message are:
|
|
* - loadRequest: Triggered when controller asks to load video.
|
|
* Will be passed in one object with url property storing the
|
|
* video url.
|
|
* - playRequest: Triggered when controller asks to play video.
|
|
* - pauseRequest: Triggered when controller asks to pause video.
|
|
* - seekRequest: Triggered when controller asks to seek video.
|
|
* Will be passed in one object with time property storing the
|
|
* seeking time.
|
|
*
|
|
* @param {object} presentation The navigator's presentation object
|
|
* @constructor
|
|
*/
|
|
function Connector(presentation) {
|
|
this._presentation = presentation;
|
|
this._msgSeq = 0; // This sequence of message sent to controller
|
|
this._lastSeq = -1; // The sequence of the last message received
|
|
this._isInit = false;
|
|
this._isInitConnection = false;
|
|
this._controllingDeviceInfo = null;
|
|
}
|
|
|
|
var proto = evt(Connector.prototype);
|
|
|
|
proto.init = function () {
|
|
|
|
if (this._isInit) {
|
|
return;
|
|
}
|
|
|
|
mDBG.log('Connector#init');
|
|
if (!this._presentation) {
|
|
throw new Error('Init connection without the presentation object.');
|
|
}
|
|
|
|
this._isInit = true;
|
|
|
|
this._presentation.receiver.getConnection().then(
|
|
this._initConnection.bind(this)
|
|
);
|
|
|
|
this._presentation.receiver.onconnectionavailable = (e) => {
|
|
this._presentation.receiver.getConnection().then(
|
|
this._initConnection.bind(this)
|
|
);
|
|
};
|
|
};
|
|
|
|
proto._initConnection = function (connection) {
|
|
|
|
if (this._isInitConnection) {
|
|
return;
|
|
}
|
|
|
|
mDBG.log('Connector#_initConnection');
|
|
if (!this._presentation) {
|
|
throw new Error('Init connection without the presentation object.');
|
|
}
|
|
|
|
this._isInitConnection = true;
|
|
|
|
mDBG.log('this._connection = ', connection);
|
|
|
|
this._connection = connection;
|
|
this._connection.onmessage = this.onConnectionMessage.bind(this);
|
|
this._connection.onstatechange = this.onConnectionStateChange.bind(this);
|
|
};
|
|
|
|
proto.sendMsg = function (msg) {
|
|
mDBG.log('Connector#sendMsg');
|
|
mDBG.log('msg = ', msg);
|
|
try {
|
|
var txt = castingMessage.stringify(msg);
|
|
this._connection.send(txt);
|
|
} catch (e) {
|
|
mDBG.error(e);
|
|
}
|
|
};
|
|
|
|
proto.getControllingDeviceInfo = function () {
|
|
return new Promise((resolve, reject) => {
|
|
if (this._controllingDeviceInfo) {
|
|
resolve(this._controllingDeviceInfo);
|
|
} else {
|
|
var handle = () => {
|
|
this.off('ControllingDeviceInfoUpdated', handle);
|
|
resolve(this._controllingDeviceInfo);
|
|
};
|
|
this.on('ControllingDeviceInfoUpdated', handle);
|
|
}
|
|
});
|
|
};
|
|
|
|
proto.replyACK = function (msg, error) {
|
|
|
|
mDBG.log('Connector#replyACK');
|
|
|
|
var reply = {
|
|
'type': 'ack',
|
|
'seq': msg.seq
|
|
};
|
|
|
|
if (error) {
|
|
reply.error = '' + error;
|
|
}
|
|
|
|
this.sendMsg(reply);
|
|
};
|
|
|
|
proto.reportStatus = function (status, data) {
|
|
|
|
mDBG.log('Connector#reportStatus');
|
|
|
|
var msg = {
|
|
'type': 'status',
|
|
'seq': this._msgSeq++,
|
|
'status': status,
|
|
'time': data.time
|
|
};
|
|
|
|
if (data.error) {
|
|
msg.error = '' + data.error;
|
|
}
|
|
|
|
if (data.detail) { // TODO: Discuss should we need this ?
|
|
msg.detail = data.detail;
|
|
}
|
|
|
|
this.sendMsg(msg);
|
|
};
|
|
|
|
proto.handleRemoteMessage = function (msg) {
|
|
|
|
mDBG.log('Connector#handleRemoteMessage');
|
|
mDBG.log('msg = ', msg);
|
|
|
|
var err;
|
|
try {
|
|
|
|
// We don't process the out of dated message.
|
|
if (this._lastSeq >= msg.seq) {
|
|
throw new Error('Receive outdated message with ' +
|
|
'msg sequence = ' + msg.seq);
|
|
}
|
|
this._lastSeq = msg.seq;
|
|
|
|
switch(msg.type) {
|
|
|
|
case 'load':
|
|
if (typeof msg.url != 'string' && !msg.url) {
|
|
throw new Error('Controller dose not provide the url to load.');
|
|
}
|
|
this.fire('loadRequest', { url : msg.url });
|
|
break;
|
|
|
|
case 'play':
|
|
this.fire('playRequest');
|
|
break;
|
|
|
|
case 'pause':
|
|
this.fire('pauseRequest');
|
|
break;
|
|
|
|
case 'seek':
|
|
var time = +msg.time;
|
|
if (time <= 0) {
|
|
throw new Error('Controller asks to seek on invalid time = ' +
|
|
time);
|
|
}
|
|
this.fire('seekRequest', { time : time });
|
|
break;
|
|
|
|
case 'device-info':
|
|
this._controllingDeviceInfo = {
|
|
displayName: msg.displayName
|
|
};
|
|
this.fire('ControllingDeviceInfoUpdated');
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
err = e;
|
|
mDBG.error(e);
|
|
}
|
|
|
|
this.replyACK(msg, err);
|
|
};
|
|
|
|
proto.onConnectionMessage = function (e) {
|
|
mDBG.log('Connector#onConnectionMessage');
|
|
|
|
var messages = castingMessage.parse(e.data);
|
|
|
|
mDBG.log('messages = ', messages);
|
|
|
|
messages.sort((a, b) => { // Make sure message sequence
|
|
return a.seq - b.seq;
|
|
});
|
|
|
|
messages.forEach(message => this.handleRemoteMessage(message));
|
|
};
|
|
|
|
proto.onConnectionStateChange = function () {
|
|
mDBG.log('Connector#onConnectionStateChange');
|
|
mDBG.log('State = ', this._connection.state);
|
|
// TODO: How to do when presentation session is closed or terminated
|
|
};
|
|
|
|
exports.Connector = Connector;
|
|
|
|
})(window);
|