diff -r 000000000000 -r 6474c204b198 dom/media/tests/mochitest/mediaStreamPlayback.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/media/tests/mochitest/mediaStreamPlayback.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,249 @@ +/* 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/. */ + +const TIMEUPDATE_TIMEOUT_LENGTH = 10000; +const ENDED_TIMEOUT_LENGTH = 10000; + +/* Time we wait for the canplaythrough event to fire + * Note: this needs to be at least 30s because the + * B2G emulator in VMs is really slow. */ +const CANPLAYTHROUGH_TIMEOUT_LENGTH = 60000; + +/** + * This class manages playback of a HTMLMediaElement with a MediaStream. + * When constructed by a caller, an object instance is created with + * a media element and a media stream object. + * + * @param {HTMLMediaElement} mediaElement the media element for playback + * @param {MediaStream} mediaStream the media stream used in + * the mediaElement for playback + */ +function MediaStreamPlayback(mediaElement, mediaStream) { + this.mediaElement = mediaElement; + this.mediaStream = mediaStream; +} + +MediaStreamPlayback.prototype = { + + /** + * Starts media with a media stream, runs it until a canplaythrough and + * timeupdate event fires, and stops the media. + * + * @param {Boolean} isResume specifies if this media element is being resumed + * from a previous run + * @param {Function} onSuccess the success callback if the media playback + * start and stop cycle completes successfully + * @param {Function} onError the error callback if the media playback + * start and stop cycle fails + */ + playMedia : function MSP_playMedia(isResume, onSuccess, onError) { + var self = this; + + this.startMedia(isResume, function() { + self.stopMediaElement(); + onSuccess(); + }, onError); + }, + + /** + * Starts the media with the associated stream. + * + * @param {Boolean} isResume specifies if the media element playback + * is being resumed from a previous run + * @param {Function} onSuccess the success function call back + * if media starts correctly + * @param {Function} onError the error function call back + * if media fails to start + */ + startMedia : function MSP_startMedia(isResume, onSuccess, onError) { + var self = this; + var canPlayThroughFired = false; + + // If we're initially running this media, check that the time is zero + if (!isResume) { + is(this.mediaStream.currentTime, 0, + "Before starting the media element, currentTime = 0"); + } + + /** + * Callback fired when the canplaythrough event is fired. We only + * run the logic of this function once, as this event can fire + * multiple times while a HTMLMediaStream is playing content from + * a real-time MediaStream. + */ + var canPlayThroughCallback = function() { + // Disable the canplaythrough event listener to prevent multiple calls + canPlayThroughFired = true; + self.mediaElement.removeEventListener('canplaythrough', + canPlayThroughCallback, false); + + is(self.mediaElement.paused, false, + "Media element should be playing"); + is(self.mediaElement.duration, Number.POSITIVE_INFINITY, + "Duration should be infinity"); + + // When the media element is playing with a real-time stream, we + // constantly switch between having data to play vs. queuing up data, + // so we can only check that the ready state is one of those two values + ok(self.mediaElement.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA || + self.mediaElement.readyState === HTMLMediaElement.HAVE_CURRENT_DATA, + "Ready state shall be HAVE_ENOUGH_DATA or HAVE_CURRENT_DATA"); + + is(self.mediaElement.seekable.length, 0, + "Seekable length shall be zero"); + is(self.mediaElement.buffered.length, 0, + "Buffered length shall be zero"); + + is(self.mediaElement.seeking, false, + "MediaElement is not seekable with MediaStream"); + ok(isNaN(self.mediaElement.startOffsetTime), + "Start offset time shall not be a number"); + is(self.mediaElement.loop, false, "Loop shall be false"); + is(self.mediaElement.preload, "", "Preload should not exist"); + is(self.mediaElement.src, "", "No src should be defined"); + is(self.mediaElement.currentSrc, "", + "Current src should still be an empty string"); + + var timeUpdateFired = false; + + var timeUpdateCallback = function() { + if (self.mediaStream.currentTime > 0 && + self.mediaElement.currentTime > 0) { + timeUpdateFired = true; + self.mediaElement.removeEventListener('timeupdate', + timeUpdateCallback, false); + onSuccess(); + } + }; + + // When timeupdate fires, we validate time has passed and move + // onto the success condition + self.mediaElement.addEventListener('timeupdate', timeUpdateCallback, + false); + + // If timeupdate doesn't fire in enough time, we fail the test + setTimeout(function() { + if (!timeUpdateFired) { + self.mediaElement.removeEventListener('timeupdate', + timeUpdateCallback, false); + onError("timeUpdate event never fired"); + } + }, TIMEUPDATE_TIMEOUT_LENGTH); + }; + + // Adds a listener intended to be fired when playback is available + // without further buffering. + this.mediaElement.addEventListener('canplaythrough', canPlayThroughCallback, + false); + + // Hooks up the media stream to the media element and starts playing it + this.mediaElement.mozSrcObject = this.mediaStream; + this.mediaElement.play(); + + // If canplaythrough doesn't fire in enough time, we fail the test + setTimeout(function() { + if (!canPlayThroughFired) { + self.mediaElement.removeEventListener('canplaythrough', + canPlayThroughCallback, false); + onError("canplaythrough event never fired"); + } + }, CANPLAYTHROUGH_TIMEOUT_LENGTH); + }, + + /** + * Stops the media with the associated stream. + * + * Precondition: The media stream and element should both be actively + * being played. + */ + stopMediaElement : function MSP_stopMediaElement() { + this.mediaElement.pause(); + this.mediaElement.mozSrcObject = null; + } +} + + +/** + * This class is basically the same as MediaStreamPlayback except + * ensures that the instance provided startMedia is a MediaStream. + * + * @param {HTMLMediaElement} mediaElement the media element for playback + * @param {LocalMediaStream} mediaStream the media stream used in + * the mediaElement for playback + */ +function LocalMediaStreamPlayback(mediaElement, mediaStream) { + ok(mediaStream instanceof LocalMediaStream, + "Stream should be a LocalMediaStream"); + MediaStreamPlayback.call(this, mediaElement, mediaStream); +} + +LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype, { + + /** + * Starts media with a media stream, runs it until a canplaythrough and + * timeupdate event fires, and calls stop() on the stream. + * + * @param {Boolean} isResume specifies if this media element is being resumed + * from a previous run + * @param {Function} onSuccess the success callback if the media element + * successfully fires ended on a stop() call + * on the stream + * @param {Function} onError the error callback if the media element fails + * to fire an ended callback on a stop() call + * on the stream + */ + playMediaWithStreamStop : { + value: function (isResume, onSuccess, onError) { + var self = this; + + this.startMedia(isResume, function() { + self.stopStreamInMediaPlayback(function() { + self.stopMediaElement(); + onSuccess(); + }, onError); + }, onError); + } + }, + + /** + * Stops the local media stream while it's currently in playback in + * a media element. + * + * Precondition: The media stream and element should both be actively + * being played. + * + * @param {Function} onSuccess the success callback if the media element + * fires an ended event from stop() being called + * @param {Function} onError the error callback if the media element + * fails to fire an ended event from stop() being + * called + */ + stopStreamInMediaPlayback : { + value: function (onSuccess, onError) { + var endedFired = false; + var self = this; + + /** + * Callback fired when the ended event fires when stop() is called on the + * stream. + */ + var endedCallback = function() { + endedFired = true; + self.mediaElement.removeEventListener('ended', endedCallback, false); + ok(true, "ended event successfully fired"); + onSuccess(); + }; + + this.mediaElement.addEventListener('ended', endedCallback, false); + this.mediaStream.stop(); + + // If ended doesn't fire in enough time, then we fail the test + setTimeout(function() { + if (!endedFired) { + onError("ended event never fired"); + } + }, ENDED_TIMEOUT_LENGTH); + } + } +});