зеркало из https://github.com/mozilla/gecko-dev.git
Bug 972017 Part 4 - Hook up the OT sdk to the direct calling window for Loop. r=nperriault
This commit is contained in:
Родитель
2a779406c7
Коммит
863c847e79
|
@ -33,6 +33,7 @@
|
|||
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/otSdkDriver.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/conversationStore.js"></script>
|
||||
<script type="text/javascript" src="loop/js/conversationViews.js"></script>
|
||||
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
|
||||
|
|
|
@ -227,6 +227,8 @@ loop.Client = (function($) {
|
|||
* @param {Function} cb Callback(err, result)
|
||||
*/
|
||||
setupOutgoingCall: function(calleeIds, callType, cb) {
|
||||
// For direct calls, we only ever use the logged-in session. Direct
|
||||
// calls by guests aren't valid.
|
||||
this.mozLoop.hawkRequest(this.mozLoop.LOOP_SESSION_TYPE.FXA,
|
||||
"/calls", "POST", {
|
||||
calleeId: calleeIds,
|
||||
|
|
|
@ -540,9 +540,15 @@ loop.conversation = (function(mozL10n) {
|
|||
|
||||
var dispatcher = new loop.Dispatcher();
|
||||
var client = new loop.Client();
|
||||
var sdkDriver = new loop.OTSdkDriver({
|
||||
dispatcher: dispatcher,
|
||||
sdk: OT
|
||||
});
|
||||
|
||||
var conversationStore = new loop.store.ConversationStore({}, {
|
||||
client: client,
|
||||
dispatcher: dispatcher
|
||||
dispatcher: dispatcher,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
|
||||
// XXX For now key this on the pref, but this should really be
|
||||
|
|
|
@ -540,9 +540,15 @@ loop.conversation = (function(mozL10n) {
|
|||
|
||||
var dispatcher = new loop.Dispatcher();
|
||||
var client = new loop.Client();
|
||||
var sdkDriver = new loop.OTSdkDriver({
|
||||
dispatcher: dispatcher,
|
||||
sdk: OT
|
||||
});
|
||||
|
||||
var conversationStore = new loop.store.ConversationStore({}, {
|
||||
client: client,
|
||||
dispatcher: dispatcher
|
||||
dispatcher: dispatcher,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
|
||||
// XXX For now key this on the pref, but this should really be
|
||||
|
|
|
@ -158,6 +158,15 @@ loop.conversationViews = (function(mozL10n) {
|
|||
*/
|
||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.addEventListener('resize', this.updateVideoContainer);
|
||||
|
||||
// The SDK needs to know about the configuration and the elements to use
|
||||
// for display. So the best way seems to pass the information here - ideally
|
||||
// the sdk wouldn't need to know this, but we can't change that.
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this._getPublisherConfig(),
|
||||
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
||||
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
||||
}));
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -165,9 +174,41 @@ loop.conversationViews = (function(mozL10n) {
|
|||
window.removeEventListener('resize', this.updateVideoContainer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns either the required DOMNode
|
||||
*
|
||||
* @param {String} className The name of the class to get the element for.
|
||||
*/
|
||||
_getElement: function(className) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the required configuration for publishing video on the sdk.
|
||||
*/
|
||||
_getPublisherConfig: function() {
|
||||
// height set to 100%" to fix video layout on Google Chrome
|
||||
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
|
||||
return {
|
||||
insertMode: "append",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
publishVideo: this.props.video.enabled,
|
||||
style: {
|
||||
bugDisplayMode: "off",
|
||||
buttonDisplayMode: "off",
|
||||
nameDisplayMode: "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to update the video container whenever the orientation or size of the
|
||||
* display area changes.
|
||||
*/
|
||||
updateVideoContainer: function() {
|
||||
var localStreamParent = document.querySelector('.local .OT_publisher');
|
||||
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
||||
var localStreamParent = this._getElement('.local .OT_publisher');
|
||||
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
|
@ -176,13 +217,26 @@ loop.conversationViews = (function(mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hangs up the call.
|
||||
*/
|
||||
hangup: function() {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.HangupCall());
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to control publishing a stream - i.e. to mute a stream
|
||||
*
|
||||
* @param {String} type The type of stream, e.g. "audio" or "video".
|
||||
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
||||
*/
|
||||
publishStream: function(type, enabled) {
|
||||
// XXX Add this as part of bug 972017.
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.SetMute({
|
||||
type: type,
|
||||
enabled: enabled
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -286,7 +340,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
case CALL_STATES.ONGOING: {
|
||||
return (OngoingConversationView({
|
||||
dispatcher: this.props.dispatcher,
|
||||
video: {enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
||||
video: {enabled: this.state.videoMuted},
|
||||
audio: {enabled: this.state.audioMuted}}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -158,6 +158,15 @@ loop.conversationViews = (function(mozL10n) {
|
|||
*/
|
||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||
window.addEventListener('resize', this.updateVideoContainer);
|
||||
|
||||
// The SDK needs to know about the configuration and the elements to use
|
||||
// for display. So the best way seems to pass the information here - ideally
|
||||
// the sdk wouldn't need to know this, but we can't change that.
|
||||
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
publisherConfig: this._getPublisherConfig(),
|
||||
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
||||
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
||||
}));
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -165,9 +174,41 @@ loop.conversationViews = (function(mozL10n) {
|
|||
window.removeEventListener('resize', this.updateVideoContainer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns either the required DOMNode
|
||||
*
|
||||
* @param {String} className The name of the class to get the element for.
|
||||
*/
|
||||
_getElement: function(className) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the required configuration for publishing video on the sdk.
|
||||
*/
|
||||
_getPublisherConfig: function() {
|
||||
// height set to 100%" to fix video layout on Google Chrome
|
||||
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
|
||||
return {
|
||||
insertMode: "append",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
publishVideo: this.props.video.enabled,
|
||||
style: {
|
||||
bugDisplayMode: "off",
|
||||
buttonDisplayMode: "off",
|
||||
nameDisplayMode: "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to update the video container whenever the orientation or size of the
|
||||
* display area changes.
|
||||
*/
|
||||
updateVideoContainer: function() {
|
||||
var localStreamParent = document.querySelector('.local .OT_publisher');
|
||||
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
||||
var localStreamParent = this._getElement('.local .OT_publisher');
|
||||
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
|
@ -176,13 +217,26 @@ loop.conversationViews = (function(mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hangs up the call.
|
||||
*/
|
||||
hangup: function() {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.HangupCall());
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to control publishing a stream - i.e. to mute a stream
|
||||
*
|
||||
* @param {String} type The type of stream, e.g. "audio" or "video".
|
||||
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
||||
*/
|
||||
publishStream: function(type, enabled) {
|
||||
// XXX Add this as part of bug 972017.
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.SetMute({
|
||||
type: type,
|
||||
enabled: enabled
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -286,7 +340,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
case CALL_STATES.ONGOING: {
|
||||
return (<OngoingConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
video={{enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
||||
video={{enabled: this.state.videoMuted}}
|
||||
audio={{enabled: this.state.audioMuted}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -69,6 +69,12 @@ loop.shared.actions = (function() {
|
|||
HangupCall: Action.define("hangupCall", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to indicate the peer hung up the call.
|
||||
*/
|
||||
PeerHungupCall: Action.define("peerHungupCall", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used for notifying of connection progress state changes.
|
||||
* The connection refers to the overall connection flow as indicated
|
||||
|
@ -85,6 +91,35 @@ loop.shared.actions = (function() {
|
|||
ConnectionFailure: Action.define("connectionFailure", {
|
||||
// A string relating to the reason the connection failed.
|
||||
reason: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used by the ongoing views to notify stores about the elements
|
||||
* required for the sdk.
|
||||
*/
|
||||
SetupStreamElements: Action.define("setupStreamElements", {
|
||||
// The configuration for the publisher/subscribe options
|
||||
publisherConfig: Object,
|
||||
// The local stream element
|
||||
getLocalElementFunc: Function,
|
||||
// The remote stream element
|
||||
getRemoteElementFunc: Function
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used for notifying that the media is now up for the call.
|
||||
*/
|
||||
MediaConnected: Action.define("mediaConnected", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to mute or unmute a stream
|
||||
*/
|
||||
SetMute: Action.define("setMute", {
|
||||
// The part of the stream to enable, e.g. "audio" or "video"
|
||||
type: String,
|
||||
// Whether or not to enable the stream.
|
||||
enabled: Boolean
|
||||
})
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -8,7 +8,7 @@ var loop = loop || {};
|
|||
loop.store = (function() {
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
|
||||
/**
|
||||
* Websocket states taken from:
|
||||
|
@ -67,7 +67,7 @@ loop.store = (function() {
|
|||
calleeId: undefined,
|
||||
// The call type for the call.
|
||||
// XXX Don't hard-code, this comes from the data in bug 1072323
|
||||
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
|
||||
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||
|
||||
// Call Connection information
|
||||
// The call id from the loop-server
|
||||
|
@ -81,7 +81,11 @@ loop.store = (function() {
|
|||
// SDK session ID
|
||||
sessionId: undefined,
|
||||
// SDK session token
|
||||
sessionToken: undefined
|
||||
sessionToken: undefined,
|
||||
// If the audio is muted
|
||||
audioMuted: true,
|
||||
// If the video is muted
|
||||
videoMuted: true
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -104,9 +108,13 @@ loop.store = (function() {
|
|||
if (!options.client) {
|
||||
throw new Error("Missing option client");
|
||||
}
|
||||
if (!options.sdkDriver) {
|
||||
throw new Error("Missing option sdkDriver");
|
||||
}
|
||||
|
||||
this.client = options.client;
|
||||
this.dispatcher = options.dispatcher;
|
||||
this.sdkDriver = options.sdkDriver;
|
||||
|
||||
this.dispatcher.register(this, [
|
||||
"connectionFailure",
|
||||
|
@ -114,8 +122,11 @@ loop.store = (function() {
|
|||
"gatherCallData",
|
||||
"connectCall",
|
||||
"hangupCall",
|
||||
"peerHungupCall",
|
||||
"cancelCall",
|
||||
"retryCall"
|
||||
"retryCall",
|
||||
"mediaConnected",
|
||||
"setMute"
|
||||
]);
|
||||
},
|
||||
|
||||
|
@ -126,6 +137,7 @@ loop.store = (function() {
|
|||
* @param {sharedActions.ConnectionFailure} actionData The action data.
|
||||
*/
|
||||
connectionFailure: function(actionData) {
|
||||
this._endSession();
|
||||
this.set({
|
||||
callState: CALL_STATES.TERMINATED,
|
||||
callStateReason: actionData.reason
|
||||
|
@ -152,7 +164,15 @@ loop.store = (function() {
|
|||
this.set({callState: CALL_STATES.ALERTING});
|
||||
break;
|
||||
}
|
||||
case WS_STATES.CONNECTING:
|
||||
case WS_STATES.CONNECTING: {
|
||||
this.sdkDriver.connectSession({
|
||||
apiKey: this.get("apiKey"),
|
||||
sessionId: this.get("sessionId"),
|
||||
sessionToken: this.get("sessionToken")
|
||||
});
|
||||
this.set({callState: CALL_STATES.ONGOING});
|
||||
break;
|
||||
}
|
||||
case WS_STATES.HALF_CONNECTED:
|
||||
case WS_STATES.CONNECTED: {
|
||||
this.set({callState: CALL_STATES.ONGOING});
|
||||
|
@ -179,6 +199,8 @@ loop.store = (function() {
|
|||
callState: CALL_STATES.GATHER
|
||||
});
|
||||
|
||||
this.videoMuted = this.get("callType") !== CALL_TYPES.AUDIO_VIDEO;
|
||||
|
||||
if (this.get("outgoing")) {
|
||||
this._setupOutgoingCall();
|
||||
} // XXX Else, other types aren't supported yet.
|
||||
|
@ -200,15 +222,20 @@ loop.store = (function() {
|
|||
* Hangs up an ongoing call.
|
||||
*/
|
||||
hangupCall: function() {
|
||||
// XXX Stop the SDK once we add it.
|
||||
|
||||
// Ensure the websocket has been disconnected.
|
||||
if (this._websocket) {
|
||||
// Let the server know the user has hung up.
|
||||
this._websocket.mediaFail();
|
||||
this._ensureWebSocketDisconnected();
|
||||
}
|
||||
|
||||
this._endSession();
|
||||
this.set({callState: CALL_STATES.FINISHED});
|
||||
},
|
||||
|
||||
/**
|
||||
* The peer hungup the call.
|
||||
*/
|
||||
peerHungupCall: function() {
|
||||
this._endSession();
|
||||
this.set({callState: CALL_STATES.FINISHED});
|
||||
},
|
||||
|
||||
|
@ -217,24 +244,15 @@ loop.store = (function() {
|
|||
*/
|
||||
cancelCall: function() {
|
||||
var callState = this.get("callState");
|
||||
if (callState === CALL_STATES.TERMINATED) {
|
||||
// All we need to do is close the window.
|
||||
this.set({callState: CALL_STATES.CLOSE});
|
||||
return;
|
||||
if (this._websocket &&
|
||||
(callState === CALL_STATES.CONNECTING ||
|
||||
callState === CALL_STATES.ALERTING)) {
|
||||
// Let the server know the user has hung up.
|
||||
this._websocket.cancel();
|
||||
}
|
||||
|
||||
if (callState === CALL_STATES.CONNECTING ||
|
||||
callState === CALL_STATES.ALERTING) {
|
||||
if (this._websocket) {
|
||||
// Let the server know the user has hung up.
|
||||
this._websocket.cancel();
|
||||
this._ensureWebSocketDisconnected();
|
||||
}
|
||||
this.set({callState: CALL_STATES.CLOSE});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Unsupported cancel in state", callState);
|
||||
this._endSession();
|
||||
this.set({callState: CALL_STATES.CLOSE});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -253,6 +271,23 @@ loop.store = (function() {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies that all media is now connected
|
||||
*/
|
||||
mediaConnected: function() {
|
||||
this._websocket.mediaUp();
|
||||
},
|
||||
|
||||
/**
|
||||
* Records the mute state for the stream.
|
||||
*
|
||||
* @param {sharedActions.setMute} actionData The mute state for the stream type.
|
||||
*/
|
||||
setMute: function(actionData) {
|
||||
var muteType = actionData.type + "Muted";
|
||||
this.set(muteType, actionData.enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtains the outgoing call data from the server and handles the
|
||||
* result.
|
||||
|
@ -308,14 +343,17 @@ loop.store = (function() {
|
|||
},
|
||||
|
||||
/**
|
||||
* Ensures the websocket gets disconnected.
|
||||
* Ensures the session is ended and the websocket is disconnected.
|
||||
*/
|
||||
_ensureWebSocketDisconnected: function() {
|
||||
this.stopListening(this._websocket);
|
||||
_endSession: function(nextState) {
|
||||
this.sdkDriver.disconnectSession();
|
||||
if (this._websocket) {
|
||||
this.stopListening(this._websocket);
|
||||
|
||||
// Now close the websocket.
|
||||
this._websocket.close();
|
||||
delete this._websocket;
|
||||
// Now close the websocket.
|
||||
this._websocket.close();
|
||||
delete this._websocket;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global loop:true */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.OTSdkDriver = (function() {
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
/**
|
||||
* This is a wrapper for the OT sdk. It is used to translate the SDK events into
|
||||
* actions, and instruct the SDK what to do as a result of actions.
|
||||
*/
|
||||
var OTSdkDriver = function(options) {
|
||||
if (!options.dispatcher) {
|
||||
throw new Error("Missing option dispatcher");
|
||||
}
|
||||
if (!options.sdk) {
|
||||
throw new Error("Missing option sdk");
|
||||
}
|
||||
|
||||
this.dispatcher = options.dispatcher;
|
||||
this.sdk = options.sdk;
|
||||
|
||||
this.dispatcher.register(this, [
|
||||
"setupStreamElements",
|
||||
"setMute"
|
||||
]);
|
||||
};
|
||||
|
||||
OTSdkDriver.prototype = {
|
||||
/**
|
||||
* Handles the setupStreamElements action. Saves the required data and
|
||||
* kicks off the initialising of the publisher.
|
||||
*
|
||||
* @param {sharedActions.SetupStreamElements} actionData The data associated
|
||||
* with the action. See action.js.
|
||||
*/
|
||||
setupStreamElements: function(actionData) {
|
||||
this.getLocalElement = actionData.getLocalElementFunc;
|
||||
this.getRemoteElement = actionData.getRemoteElementFunc;
|
||||
this.publisherConfig = actionData.publisherConfig;
|
||||
|
||||
// At this state we init the publisher, even though we might be waiting for
|
||||
// the initial connect of the session. This saves time when setting up
|
||||
// the media.
|
||||
this.publisher = this.sdk.initPublisher(this.getLocalElement(),
|
||||
this.publisherConfig,
|
||||
this._onPublishComplete.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the setMute action. Informs the published stream to mute
|
||||
* or unmute audio as appropriate.
|
||||
*
|
||||
* @param {sharedActions.SetMute} actionData The data associated with the
|
||||
* action. See action.js.
|
||||
*/
|
||||
setMute: function(actionData) {
|
||||
if (actionData.type === "audio") {
|
||||
this.publisher.publishAudio(actionData.enabled);
|
||||
} else {
|
||||
this.publisher.publishVideo(actionData.enabled);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects a session for the SDK, listening to the required events.
|
||||
*
|
||||
* sessionData items:
|
||||
* - sessionId: The OT session ID
|
||||
* - apiKey: The OT API key
|
||||
* - sessionToken: The token for the OT session
|
||||
*
|
||||
* @param {Object} sessionData The session data for setting up the OT session.
|
||||
*/
|
||||
connectSession: function(sessionData) {
|
||||
this.session = this.sdk.initSession(sessionData.sessionId);
|
||||
|
||||
this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
|
||||
this.session.on("connectionDestroyed",
|
||||
this._onConnectionDestroyed.bind(this));
|
||||
this.session.on("sessionDisconnected",
|
||||
this._onSessionDisconnected.bind(this));
|
||||
|
||||
// This starts the actual session connection.
|
||||
this.session.connect(sessionData.apiKey, sessionData.sessionToken,
|
||||
this._onConnectionComplete.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnects the sdk session.
|
||||
*/
|
||||
disconnectSession: function() {
|
||||
if (this.session) {
|
||||
this.session.off("streamCreated", this._onRemoteStreamCreated.bind(this));
|
||||
this.session.off("connectionDestroyed",
|
||||
this._onConnectionDestroyed.bind(this));
|
||||
this.session.off("sessionDisconnected",
|
||||
this._onSessionDisconnected.bind(this));
|
||||
|
||||
this.session.disconnect();
|
||||
delete this.session;
|
||||
}
|
||||
if (this.publisher) {
|
||||
this.publisher.destroy();
|
||||
delete this.publisher;
|
||||
}
|
||||
|
||||
// Also, tidy these variables ready for next time.
|
||||
delete this._sessionConnected;
|
||||
delete this._publisherReady;
|
||||
delete this._publishedLocalStream;
|
||||
delete this._subscribedRemoteStream;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called once the session has finished connecting.
|
||||
*
|
||||
* @param {Error} error An OT error object, null if there was no error.
|
||||
*/
|
||||
_onConnectionComplete: function(error) {
|
||||
if (error) {
|
||||
console.error("Failed to complete connection", error);
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: "couldNotConnect"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this._sessionConnected = true;
|
||||
this._maybePublishLocalStream();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the connection event for a peer's connection being dropped.
|
||||
*
|
||||
* @param {SessionDisconnectEvent} event The event details
|
||||
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
|
||||
*/
|
||||
_onConnectionDestroyed: function(event) {
|
||||
var action;
|
||||
if (event.reason === "clientDisconnected") {
|
||||
action = new sharedActions.PeerHungupCall();
|
||||
} else {
|
||||
// Strictly speaking this isn't a failure on our part, but since our
|
||||
// flow requires a full reconnection, then we just treat this as
|
||||
// if a failure of our end had occurred.
|
||||
action = new sharedActions.ConnectionFailure({
|
||||
reason: "peerNetworkDisconnected"
|
||||
});
|
||||
}
|
||||
this.dispatcher.dispatch(action);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the session event for the connection for this client being
|
||||
* destroyed.
|
||||
*
|
||||
* @param {SessionDisconnectEvent} event The event details:
|
||||
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
|
||||
*/
|
||||
_onSessionDisconnected: function(event) {
|
||||
// We only need to worry about the network disconnected reason here.
|
||||
if (event.reason === "networkDisconnected") {
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: "networkDisconnected"
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the event when the remote stream is created.
|
||||
*
|
||||
* @param {StreamEvent} event The event details:
|
||||
* https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
|
||||
*/
|
||||
_onRemoteStreamCreated: function(event) {
|
||||
this.session.subscribe(event.stream,
|
||||
this.getRemoteElement(), this.publisherConfig);
|
||||
|
||||
this._subscribedRemoteStream = true;
|
||||
if (this._checkAllStreamsConnected()) {
|
||||
this.dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the publishing being complete.
|
||||
*
|
||||
* @param {Error} error An OT error object, null if there was no error.
|
||||
*/
|
||||
_onPublishComplete: function(error) {
|
||||
if (error) {
|
||||
console.error("Failed to initialize publisher", error);
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: "noMedia"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this._publisherReady = true;
|
||||
this._maybePublishLocalStream();
|
||||
},
|
||||
|
||||
/**
|
||||
* Publishes the local stream if the session is connected
|
||||
* and the publisher is ready.
|
||||
*/
|
||||
_maybePublishLocalStream: function() {
|
||||
if (this._sessionConnected && this._publisherReady) {
|
||||
// We are clear to publish the stream to the session.
|
||||
this.session.publish(this.publisher);
|
||||
|
||||
// Now record the fact, and check if we've got all media yet.
|
||||
this._publishedLocalStream = true;
|
||||
if (this._checkAllStreamsConnected()) {
|
||||
this.dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to check if both local and remote streams are available
|
||||
* and send an action if they are.
|
||||
*/
|
||||
_checkAllStreamsConnected: function() {
|
||||
return this._publishedLocalStream &&
|
||||
this._subscribedRemoteStream;
|
||||
}
|
||||
};
|
||||
|
||||
return OTSdkDriver;
|
||||
|
||||
})();
|
|
@ -59,6 +59,7 @@ browser.jar:
|
|||
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
||||
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
||||
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
||||
content/browser/loop/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
|
||||
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
||||
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
||||
content/browser/loop/shared/js/validate.js (content/shared/js/validate.js)
|
||||
|
|
|
@ -161,26 +161,120 @@ describe("loop.conversationViews", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("OngoingConversationView", function() {
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(
|
||||
loop.conversationViews.OngoingConversationView(props));
|
||||
}
|
||||
|
||||
it("should dispatch a setupStreamElements action when the view is created",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
dispatcher: dispatcher
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "setupStreamElements"));
|
||||
});
|
||||
|
||||
it("should dispatch a hangupCall action when the hangup button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
dispatcher: dispatcher
|
||||
});
|
||||
|
||||
var hangupBtn = view.getDOMNode().querySelector('.btn-hangup');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(hangupBtn);
|
||||
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "hangupCall"));
|
||||
});
|
||||
|
||||
it("should dispatch a setMute action when the audio mute button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
dispatcher: dispatcher,
|
||||
audio: {enabled: false}
|
||||
});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(muteBtn);
|
||||
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "setMute"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("enabled", true));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("type", "audio"));
|
||||
});
|
||||
|
||||
it("should dispatch a setMute action when the video mute button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
dispatcher: dispatcher,
|
||||
video: {enabled: true}
|
||||
});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(muteBtn);
|
||||
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "setMute"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("enabled", false));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("type", "video"));
|
||||
});
|
||||
|
||||
it("should set the mute button as mute off", function() {
|
||||
view = mountTestComponent({
|
||||
dispatcher: dispatcher,
|
||||
video: {enabled: true}
|
||||
});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
|
||||
|
||||
expect(muteBtn.classList.contains("muted")).eql(false);
|
||||
});
|
||||
|
||||
it("should set the mute button as mute on", function() {
|
||||
view = mountTestComponent({
|
||||
dispatcher: dispatcher,
|
||||
audio: {enabled: false}
|
||||
});
|
||||
|
||||
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
|
||||
|
||||
expect(muteBtn.classList.contains("muted")).eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OutgoingConversationView", function() {
|
||||
var store;
|
||||
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
loop.conversationViews.OutgoingConversationView({
|
||||
dispatcher: dispatcher,
|
||||
store: store
|
||||
}));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
store = new loop.store.ConversationStore({}, {
|
||||
dispatcher: dispatcher,
|
||||
client: {}
|
||||
});
|
||||
|
||||
navigator.mozLoop = {
|
||||
getLoopCharPref: function() { return "fake"; },
|
||||
appVersionInfo: sinon.spy()
|
||||
};
|
||||
|
||||
store = new loop.store.ConversationStore({}, {
|
||||
dispatcher: dispatcher,
|
||||
client: {},
|
||||
sdkDriver: {}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
|
|
@ -143,7 +143,8 @@ describe("loop.conversation", function() {
|
|||
dispatcher = new loop.Dispatcher();
|
||||
store = new loop.store.ConversationStore({}, {
|
||||
client: client,
|
||||
dispatcher: dispatcher
|
||||
dispatcher: dispatcher,
|
||||
sdkDriver: {}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<script src="../../content/shared/js/actions.js"></script>
|
||||
<script src="../../content/shared/js/validate.js"></script>
|
||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||
<script src="../../content/js/client.js"></script>
|
||||
<script src="../../content/js/conversationViews.js"></script>
|
||||
<script src="../../content/js/conversation.js"></script>
|
||||
|
|
|
@ -10,8 +10,9 @@ describe("loop.ConversationStore", function () {
|
|||
var WS_STATES = loop.store.WS_STATES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sandbox, dispatcher, client, store, fakeSessionData;
|
||||
var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver;
|
||||
var connectPromise, resolveConnectPromise, rejectConnectPromise;
|
||||
var wsCancelSpy, wsCloseSpy, wsMediaUpSpy, fakeWebsocket;
|
||||
|
||||
function checkFailures(done, f) {
|
||||
try {
|
||||
|
@ -29,9 +30,25 @@ describe("loop.ConversationStore", function () {
|
|||
client = {
|
||||
setupOutgoingCall: sinon.stub()
|
||||
};
|
||||
sdkDriver = {
|
||||
connectSession: sinon.stub(),
|
||||
disconnectSession: sinon.stub()
|
||||
};
|
||||
|
||||
wsCancelSpy = sinon.spy();
|
||||
wsCloseSpy = sinon.spy();
|
||||
wsMediaUpSpy = sinon.spy();
|
||||
|
||||
fakeWebsocket = {
|
||||
cancel: wsCancelSpy,
|
||||
close: wsCloseSpy,
|
||||
mediaUp: wsMediaUpSpy
|
||||
};
|
||||
|
||||
store = new loop.store.ConversationStore({}, {
|
||||
client: client,
|
||||
dispatcher: dispatcher
|
||||
dispatcher: dispatcher,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
fakeSessionData = {
|
||||
apiKey: "fakeKey",
|
||||
|
@ -63,18 +80,51 @@ describe("loop.ConversationStore", function () {
|
|||
describe("#initialize", function() {
|
||||
it("should throw an error if the dispatcher is missing", function() {
|
||||
expect(function() {
|
||||
new loop.store.ConversationStore({}, {client: client});
|
||||
new loop.store.ConversationStore({}, {
|
||||
client: client,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
}).to.Throw(/dispatcher/);
|
||||
});
|
||||
|
||||
it("should throw an error if the client is missing", function() {
|
||||
expect(function() {
|
||||
new loop.store.ConversationStore({}, {dispatcher: dispatcher});
|
||||
new loop.store.ConversationStore({}, {
|
||||
dispatcher: dispatcher,
|
||||
sdkDriver: sdkDriver
|
||||
});
|
||||
}).to.Throw(/client/);
|
||||
});
|
||||
|
||||
it("should throw an error if the sdkDriver is missing", function() {
|
||||
expect(function() {
|
||||
new loop.store.ConversationStore({}, {
|
||||
client: client,
|
||||
dispatcher: dispatcher
|
||||
});
|
||||
}).to.Throw(/sdkDriver/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#connectionFailure", function() {
|
||||
beforeEach(function() {
|
||||
store._websocket = fakeWebsocket;
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the state to 'terminated'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
|
||||
|
@ -119,35 +169,29 @@ describe("loop.ConversationStore", function () {
|
|||
});
|
||||
|
||||
describe("progress: connecting", function() {
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
beforeEach(function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
});
|
||||
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress: half-connected", function() {
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
it("should connect the session", function() {
|
||||
store.set(fakeSessionData);
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.HALF_CONNECTED}));
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
});
|
||||
});
|
||||
|
||||
describe("progress: connecting", function() {
|
||||
it("should change the state to 'ongoing'", function() {
|
||||
store.set({callState: CALL_STATES.ALERTING});
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTED}));
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||
sinon.assert.calledOnce(sdkDriver.connectSession);
|
||||
sinon.assert.calledWithExactly(sdkDriver.connectSession, {
|
||||
apiKey: "fakeKey",
|
||||
sessionId: "321456",
|
||||
sessionToken: "341256"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -332,6 +376,12 @@ describe("loop.ConversationStore", function () {
|
|||
store.set({callState: CALL_STATES.ONGOING});
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should send a media-fail message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||
|
||||
|
@ -351,7 +401,69 @@ describe("loop.ConversationStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#peerHungupCall", function() {
|
||||
var wsMediaFailSpy, wsCloseSpy;
|
||||
beforeEach(function() {
|
||||
wsMediaFailSpy = sinon.spy();
|
||||
wsCloseSpy = sinon.spy();
|
||||
|
||||
store._websocket = {
|
||||
mediaFail: wsMediaFailSpy,
|
||||
close: wsCloseSpy
|
||||
};
|
||||
store.set({callState: CALL_STATES.ONGOING});
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the callState to finished", function() {
|
||||
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#cancelCall", function() {
|
||||
beforeEach(function() {
|
||||
store._websocket = fakeWebsocket;
|
||||
|
||||
store.set({callState: CALL_STATES.CONNECTING});
|
||||
});
|
||||
|
||||
it("should disconnect the session", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||
});
|
||||
|
||||
it("should send a cancel message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCancelSpy);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the state to close if the call is connecting", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
|
||||
it("should set the state to close if the call has terminated already", function() {
|
||||
store.set({callState: CALL_STATES.TERMINATED});
|
||||
|
||||
|
@ -360,37 +472,6 @@ describe("loop.ConversationStore", function () {
|
|||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
|
||||
describe("whilst connecting", function() {
|
||||
var wsCancelSpy, wsCloseSpy;
|
||||
beforeEach(function() {
|
||||
wsCancelSpy = sinon.spy();
|
||||
wsCloseSpy = sinon.spy();
|
||||
|
||||
store._websocket = {
|
||||
cancel: wsCancelSpy,
|
||||
close: wsCloseSpy
|
||||
};
|
||||
store.set({callState: CALL_STATES.CONNECTING});
|
||||
});
|
||||
|
||||
it("should send a cancel message to the websocket if it is open", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCancelSpy);
|
||||
});
|
||||
|
||||
it("should ensure the websocket is closed", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
sinon.assert.calledOnce(wsCloseSpy);
|
||||
});
|
||||
|
||||
it("should set the state to close if the call is connecting", function() {
|
||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
|
||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#retryCall", function() {
|
||||
|
@ -418,6 +499,40 @@ describe("loop.ConversationStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#mediaConnected", function() {
|
||||
it("should send mediaUp via the websocket", function() {
|
||||
store._websocket = fakeWebsocket;
|
||||
|
||||
dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||
|
||||
sinon.assert.calledOnce(wsMediaUpSpy);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setMute", function() {
|
||||
it("should save the mute state for the audio stream", function() {
|
||||
store.set({"audioMuted": false});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.SetMute({
|
||||
type: "audio",
|
||||
enabled: true
|
||||
}));
|
||||
|
||||
expect(store.get("audioMuted")).eql(true);
|
||||
});
|
||||
|
||||
it("should save the mute state for the video stream", function() {
|
||||
store.set({"videoMuted": true});
|
||||
|
||||
dispatcher.dispatch(new sharedActions.SetMute({
|
||||
type: "video",
|
||||
enabled: false
|
||||
}));
|
||||
|
||||
expect(store.get("videoMuted")).eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
describe("Websocket progress", function() {
|
||||
beforeEach(function() {
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
<script src="../../content/shared/js/validate.js"></script>
|
||||
<script src="../../content/shared/js/actions.js"></script>
|
||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||
<script src="../../content/shared/js/conversationStore.js"></script>
|
||||
|
||||
<!-- Test scripts -->
|
||||
|
@ -54,6 +55,7 @@
|
|||
<script src="validate_test.js"></script>
|
||||
<script src="dispatcher_test.js"></script>
|
||||
<script src="conversationStore_test.js"></script>
|
||||
<script src="otSdkDriver_test.js"></script>
|
||||
<script>
|
||||
mocha.run(function () {
|
||||
$("#mocha").append("<p id='complete'>Complete.</p>");
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var expect = chai.expect;
|
||||
|
||||
describe("loop.OTSdkDriver", function () {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
var sandbox;
|
||||
var dispatcher, driver, publisher, sdk, session, sessionData;
|
||||
var fakeLocalElement, fakeRemoteElement, publisherConfig;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
fakeLocalElement = {fake: 1};
|
||||
fakeRemoteElement = {fake: 2};
|
||||
publisherConfig = {
|
||||
fake: "config"
|
||||
};
|
||||
sessionData = {
|
||||
apiKey: "1234567890",
|
||||
sessionId: "3216549870",
|
||||
sessionToken: "1357924680"
|
||||
};
|
||||
|
||||
dispatcher = new loop.Dispatcher();
|
||||
session = _.extend({
|
||||
connect: sinon.stub(),
|
||||
disconnect: sinon.stub(),
|
||||
publish: sinon.stub(),
|
||||
subscribe: sinon.stub()
|
||||
}, Backbone.Events);
|
||||
|
||||
publisher = {
|
||||
destroy: sinon.stub(),
|
||||
publishAudio: sinon.stub(),
|
||||
publishVideo: sinon.stub()
|
||||
};
|
||||
|
||||
sdk = {
|
||||
initPublisher: sinon.stub(),
|
||||
initSession: sinon.stub().returns(session)
|
||||
};
|
||||
|
||||
driver = new loop.OTSdkDriver({
|
||||
dispatcher: dispatcher,
|
||||
sdk: sdk
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe("Constructor", function() {
|
||||
it("should throw an error if the dispatcher is missing", function() {
|
||||
expect(function() {
|
||||
new loop.OTSdkDriver({sdk: sdk});
|
||||
}).to.Throw(/dispatcher/);
|
||||
});
|
||||
|
||||
it("should throw an error if the sdk is missing", function() {
|
||||
expect(function() {
|
||||
new loop.OTSdkDriver({dispatcher: dispatcher});
|
||||
}).to.Throw(/sdk/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setupStreamElements", function() {
|
||||
it("should call initPublisher", function() {
|
||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||
publisherConfig: publisherConfig
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(sdk.initPublisher);
|
||||
sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
|
||||
});
|
||||
|
||||
describe("On Publisher Complete", function() {
|
||||
it("should publish the stream if the connection is ready", function() {
|
||||
sdk.initPublisher.callsArgWith(2, null);
|
||||
|
||||
driver.session = session;
|
||||
driver._sessionConnected = true;
|
||||
|
||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||
publisherConfig: publisherConfig
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(session.publish);
|
||||
});
|
||||
|
||||
it("should dispatch connectionFailure if connecting failed", function() {
|
||||
sdk.initPublisher.callsArgWith(2, new Error("Failure"));
|
||||
|
||||
// Special stub, as we want to use the dispatcher, but also know that
|
||||
// we've been called correctly for the second dispatch.
|
||||
var dispatchStub = (function() {
|
||||
var originalDispatch = dispatcher.dispatch.bind(dispatcher);
|
||||
return sandbox.stub(dispatcher, "dispatch", function(action) {
|
||||
originalDispatch(action);
|
||||
});
|
||||
}());
|
||||
|
||||
driver.session = session;
|
||||
driver._sessionConnected = true;
|
||||
|
||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||
publisherConfig: publisherConfig
|
||||
}));
|
||||
|
||||
sinon.assert.called(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionFailure"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("reason", "noMedia"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setMute", function() {
|
||||
beforeEach(function() {
|
||||
sdk.initPublisher.returns(publisher);
|
||||
|
||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||
publisherConfig: publisherConfig
|
||||
}));
|
||||
});
|
||||
|
||||
it("should publishAudio with the correct enabled value", function() {
|
||||
dispatcher.dispatch(new sharedActions.SetMute({
|
||||
type: "audio",
|
||||
enabled: false
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(publisher.publishAudio);
|
||||
sinon.assert.calledWithExactly(publisher.publishAudio, false);
|
||||
});
|
||||
|
||||
it("should publishVideo with the correct enabled value", function() {
|
||||
dispatcher.dispatch(new sharedActions.SetMute({
|
||||
type: "video",
|
||||
enabled: true
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(publisher.publishVideo);
|
||||
sinon.assert.calledWithExactly(publisher.publishVideo, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#connectSession", function() {
|
||||
it("should initialise a new session", function() {
|
||||
driver.connectSession(sessionData);
|
||||
|
||||
sinon.assert.calledOnce(sdk.initSession);
|
||||
sinon.assert.calledWithExactly(sdk.initSession, "3216549870");
|
||||
});
|
||||
|
||||
it("should connect the session", function () {
|
||||
driver.connectSession(sessionData);
|
||||
|
||||
sinon.assert.calledOnce(session.connect);
|
||||
sinon.assert.calledWith(session.connect, "1234567890", "1357924680");
|
||||
});
|
||||
|
||||
describe("On connection complete", function() {
|
||||
it("should publish the stream if the publisher is ready", function() {
|
||||
driver._publisherReady = true;
|
||||
session.connect.callsArg(2);
|
||||
|
||||
driver.connectSession(sessionData);
|
||||
|
||||
sinon.assert.calledOnce(session.publish);
|
||||
});
|
||||
|
||||
it("should dispatch connectionFailure if connecting failed", function() {
|
||||
session.connect.callsArgWith(2, new Error("Failure"));
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
||||
driver.connectSession(sessionData);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionFailure"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("reason", "couldNotConnect"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#disconnectionSession", function() {
|
||||
it("should disconnect the session", function() {
|
||||
driver.session = session;
|
||||
|
||||
driver.disconnectSession();
|
||||
|
||||
sinon.assert.calledOnce(session.disconnect);
|
||||
});
|
||||
|
||||
it("should destroy the publisher", function() {
|
||||
driver.publisher = publisher;
|
||||
|
||||
driver.disconnectSession();
|
||||
|
||||
sinon.assert.calledOnce(publisher.destroy);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
beforeEach(function() {
|
||||
driver.connectSession(sessionData);
|
||||
|
||||
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||
publisherConfig: publisherConfig
|
||||
}));
|
||||
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
});
|
||||
|
||||
describe("connectionDestroyed", function() {
|
||||
it("should dispatch a peerHungupCall action if the client disconnected", function() {
|
||||
session.trigger("connectionDestroyed", {
|
||||
reason: "clientDisconnected"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "peerHungupCall"));
|
||||
});
|
||||
|
||||
it("should dispatch a connectionFailure action if the connection failed", function() {
|
||||
session.trigger("connectionDestroyed", {
|
||||
reason: "networkDisconnected"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionFailure"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("reason", "peerNetworkDisconnected"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("sessionDisconnected", function() {
|
||||
it("should dispatch a connectionFailure action if the session was disconnected",
|
||||
function() {
|
||||
session.trigger("sessionDisconnected", {
|
||||
reason: "networkDisconnected"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "connectionFailure"));
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("reason", "networkDisconnected"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("streamCreated", function() {
|
||||
var fakeStream;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeStream = {
|
||||
fakeStream: 3
|
||||
};
|
||||
});
|
||||
|
||||
it("should subscribe to the stream", function() {
|
||||
session.trigger("streamCreated", {stream: fakeStream});
|
||||
|
||||
sinon.assert.calledOnce(session.subscribe);
|
||||
sinon.assert.calledWithExactly(session.subscribe,
|
||||
fakeStream, fakeRemoteElement, publisherConfig);
|
||||
});
|
||||
|
||||
it("should dispach a mediaConnected action if both streams are up", function() {
|
||||
driver._publishedLocalStream = true;
|
||||
|
||||
session.trigger("streamCreated", {stream: fakeStream});
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "mediaConnected"));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче