summaryrefslogtreecommitdiffstats
path: root/dom/media/tests/mochitest/mediaStreamPlayback.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/tests/mochitest/mediaStreamPlayback.js')
-rw-r--r--dom/media/tests/mochitest/mediaStreamPlayback.js252
1 files changed, 252 insertions, 0 deletions
diff --git a/dom/media/tests/mochitest/mediaStreamPlayback.js b/dom/media/tests/mochitest/mediaStreamPlayback.js
new file mode 100644
index 000000000..a72d65f3f
--- /dev/null
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -0,0 +1,252 @@
+/* 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 ENDED_TIMEOUT_LENGTH = 30000;
+
+/* The time we wait depends primarily on the canplaythrough event firing
+ * Note: this needs to be at least 30s because the
+ * B2G emulator in VMs is really slow. */
+const VERIFYPLAYING_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 element with a media stream, runs it until a canplaythrough
+ * and timeupdate event fires, and calls stop() on all its tracks.
+ *
+ * @param {Boolean} isResume specifies if this media element is being resumed
+ * from a previous run
+ */
+ playMedia : function(isResume) {
+ this.startMedia(isResume);
+ return this.verifyPlaying()
+ .then(() => this.stopTracksForStreamInMediaPlayback())
+ .then(() => this.detachFromMediaElement());
+ },
+
+ /**
+ * Stops the local media stream's tracks while it's currently in playback in
+ * a media element.
+ *
+ * Precondition: The media stream and element should both be actively
+ * being played. All the stream's tracks must be local.
+ */
+ stopTracksForStreamInMediaPlayback : function () {
+ var elem = this.mediaElement;
+ return Promise.all([
+ haveEvent(elem, "ended", wait(ENDED_TIMEOUT_LENGTH, new Error("Timeout"))),
+ ...this.mediaStream.getTracks().map(t => (t.stop(), haveNoEvent(t, "ended")))
+ ]);
+ },
+
+ /**
+ * Starts media with a media stream, runs it until a canplaythrough and
+ * timeupdate event fires, and detaches from the element without stopping media.
+ *
+ * @param {Boolean} isResume specifies if this media element is being resumed
+ * from a previous run
+ */
+ playMediaWithoutStoppingTracks : function(isResume) {
+ this.startMedia(isResume);
+ return this.verifyPlaying()
+ .then(() => this.detachFromMediaElement());
+ },
+
+ /**
+ * Starts the media with the associated stream.
+ *
+ * @param {Boolean} isResume specifies if the media element playback
+ * is being resumed from a previous run
+ */
+ startMedia : function(isResume) {
+
+ // If we're playing media element for the first time, check that time is zero.
+ if (!isResume) {
+ is(this.mediaElement.currentTime, 0,
+ "Before starting the media element, currentTime = 0");
+ }
+ this.canPlayThroughFired = listenUntil(this.mediaElement, 'canplaythrough',
+ () => true);
+
+ // Hooks up the media stream to the media element and starts playing it
+ this.mediaElement.srcObject = this.mediaStream;
+ this.mediaElement.play();
+ },
+
+ /**
+ * Verifies that media is playing.
+ */
+ verifyPlaying : function() {
+ var lastStreamTime = this.mediaStream.currentTime;
+ var lastElementTime = this.mediaElement.currentTime;
+
+ var mediaTimeProgressed = listenUntil(this.mediaElement, 'timeupdate',
+ () => this.mediaStream.currentTime > lastStreamTime &&
+ this.mediaElement.currentTime > lastElementTime);
+
+ return timeout(Promise.all([this.canPlayThroughFired, mediaTimeProgressed]),
+ VERIFYPLAYING_TIMEOUT_LENGTH, "verifyPlaying timed out")
+ .then(() => {
+ is(this.mediaElement.paused, false,
+ "Media element should be playing");
+ is(this.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(this.mediaElement.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA ||
+ this.mediaElement.readyState === HTMLMediaElement.HAVE_CURRENT_DATA,
+ "Ready state shall be HAVE_ENOUGH_DATA or HAVE_CURRENT_DATA");
+
+ is(this.mediaElement.seekable.length, 0,
+ "Seekable length shall be zero");
+ is(this.mediaElement.buffered.length, 0,
+ "Buffered length shall be zero");
+
+ is(this.mediaElement.seeking, false,
+ "MediaElement is not seekable with MediaStream");
+ ok(isNaN(this.mediaElement.startOffsetTime),
+ "Start offset time shall not be a number");
+ is(this.mediaElement.loop, false, "Loop shall be false");
+ is(this.mediaElement.preload, "", "Preload should not exist");
+ is(this.mediaElement.src, "", "No src should be defined");
+ is(this.mediaElement.currentSrc, "",
+ "Current src should still be an empty string");
+ });
+ },
+
+ /**
+ * Detaches from the element without stopping the media.
+ *
+ * Precondition: The media stream and element should both be actively
+ * being played.
+ */
+ detachFromMediaElement : function() {
+ this.mediaElement.pause();
+ this.mediaElement.srcObject = 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, {
+
+ /**
+ * DEPRECATED - MediaStream.stop() is going away. Use MediaStreamTrack.stop()!
+ *
+ * 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
+ */
+ playMediaWithDeprecatedStreamStop : {
+ value: function(isResume) {
+ this.startMedia(isResume);
+ return this.verifyPlaying()
+ .then(() => this.deprecatedStopStreamInMediaPlayback())
+ .then(() => this.detachFromMediaElement());
+ }
+ },
+
+ /**
+ * DEPRECATED - MediaStream.stop() is going away. Use MediaStreamTrack.stop()!
+ *
+ * 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.
+ *
+ */
+ deprecatedStopStreamInMediaPlayback : {
+ value: function () {
+ return new Promise((resolve, reject) => {
+ /**
+ * Callback fired when the ended event fires when stop() is called on the
+ * stream.
+ */
+ var endedCallback = () => {
+ this.mediaElement.removeEventListener('ended', endedCallback, false);
+ ok(true, "ended event successfully fired");
+ resolve();
+ };
+
+ this.mediaElement.addEventListener('ended', endedCallback, false);
+ this.mediaStream.stop();
+
+ // If ended doesn't fire in enough time, then we fail the test
+ setTimeout(() => {
+ reject(new Error("ended event never fired"));
+ }, ENDED_TIMEOUT_LENGTH);
+ });
+ }
+ }
+});
+
+// haxx to prevent SimpleTest from failing at window.onload
+function addLoadEvent() {}
+
+var scriptsReady = Promise.all([
+ "/tests/SimpleTest/SimpleTest.js",
+ "head.js"
+].map(script => {
+ var el = document.createElement("script");
+ el.src = script;
+ document.head.appendChild(el);
+ return new Promise(r => el.onload = r);
+}));
+
+function createHTML(options) {
+ return scriptsReady.then(() => realCreateHTML(options));
+}
+
+var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
+
+// noGum - Helper to detect whether active guM tracks still exist.
+//
+// It relies on the fact that, by spec, device labels from enumerateDevices are
+// only visible during active gum calls. They're also visible when persistent
+// permissions are granted, so turn off media.navigator.permission.disabled
+// (which is normally on otherwise in our tests). Lastly, we must turn on
+// media.navigator.permission.fake otherwise fake devices don't count as active.
+
+var noGum = () => pushPrefs(["media.navigator.permission.disabled", false],
+ ["media.navigator.permission.fake", true])
+ .then(() => navigator.mediaDevices.enumerateDevices())
+ .then(([device]) => device &&
+ is(device.label, "", "Test must leave no active gUM streams behind."));
+
+var runTest = testFunction => scriptsReady
+ .then(() => runTestWhenReady(testFunction))
+ .then(() => noGum())
+ .then(() => finish());