author | Phil Ringnalda <philringnalda@gmail.com> |
Sun, 08 Mar 2015 13:29:18 -0700 | |
changeset 232465 | eab4a81e445744d646c475d6da8c69e4ac8df9de |
parent 232461 | d5beb5eb843344e7b9952d56cac0ab9ed5e2577e (current diff) |
parent 232464 | d94db91d6fb1fa201c23bbead12bfd60daf57927 (diff) |
child 232470 | ed20bd96bbaa859814e2d299ef1d94dcd21738e2 |
child 232480 | 8bc13df2c603d26d9d26474a9a62dabbfb22f668 |
child 232499 | c0aa91aff8e578d5fcf4f9cfe99fd3b9b770af99 |
push id | 28382 |
push user | philringnalda@gmail.com |
push date | Sun, 08 Mar 2015 20:29:29 +0000 |
treeherder | mozilla-central@eab4a81e4457 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 39.0a1 |
first release with | nightly linux32
eab4a81e4457
/
39.0a1
/
20150309030224
/
files
nightly linux64
eab4a81e4457
/
39.0a1
/
20150309030224
/
files
nightly mac
eab4a81e4457
/
39.0a1
/
20150309030224
/
files
nightly win32
eab4a81e4457
/
39.0a1
/
20150309030224
/
files
nightly win64
eab4a81e4457
/
39.0a1
/
20150309030224
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
39.0a1
/
20150309030224
/
pushlog to previous
nightly linux64
39.0a1
/
20150309030224
/
pushlog to previous
nightly mac
39.0a1
/
20150309030224
/
pushlog to previous
nightly win32
39.0a1
/
20150309030224
/
pushlog to previous
nightly win64
39.0a1
/
20150309030224
/
pushlog to previous
|
--- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -113,16 +113,17 @@ loop.conversation = (function(mozL10n) { navigator.mozLoop.setLoopPref("ot.guid", guid, PREF_STRING); callback(null); } }); var dispatcher = new loop.Dispatcher(); var client = new loop.Client(); var sdkDriver = new loop.OTSdkDriver({ + isDesktop: true, dispatcher: dispatcher, sdk: OT }); var appVersionInfo = navigator.mozLoop.appVersionInfo; var feedbackClient = new loop.FeedbackAPIClient( navigator.mozLoop.getLoopPref("feedback.baseUrl"), { product: navigator.mozLoop.getLoopPref("feedback.product"), platform: appVersionInfo.OS, @@ -132,20 +133,22 @@ loop.conversation = (function(mozL10n) { // Create the stores. var conversationAppStore = new loop.store.ConversationAppStore({ dispatcher: dispatcher, mozLoop: navigator.mozLoop }); var conversationStore = new loop.store.ConversationStore(dispatcher, { client: client, + isDesktop: true, mozLoop: navigator.mozLoop, sdkDriver: sdkDriver }); var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, { + isDesktop: true, mozLoop: navigator.mozLoop, sdkDriver: sdkDriver }); var roomStore = new loop.store.RoomStore(dispatcher, { mozLoop: navigator.mozLoop, activeRoomStore: activeRoomStore }); var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
--- a/browser/components/loop/content/js/conversation.jsx +++ b/browser/components/loop/content/js/conversation.jsx @@ -113,16 +113,17 @@ loop.conversation = (function(mozL10n) { navigator.mozLoop.setLoopPref("ot.guid", guid, PREF_STRING); callback(null); } }); var dispatcher = new loop.Dispatcher(); var client = new loop.Client(); var sdkDriver = new loop.OTSdkDriver({ + isDesktop: true, dispatcher: dispatcher, sdk: OT }); var appVersionInfo = navigator.mozLoop.appVersionInfo; var feedbackClient = new loop.FeedbackAPIClient( navigator.mozLoop.getLoopPref("feedback.baseUrl"), { product: navigator.mozLoop.getLoopPref("feedback.product"), platform: appVersionInfo.OS, @@ -132,20 +133,22 @@ loop.conversation = (function(mozL10n) { // Create the stores. var conversationAppStore = new loop.store.ConversationAppStore({ dispatcher: dispatcher, mozLoop: navigator.mozLoop }); var conversationStore = new loop.store.ConversationStore(dispatcher, { client: client, + isDesktop: true, mozLoop: navigator.mozLoop, sdkDriver: sdkDriver }); var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, { + isDesktop: true, mozLoop: navigator.mozLoop, sdkDriver: sdkDriver }); var roomStore = new loop.store.RoomStore(dispatcher, { mozLoop: navigator.mozLoop, activeRoomStore: activeRoomStore }); var feedbackStore = new loop.store.FeedbackStore(dispatcher, {
--- a/browser/components/loop/content/shared/js/activeRoomStore.js +++ b/browser/components/loop/content/shared/js/activeRoomStore.js @@ -50,16 +50,18 @@ loop.store.ActiveRoomStore = (function() throw new Error("Missing option mozLoop"); } this._mozLoop = options.mozLoop; if (!options.sdkDriver) { throw new Error("Missing option sdkDriver"); } this._sdkDriver = options.sdkDriver; + + this._isDesktop = options.isDesktop || false; }, /** * Returns initial state data for this active room. */ getInitialStoreState: function() { return { roomState: ROOM_STATES.INIT, @@ -351,16 +353,31 @@ loop.store.ActiveRoomStore = (function() }, /** * Handles disconnection of this local client from the sdk servers. * * @param {sharedActions.ConnectionFailure} actionData */ connectionFailure: function(actionData) { + /** + * XXX This is a workaround for desktop machines that do not have a + * camera installed. As we don't yet have device enumeration, when + * we do, this can be removed (bug 1138851), and the sdk should handle it. + */ + if (this._isDesktop && + actionData.reason === FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA && + this.getStoreState().videoMuted === false) { + // We failed to publish with media, so due to the bug, we try again without + // video. + this.setStoreState({videoMuted: true}); + this._sdkDriver.retryPublishWithoutVideo(); + return; + } + // Treat all reasons as something failed. In theory, clientDisconnected // could be a success case, but there's no way we should be intentionally // sending that and still have the window open. this.setStoreState({ failureReason: actionData.reason }); this._leaveRoom(ROOM_STATES.FAILED);
--- a/browser/components/loop/content/shared/js/conversationStore.js +++ b/browser/components/loop/content/shared/js/conversationStore.js @@ -5,18 +5,19 @@ /* global loop:true */ var loop = loop || {}; loop.store = loop.store || {}; (function() { var sharedActions = loop.shared.actions; var CALL_TYPES = loop.shared.utils.CALL_TYPES; + var REST_ERRNOS = loop.shared.utils.REST_ERRNOS; + var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS; - var REST_ERRNOS = loop.shared.utils.REST_ERRNOS; /** * Websocket states taken from: * https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress */ var WS_STATES = loop.store.WS_STATES = { // The call is starting, and the remote party is not yet being alerted. INIT: "init", // The called party is being alerted. @@ -127,25 +128,41 @@ loop.store = loop.store || {}; } if (!options.mozLoop) { throw new Error("Missing option mozLoop"); } this.client = options.client; this.sdkDriver = options.sdkDriver; this.mozLoop = options.mozLoop; + this._isDesktop = options.isDesktop || false; }, /** * Handles the connection failure action, setting the state to * terminated. * * @param {sharedActions.ConnectionFailure} actionData The action data. */ connectionFailure: function(actionData) { + /** + * XXX This is a workaround for desktop machines that do not have a + * camera installed. As we don't yet have device enumeration, when + * we do, this can be removed (bug 1138851), and the sdk should handle it. + */ + if (this._isDesktop && + actionData.reason === FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA && + this.getStoreState().videoMuted === false) { + // We failed to publish with media, so due to the bug, we try again without + // video. + this.setStoreState({videoMuted: true}); + this.sdkDriver.retryPublishWithoutVideo(); + return; + } + this._endSession(); this.setStoreState({ callState: CALL_STATES.TERMINATED, callStateReason: actionData.reason }); }, /**
--- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -28,16 +28,31 @@ loop.OTSdkDriver = (function() { this.sdk = options.sdk; this.connections = {}; this.dispatcher.register(this, [ "setupStreamElements", "setMute" ]); + + /** + * XXX This is a workaround for desktop machines that do not have a + * camera installed. As we don't yet have device enumeration, when + * we do, this can be removed (bug 1138851), and the sdk should handle it. + */ + if ("isDesktop" in options && options.isDesktop && + !window.MediaStreamTrack.getSources) { + // If there's no getSources function, the sdk defines its own and caches + // the result. So here we define the "normal" one which doesn't get cached, so + // we can change it later. + window.MediaStreamTrack.getSources = function(callback) { + callback([{kind: "audio"}, {kind: "video"}]); + }; + } }; OTSdkDriver.prototype = { /** * Clones the publisher config into a new object, as the sdk modifies the * properties object. */ _getCopyPublisherConfig: function() { @@ -52,29 +67,50 @@ loop.OTSdkDriver = (function() { * with the action. See action.js. */ setupStreamElements: function(actionData) { this.getLocalElement = actionData.getLocalElementFunc; this.getScreenShareElementFunc = actionData.getScreenShareElementFunc; this.getRemoteElement = actionData.getRemoteElementFunc; this.publisherConfig = actionData.publisherConfig; + this.sdk.on("exception", this._onOTException.bind(this)); + // 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._publishLocalStreams(); + }, + + /** + * Internal function to publish a local stream. + * XXX This can be simplified when bug 1138851 is actioned. + */ + _publishLocalStreams: function() { this.publisher = this.sdk.initPublisher(this.getLocalElement(), this._getCopyPublisherConfig()); this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this)); this.publisher.on("accessAllowed", this._onPublishComplete.bind(this)); this.publisher.on("accessDenied", this._onPublishDenied.bind(this)); this.publisher.on("accessDialogOpened", this._onAccessDialogOpened.bind(this)); }, /** + * Forces the sdk into not using video, and starts publishing again. + * XXX This is part of the work around that will be removed by bug 1138851. + */ + retryPublishWithoutVideo: function() { + window.MediaStreamTrack.getSources = function(callback) { + callback([{kind: "audio"}]); + }; + this._publishLocalStreams(); + }, + + /** * 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") { @@ -431,16 +467,32 @@ loop.OTSdkDriver = (function() { // This prevents the SDK's "access denied" dialog showing. event.preventDefault(); this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ reason: FAILURE_DETAILS.MEDIA_DENIED })); }, + _onOTException: function(event) { + if (event.code === OT.ExceptionCodes.UNABLE_TO_PUBLISH && + event.message === "GetUserMedia") { + // We free up the publisher here in case the store wants to try + // grabbing the media again. + if (this.publisher) { + this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated"); + this.publisher.destroy(); + delete this.publisher; + } + this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ + reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA + })); + } + }, + /** * Handles publishing of property changes to a stream. */ _onStreamPropertyChanged: function(event) { if (event.changedProperty == STREAM_PROPERTIES.VIDEO_DIMENSIONS) { this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({ isLocal: event.stream.connection.id == this.session.connection.id, videoType: event.stream.videoType,
--- a/browser/components/loop/content/shared/js/utils.js +++ b/browser/components/loop/content/shared/js/utils.js @@ -31,16 +31,17 @@ loop.shared.utils = (function(mozL10n) { CLOSED: "closed", MEDIA_FAIL: "media-fail", REJECT: "reject", TIMEOUT: "timeout" }; var FAILURE_DETAILS = { MEDIA_DENIED: "reason-media-denied", + UNABLE_TO_PUBLISH_MEDIA: "unable-to-publish-media", COULD_NOT_CONNECT: "reason-could-not-connect", NETWORK_DISCONNECTED: "reason-network-disconnected", EXPIRED_OR_INVALID: "reason-expired-or-invalid", UNKNOWN: "reason-unknown" }; var STREAM_PROPERTIES = { VIDEO_DIMENSIONS: "videoDimensions",
--- a/browser/components/loop/test/shared/activeRoomStore_test.js +++ b/browser/components/loop/test/shared/activeRoomStore_test.js @@ -36,16 +36,17 @@ describe("loop.store.ActiveRoomStore", f setScreenShareState: sinon.stub(), getActiveTabWindowId: sandbox.stub().callsArgWith(0, null, 42) }; fakeSdkDriver = { connectSession: sinon.stub(), disconnectSession: sinon.stub(), forceDisconnectAll: sinon.stub().callsArg(0), + retryPublishWithoutVideo: sinon.stub(), startScreenShare: sinon.stub(), switchAcquiredWindow: sinon.stub(), endScreenShare: sinon.stub().returns(true) }; fakeMultiplexGum = { reset: sandbox.spy() }; @@ -604,16 +605,36 @@ describe("loop.store.ActiveRoomStore", f sessionToken: "1627384950" }); connectionFailureAction = new sharedActions.ConnectionFailure({ reason: "FAIL" }); }); + it("should retry publishing if on desktop, and in the videoMuted state", function() { + store._isDesktop = true; + + store.connectionFailure(new sharedActions.ConnectionFailure({ + reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA + })); + + sinon.assert.calledOnce(fakeSdkDriver.retryPublishWithoutVideo); + }); + + it("should set videoMuted to try when retrying publishing", function() { + store._isDesktop = true; + + store.connectionFailure(new sharedActions.ConnectionFailure({ + reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA + })); + + expect(store.getStoreState().videoMuted).eql(true); + }); + it("should store the failure reason", function() { store.connectionFailure(connectionFailureAction); expect(store.getStoreState().failureReason).eql("FAIL"); }); it("should reset the multiplexGum", function() { store.connectionFailure(connectionFailureAction);
--- a/browser/components/loop/test/shared/conversationStore_test.js +++ b/browser/components/loop/test/shared/conversationStore_test.js @@ -4,16 +4,17 @@ var expect = chai.expect; describe("loop.store.ConversationStore", function () { "use strict"; var CALL_STATES = loop.store.CALL_STATES; var WS_STATES = loop.store.WS_STATES; var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS; + var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS; var sharedActions = loop.shared.actions; var sharedUtils = loop.shared.utils; var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver; var contact, fakeMozLoop; var connectPromise, resolveConnectPromise, rejectConnectPromise; var wsCancelSpy, wsCloseSpy, wsMediaUpSpy, fakeWebsocket; function checkFailures(done, f) { @@ -51,17 +52,18 @@ describe("loop.store.ConversationStore", dispatcher = new loop.Dispatcher(); client = { setupOutgoingCall: sinon.stub(), requestCallUrl: sinon.stub() }; sdkDriver = { connectSession: sinon.stub(), - disconnectSession: sinon.stub() + disconnectSession: sinon.stub(), + retryPublishWithoutVideo: sinon.stub() }; wsCancelSpy = sinon.spy(); wsCloseSpy = sinon.spy(); wsMediaUpSpy = sinon.spy(); fakeWebsocket = { cancel: wsCancelSpy, @@ -130,16 +132,36 @@ describe("loop.store.ConversationStore", }); describe("#connectionFailure", function() { beforeEach(function() { store._websocket = fakeWebsocket; store.setStoreState({windowId: "42"}); }); + it("should retry publishing if on desktop, and in the videoMuted state", function() { + store._isDesktop = true; + + store.connectionFailure(new sharedActions.ConnectionFailure({ + reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA + })); + + sinon.assert.calledOnce(sdkDriver.retryPublishWithoutVideo); + }); + + it("should set videoMuted to try when retrying publishing", function() { + store._isDesktop = true; + + store.connectionFailure(new sharedActions.ConnectionFailure({ + reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA + })); + + expect(store.getStoreState().videoMuted).eql(true); + }); + it("should disconnect the session", function() { store.connectionFailure( new sharedActions.ConnectionFailure({reason: "fake"})); sinon.assert.calledOnce(sdkDriver.disconnectSession); }); it("should ensure the websocket is closed", function() {
--- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -5,16 +5,17 @@ var expect = chai.expect; describe("loop.OTSdkDriver", function () { "use strict"; var sharedActions = loop.shared.actions; var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS; var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES; var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES; + var sandbox; var dispatcher, driver, publisher, sdk, session, sessionData; var fakeLocalElement, fakeRemoteElement, fakeScreenElement; var publisherConfig, fakeEvent; beforeEach(function() { sandbox = sinon.sandbox.create(); @@ -47,19 +48,25 @@ describe("loop.OTSdkDriver", function () destroy: sinon.stub(), publishAudio: sinon.stub(), publishVideo: sinon.stub(), _: { switchAcquiredWindow: sinon.stub() } }, Backbone.Events); - sdk = { + sdk = _.extend({ initPublisher: sinon.stub().returns(publisher), initSession: sinon.stub().returns(session) + }, Backbone.Events); + + window.OT = { + ExceptionCodes: { + UNABLE_TO_PUBLISH: 1500 + } }; driver = new loop.OTSdkDriver({ dispatcher: dispatcher, sdk: sdk }); }); @@ -89,16 +96,47 @@ describe("loop.OTSdkDriver", function () publisherConfig: publisherConfig })); sinon.assert.calledOnce(sdk.initPublisher); sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig); }); }); + describe("#retryPublishWithoutVideo", function() { + beforeEach(function() { + sdk.initPublisher.returns(publisher); + + driver.setupStreamElements(new sharedActions.SetupStreamElements({ + getLocalElementFunc: function() {return fakeLocalElement;}, + getRemoteElementFunc: function() {return fakeRemoteElement;}, + publisherConfig: publisherConfig + })); + }); + + it("should make MediaStreamTrack.getSources return without a video source", function(done) { + driver.retryPublishWithoutVideo(); + + window.MediaStreamTrack.getSources(function(sources) { + expect(sources.some(function(src) { + return src.kind === "video"; + })).eql(false); + + done(); + }); + }); + + it("should call initPublisher", function() { + driver.retryPublishWithoutVideo(); + + sinon.assert.calledTwice(sdk.initPublisher); + sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig); + }); + }); + describe("#setMute", function() { beforeEach(function() { sdk.initPublisher.returns(publisher); dispatcher.dispatch(new sharedActions.SetupStreamElements({ getLocalElementFunc: function() {return fakeLocalElement;}, getRemoteElementFunc: function() {return fakeRemoteElement;}, publisherConfig: publisherConfig @@ -622,16 +660,42 @@ describe("loop.OTSdkDriver", function () describe("accessDialogOpened", function() { it("should prevent the default event behavior", function() { publisher.trigger("accessDialogOpened", fakeEvent); sinon.assert.calledOnce(fakeEvent.preventDefault); }); }); + + describe("exception", function() { + describe("Unable to publish (GetUserMedia)", function() { + it("should destroy the publisher", function() { + sdk.trigger("exception", { + code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message: "GetUserMedia" + }); + + sinon.assert.calledOnce(publisher.destroy); + }); + + it("should dispatch a ConnectionFailure action", function() { + sdk.trigger("exception", { + code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message: "GetUserMedia" + }); + + sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ConnectionFailure({ + reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA + })); + }); + }); + }); }); describe("Events (screenshare)", function() { beforeEach(function() { driver.connectSession(sessionData); driver.getScreenShareElementFunc = function() {};
--- a/browser/devtools/performance/test/browser_perf-overview-time-interval.js +++ b/browser/devtools/performance/test/browser_perf-overview-time-interval.js @@ -37,19 +37,19 @@ function spawnTest () { let notified = once(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED); OverviewView.setTimeInterval({ startTime: 10, endTime: 20 }); yield notified; let firstInterval = OverviewView.getTimeInterval(); info("First interval start time: " + firstInterval.startTime); info("First interval end time: " + firstInterval.endTime); - ok(firstInterval.startTime - 10 < Number.EPSILON, + is(Math.round(firstInterval.startTime), 10, "The interval's start time was properly set."); - ok(firstInterval.endTime - 20 < Number.EPSILON, + is(Math.round(firstInterval.endTime), 20, "The interval's end time was properly set."); // Get/set another time interval and make sure there's no event propagation. function fail() { ok(false, "The selection event should not have propagated."); }