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 +});