gecko/dom/media/tests/mochitest/mediaStreamPlayback.js

250 lines
9.1 KiB
JavaScript

/* 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 = 30000;
/* 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);
}
}
});