dom/media/tests/mochitest/mediaStreamPlayback.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/media/tests/mochitest/mediaStreamPlayback.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,249 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +const TIMEUPDATE_TIMEOUT_LENGTH = 10000;
     1.9 +const ENDED_TIMEOUT_LENGTH = 10000;
    1.10 +
    1.11 +/* Time we wait for the canplaythrough event to fire
    1.12 + * Note: this needs to be at least 30s because the
    1.13 + *       B2G emulator in VMs is really slow. */
    1.14 +const CANPLAYTHROUGH_TIMEOUT_LENGTH = 60000;
    1.15 +
    1.16 +/**
    1.17 + * This class manages playback of a HTMLMediaElement with a MediaStream.
    1.18 + * When constructed by a caller, an object instance is created with
    1.19 + * a media element and a media stream object.
    1.20 + *
    1.21 + * @param {HTMLMediaElement} mediaElement the media element for playback
    1.22 + * @param {MediaStream} mediaStream the media stream used in
    1.23 + *                                  the mediaElement for playback
    1.24 + */
    1.25 +function MediaStreamPlayback(mediaElement, mediaStream) {
    1.26 +  this.mediaElement = mediaElement;
    1.27 +  this.mediaStream = mediaStream;
    1.28 +}
    1.29 +
    1.30 +MediaStreamPlayback.prototype = {
    1.31 +
    1.32 +  /**
    1.33 +   * Starts media with a media stream, runs it until a canplaythrough and
    1.34 +   * timeupdate event fires, and stops the media.
    1.35 +   *
    1.36 +   * @param {Boolean} isResume specifies if this media element is being resumed
    1.37 +   *                           from a previous run
    1.38 +   * @param {Function} onSuccess the success callback if the media playback
    1.39 +   *                             start and stop cycle completes successfully
    1.40 +   * @param {Function} onError the error callback if the media playback
    1.41 +   *                           start and stop cycle fails
    1.42 +   */
    1.43 +  playMedia : function MSP_playMedia(isResume, onSuccess, onError) {
    1.44 +    var self = this;
    1.45 +
    1.46 +    this.startMedia(isResume, function() {
    1.47 +      self.stopMediaElement();
    1.48 +      onSuccess();
    1.49 +    }, onError);
    1.50 +  },
    1.51 +
    1.52 +  /**
    1.53 +   * Starts the media with the associated stream.
    1.54 +   *
    1.55 +   * @param {Boolean} isResume specifies if the media element playback
    1.56 +   *                           is being resumed from a previous run
    1.57 +   * @param {Function} onSuccess the success function call back
    1.58 +   *                             if media starts correctly
    1.59 +   * @param {Function} onError the error function call back
    1.60 +   *                           if media fails to start
    1.61 +   */
    1.62 +  startMedia : function MSP_startMedia(isResume, onSuccess, onError) {
    1.63 +    var self = this;
    1.64 +    var canPlayThroughFired = false;
    1.65 +
    1.66 +    // If we're initially running this media, check that the time is zero
    1.67 +    if (!isResume) {
    1.68 +      is(this.mediaStream.currentTime, 0,
    1.69 +         "Before starting the media element, currentTime = 0");
    1.70 +    }
    1.71 +
    1.72 +    /**
    1.73 +     * Callback fired when the canplaythrough event is fired. We only
    1.74 +     * run the logic of this function once, as this event can fire
    1.75 +     * multiple times while a HTMLMediaStream is playing content from
    1.76 +     * a real-time MediaStream.
    1.77 +     */
    1.78 +    var canPlayThroughCallback = function() {
    1.79 +      // Disable the canplaythrough event listener to prevent multiple calls
    1.80 +      canPlayThroughFired = true;
    1.81 +      self.mediaElement.removeEventListener('canplaythrough',
    1.82 +        canPlayThroughCallback, false);
    1.83 +
    1.84 +      is(self.mediaElement.paused, false,
    1.85 +        "Media element should be playing");
    1.86 +      is(self.mediaElement.duration, Number.POSITIVE_INFINITY,
    1.87 +        "Duration should be infinity");
    1.88 +
    1.89 +      // When the media element is playing with a real-time stream, we
    1.90 +      // constantly switch between having data to play vs. queuing up data,
    1.91 +      // so we can only check that the ready state is one of those two values
    1.92 +      ok(self.mediaElement.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA ||
    1.93 +         self.mediaElement.readyState === HTMLMediaElement.HAVE_CURRENT_DATA,
    1.94 +         "Ready state shall be HAVE_ENOUGH_DATA or HAVE_CURRENT_DATA");
    1.95 +
    1.96 +      is(self.mediaElement.seekable.length, 0,
    1.97 +         "Seekable length shall be zero");
    1.98 +      is(self.mediaElement.buffered.length, 0,
    1.99 +         "Buffered length shall be zero");
   1.100 +
   1.101 +      is(self.mediaElement.seeking, false,
   1.102 +         "MediaElement is not seekable with MediaStream");
   1.103 +      ok(isNaN(self.mediaElement.startOffsetTime),
   1.104 +         "Start offset time shall not be a number");
   1.105 +      is(self.mediaElement.loop, false, "Loop shall be false");
   1.106 +      is(self.mediaElement.preload, "", "Preload should not exist");
   1.107 +      is(self.mediaElement.src, "", "No src should be defined");
   1.108 +      is(self.mediaElement.currentSrc, "",
   1.109 +         "Current src should still be an empty string");
   1.110 +
   1.111 +      var timeUpdateFired = false;
   1.112 +
   1.113 +      var timeUpdateCallback = function() {
   1.114 +        if (self.mediaStream.currentTime > 0 &&
   1.115 +            self.mediaElement.currentTime > 0) {
   1.116 +          timeUpdateFired = true;
   1.117 +          self.mediaElement.removeEventListener('timeupdate',
   1.118 +            timeUpdateCallback, false);
   1.119 +          onSuccess();
   1.120 +        }
   1.121 +      };
   1.122 +
   1.123 +      // When timeupdate fires, we validate time has passed and move
   1.124 +      // onto the success condition
   1.125 +      self.mediaElement.addEventListener('timeupdate', timeUpdateCallback,
   1.126 +        false);
   1.127 +
   1.128 +      // If timeupdate doesn't fire in enough time, we fail the test
   1.129 +      setTimeout(function() {
   1.130 +        if (!timeUpdateFired) {
   1.131 +          self.mediaElement.removeEventListener('timeupdate',
   1.132 +            timeUpdateCallback, false);
   1.133 +          onError("timeUpdate event never fired");
   1.134 +        }
   1.135 +      }, TIMEUPDATE_TIMEOUT_LENGTH);
   1.136 +    };
   1.137 +
   1.138 +    // Adds a listener intended to be fired when playback is available
   1.139 +    // without further buffering.
   1.140 +    this.mediaElement.addEventListener('canplaythrough', canPlayThroughCallback,
   1.141 +      false);
   1.142 +
   1.143 +    // Hooks up the media stream to the media element and starts playing it
   1.144 +    this.mediaElement.mozSrcObject = this.mediaStream;
   1.145 +    this.mediaElement.play();
   1.146 +
   1.147 +    // If canplaythrough doesn't fire in enough time, we fail the test
   1.148 +    setTimeout(function() {
   1.149 +      if (!canPlayThroughFired) {
   1.150 +        self.mediaElement.removeEventListener('canplaythrough',
   1.151 +          canPlayThroughCallback, false);
   1.152 +        onError("canplaythrough event never fired");
   1.153 +      }
   1.154 +    }, CANPLAYTHROUGH_TIMEOUT_LENGTH);
   1.155 +  },
   1.156 +
   1.157 +  /**
   1.158 +   * Stops the media with the associated stream.
   1.159 +   *
   1.160 +   * Precondition: The media stream and element should both be actively
   1.161 +   *               being played.
   1.162 +   */
   1.163 +  stopMediaElement : function MSP_stopMediaElement() {
   1.164 +    this.mediaElement.pause();
   1.165 +    this.mediaElement.mozSrcObject = null;
   1.166 +  }
   1.167 +}
   1.168 +
   1.169 +
   1.170 +/**
   1.171 + * This class is basically the same as MediaStreamPlayback except
   1.172 + * ensures that the instance provided startMedia is a MediaStream.
   1.173 + *
   1.174 + * @param {HTMLMediaElement} mediaElement the media element for playback
   1.175 + * @param {LocalMediaStream} mediaStream the media stream used in
   1.176 + *                                       the mediaElement for playback
   1.177 + */
   1.178 +function LocalMediaStreamPlayback(mediaElement, mediaStream) {
   1.179 +  ok(mediaStream instanceof LocalMediaStream,
   1.180 +     "Stream should be a LocalMediaStream");
   1.181 +  MediaStreamPlayback.call(this, mediaElement, mediaStream);
   1.182 +}
   1.183 +
   1.184 +LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype, {
   1.185 +
   1.186 +  /**
   1.187 +   * Starts media with a media stream, runs it until a canplaythrough and
   1.188 +   * timeupdate event fires, and calls stop() on the stream.
   1.189 +   *
   1.190 +   * @param {Boolean} isResume specifies if this media element is being resumed
   1.191 +   *                           from a previous run
   1.192 +   * @param {Function} onSuccess the success callback if the media element
   1.193 +   *                             successfully fires ended on a stop() call
   1.194 +   *                             on the stream
   1.195 +   * @param {Function} onError the error callback if the media element fails
   1.196 +   *                           to fire an ended callback on a stop() call
   1.197 +   *                           on the stream
   1.198 +   */
   1.199 +  playMediaWithStreamStop : {
   1.200 +    value: function (isResume, onSuccess, onError) {
   1.201 +      var self = this;
   1.202 +
   1.203 +      this.startMedia(isResume, function() {
   1.204 +        self.stopStreamInMediaPlayback(function() {
   1.205 +          self.stopMediaElement();
   1.206 +          onSuccess();
   1.207 +        }, onError);
   1.208 +      }, onError);
   1.209 +    }
   1.210 +  },
   1.211 +
   1.212 +  /**
   1.213 +   * Stops the local media stream while it's currently in playback in
   1.214 +   * a media element.
   1.215 +   *
   1.216 +   * Precondition: The media stream and element should both be actively
   1.217 +   *               being played.
   1.218 +   *
   1.219 +   * @param {Function} onSuccess the success callback if the media element
   1.220 +   *                             fires an ended event from stop() being called
   1.221 +   * @param {Function} onError the error callback if the media element
   1.222 +   *                           fails to fire an ended event from stop() being
   1.223 +   *                           called
   1.224 +   */
   1.225 +  stopStreamInMediaPlayback : {
   1.226 +    value: function (onSuccess, onError) {
   1.227 +      var endedFired = false;
   1.228 +      var self = this;
   1.229 +
   1.230 +      /**
   1.231 +       * Callback fired when the ended event fires when stop() is called on the
   1.232 +       * stream.
   1.233 +       */
   1.234 +      var endedCallback = function() {
   1.235 +        endedFired = true;
   1.236 +        self.mediaElement.removeEventListener('ended', endedCallback, false);
   1.237 +        ok(true, "ended event successfully fired");
   1.238 +        onSuccess();
   1.239 +      };
   1.240 +
   1.241 +      this.mediaElement.addEventListener('ended', endedCallback, false);
   1.242 +      this.mediaStream.stop();
   1.243 +
   1.244 +      // If ended doesn't fire in enough time, then we fail the test
   1.245 +      setTimeout(function() {
   1.246 +        if (!endedFired) {
   1.247 +          onError("ended event never fired");
   1.248 +        }
   1.249 +      }, ENDED_TIMEOUT_LENGTH);
   1.250 +    }
   1.251 +  }
   1.252 +});

mercurial