Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const TIMEUPDATE_TIMEOUT_LENGTH = 10000;
6 const ENDED_TIMEOUT_LENGTH = 10000;
8 /* Time we wait for the canplaythrough event to fire
9 * Note: this needs to be at least 30s because the
10 * B2G emulator in VMs is really slow. */
11 const CANPLAYTHROUGH_TIMEOUT_LENGTH = 60000;
13 /**
14 * This class manages playback of a HTMLMediaElement with a MediaStream.
15 * When constructed by a caller, an object instance is created with
16 * a media element and a media stream object.
17 *
18 * @param {HTMLMediaElement} mediaElement the media element for playback
19 * @param {MediaStream} mediaStream the media stream used in
20 * the mediaElement for playback
21 */
22 function MediaStreamPlayback(mediaElement, mediaStream) {
23 this.mediaElement = mediaElement;
24 this.mediaStream = mediaStream;
25 }
27 MediaStreamPlayback.prototype = {
29 /**
30 * Starts media with a media stream, runs it until a canplaythrough and
31 * timeupdate event fires, and stops the media.
32 *
33 * @param {Boolean} isResume specifies if this media element is being resumed
34 * from a previous run
35 * @param {Function} onSuccess the success callback if the media playback
36 * start and stop cycle completes successfully
37 * @param {Function} onError the error callback if the media playback
38 * start and stop cycle fails
39 */
40 playMedia : function MSP_playMedia(isResume, onSuccess, onError) {
41 var self = this;
43 this.startMedia(isResume, function() {
44 self.stopMediaElement();
45 onSuccess();
46 }, onError);
47 },
49 /**
50 * Starts the media with the associated stream.
51 *
52 * @param {Boolean} isResume specifies if the media element playback
53 * is being resumed from a previous run
54 * @param {Function} onSuccess the success function call back
55 * if media starts correctly
56 * @param {Function} onError the error function call back
57 * if media fails to start
58 */
59 startMedia : function MSP_startMedia(isResume, onSuccess, onError) {
60 var self = this;
61 var canPlayThroughFired = false;
63 // If we're initially running this media, check that the time is zero
64 if (!isResume) {
65 is(this.mediaStream.currentTime, 0,
66 "Before starting the media element, currentTime = 0");
67 }
69 /**
70 * Callback fired when the canplaythrough event is fired. We only
71 * run the logic of this function once, as this event can fire
72 * multiple times while a HTMLMediaStream is playing content from
73 * a real-time MediaStream.
74 */
75 var canPlayThroughCallback = function() {
76 // Disable the canplaythrough event listener to prevent multiple calls
77 canPlayThroughFired = true;
78 self.mediaElement.removeEventListener('canplaythrough',
79 canPlayThroughCallback, false);
81 is(self.mediaElement.paused, false,
82 "Media element should be playing");
83 is(self.mediaElement.duration, Number.POSITIVE_INFINITY,
84 "Duration should be infinity");
86 // When the media element is playing with a real-time stream, we
87 // constantly switch between having data to play vs. queuing up data,
88 // so we can only check that the ready state is one of those two values
89 ok(self.mediaElement.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA ||
90 self.mediaElement.readyState === HTMLMediaElement.HAVE_CURRENT_DATA,
91 "Ready state shall be HAVE_ENOUGH_DATA or HAVE_CURRENT_DATA");
93 is(self.mediaElement.seekable.length, 0,
94 "Seekable length shall be zero");
95 is(self.mediaElement.buffered.length, 0,
96 "Buffered length shall be zero");
98 is(self.mediaElement.seeking, false,
99 "MediaElement is not seekable with MediaStream");
100 ok(isNaN(self.mediaElement.startOffsetTime),
101 "Start offset time shall not be a number");
102 is(self.mediaElement.loop, false, "Loop shall be false");
103 is(self.mediaElement.preload, "", "Preload should not exist");
104 is(self.mediaElement.src, "", "No src should be defined");
105 is(self.mediaElement.currentSrc, "",
106 "Current src should still be an empty string");
108 var timeUpdateFired = false;
110 var timeUpdateCallback = function() {
111 if (self.mediaStream.currentTime > 0 &&
112 self.mediaElement.currentTime > 0) {
113 timeUpdateFired = true;
114 self.mediaElement.removeEventListener('timeupdate',
115 timeUpdateCallback, false);
116 onSuccess();
117 }
118 };
120 // When timeupdate fires, we validate time has passed and move
121 // onto the success condition
122 self.mediaElement.addEventListener('timeupdate', timeUpdateCallback,
123 false);
125 // If timeupdate doesn't fire in enough time, we fail the test
126 setTimeout(function() {
127 if (!timeUpdateFired) {
128 self.mediaElement.removeEventListener('timeupdate',
129 timeUpdateCallback, false);
130 onError("timeUpdate event never fired");
131 }
132 }, TIMEUPDATE_TIMEOUT_LENGTH);
133 };
135 // Adds a listener intended to be fired when playback is available
136 // without further buffering.
137 this.mediaElement.addEventListener('canplaythrough', canPlayThroughCallback,
138 false);
140 // Hooks up the media stream to the media element and starts playing it
141 this.mediaElement.mozSrcObject = this.mediaStream;
142 this.mediaElement.play();
144 // If canplaythrough doesn't fire in enough time, we fail the test
145 setTimeout(function() {
146 if (!canPlayThroughFired) {
147 self.mediaElement.removeEventListener('canplaythrough',
148 canPlayThroughCallback, false);
149 onError("canplaythrough event never fired");
150 }
151 }, CANPLAYTHROUGH_TIMEOUT_LENGTH);
152 },
154 /**
155 * Stops the media with the associated stream.
156 *
157 * Precondition: The media stream and element should both be actively
158 * being played.
159 */
160 stopMediaElement : function MSP_stopMediaElement() {
161 this.mediaElement.pause();
162 this.mediaElement.mozSrcObject = null;
163 }
164 }
167 /**
168 * This class is basically the same as MediaStreamPlayback except
169 * ensures that the instance provided startMedia is a MediaStream.
170 *
171 * @param {HTMLMediaElement} mediaElement the media element for playback
172 * @param {LocalMediaStream} mediaStream the media stream used in
173 * the mediaElement for playback
174 */
175 function LocalMediaStreamPlayback(mediaElement, mediaStream) {
176 ok(mediaStream instanceof LocalMediaStream,
177 "Stream should be a LocalMediaStream");
178 MediaStreamPlayback.call(this, mediaElement, mediaStream);
179 }
181 LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype, {
183 /**
184 * Starts media with a media stream, runs it until a canplaythrough and
185 * timeupdate event fires, and calls stop() on the stream.
186 *
187 * @param {Boolean} isResume specifies if this media element is being resumed
188 * from a previous run
189 * @param {Function} onSuccess the success callback if the media element
190 * successfully fires ended on a stop() call
191 * on the stream
192 * @param {Function} onError the error callback if the media element fails
193 * to fire an ended callback on a stop() call
194 * on the stream
195 */
196 playMediaWithStreamStop : {
197 value: function (isResume, onSuccess, onError) {
198 var self = this;
200 this.startMedia(isResume, function() {
201 self.stopStreamInMediaPlayback(function() {
202 self.stopMediaElement();
203 onSuccess();
204 }, onError);
205 }, onError);
206 }
207 },
209 /**
210 * Stops the local media stream while it's currently in playback in
211 * a media element.
212 *
213 * Precondition: The media stream and element should both be actively
214 * being played.
215 *
216 * @param {Function} onSuccess the success callback if the media element
217 * fires an ended event from stop() being called
218 * @param {Function} onError the error callback if the media element
219 * fails to fire an ended event from stop() being
220 * called
221 */
222 stopStreamInMediaPlayback : {
223 value: function (onSuccess, onError) {
224 var endedFired = false;
225 var self = this;
227 /**
228 * Callback fired when the ended event fires when stop() is called on the
229 * stream.
230 */
231 var endedCallback = function() {
232 endedFired = true;
233 self.mediaElement.removeEventListener('ended', endedCallback, false);
234 ok(true, "ended event successfully fired");
235 onSuccess();
236 };
238 this.mediaElement.addEventListener('ended', endedCallback, false);
239 this.mediaStream.stop();
241 // If ended doesn't fire in enough time, then we fail the test
242 setTimeout(function() {
243 if (!endedFired) {
244 onError("ended event never fired");
245 }
246 }, ENDED_TIMEOUT_LENGTH);
247 }
248 }
249 });