1 // Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 2 // 3 // Use of this source code is governed by a BSD-style license 4 // that can be found in the LICENSE file in the root of the source 5 // tree. An additional intellectual property rights grant can be found 6 // in the file PATENTS. All contributing project authors may 7 // be found in the AUTHORS file in the root of the source tree. 8 9 setup({timeout:5000}); 10 11 // Helper functions to minimize code duplication. 12 function failedCallback(test) { 13 return test.step_func(function (error) { 14 assert_unreached('Should not get an error callback'); 15 }); 16 } 17 function invokeGetUserMedia(test, okCallback) { 18 navigator.getUserMedia({ video: true, audio: true }, okCallback, 19 failedCallback(test)); 20 } 21 22 function createInvisibleVideoTag() { 23 var video = document.createElement('video'); 24 video.autoplay = true; 25 video.style.display = 'none'; 26 document.body.appendChild(video); 27 return video; 28 } 29 30 // 4.2 MediaStream. 31 var mediaStreamTest = async_test('4.2 MediaStream'); 32 33 function verifyMediaStream(stream) { 34 // TODO(kjellander): Add checks for default values where applicable. 35 test(function () { 36 assert_own_property(stream, 'id'); 37 assert_true(typeof stream.id === 'string'); 38 assert_readonly(stream, 'id'); 39 }, '[MediaStream] id attribute'); 40 41 test(function () { 42 assert_inherits(stream, 'getAudioTracks'); 43 assert_true(typeof stream.getAudioTracks === 'function'); 44 }, '[MediaStream] getAudioTracks function'); 45 46 test(function () { 47 assert_inherits(stream, 'getVideoTracks'); 48 assert_true(typeof stream.getVideoTracks === 'function'); 49 }, '[MediaStream] getVideoTracks function'); 50 51 test(function () { 52 assert_inherits(stream, 'getTrackById'); 53 assert_true(typeof stream.getTrackById === 'function'); 54 }, '[MediaStream] getTrackById function'); 55 56 test(function () { 57 assert_inherits(stream, 'addTrack'); 58 assert_true(typeof stream.addTrack === 'function'); 59 }, '[MediaStream] addTrack function'); 60 61 test(function () { 62 assert_inherits(stream, 'removeTrack'); 63 assert_true(typeof stream.removeTrack === 'function'); 64 }, '[MediaStream] removeTrack function'); 65 66 test(function () { 67 assert_inherits(stream, 'clone'); 68 assert_true(typeof stream.clone === 'function'); 69 }, '[MediaStream] clone function'); 70 71 test(function () { 72 assert_own_property(stream, 'active'); 73 assert_true(typeof stream.active === 'boolean'); 74 assert_readonly(stream, 'active'); 75 }, '[MediaStream] active attribute'); 76 77 test(function () { 78 assert_own_property(stream, 'onactive'); 79 assert_true(stream.onactive === null); 80 }, '[MediaStream] onactive EventHandler'); 81 82 test(function () { 83 assert_own_property(stream, 'oninactive'); 84 assert_true(stream.oninactive === null); 85 }, '[MediaStream] oninactive EventHandler'); 86 87 test(function () { 88 assert_own_property(stream, 'onaddtrack'); 89 assert_true(stream.onaddtrack === null); 90 }, '[MediaStream] onaddtrack EventHandler'); 91 92 test(function () { 93 assert_own_property(stream, 'onremovetrack'); 94 assert_true(stream.onremovetrack === null); 95 }, '[MediaStream] onremovetrack EventHandler'); 96 } 97 98 mediaStreamTest.step(function() { 99 var okCallback = mediaStreamTest.step_func(function (stream) { 100 verifyMediaStream(stream); 101 var videoTracks = stream.getVideoTracks(); 102 assert_true(videoTracks.length > 0); 103 mediaStreamTest.done(); 104 }); 105 106 invokeGetUserMedia(mediaStreamTest, okCallback); 107 }); 108 109 var mediaStreamCallbacksTest = async_test('4.2.2 MediaStream callbacks'); 110 111 mediaStreamCallbacksTest.step(function() { 112 var addCallbackCalled = false; 113 var onAddTrackCallback = mediaStreamCallbacksTest.step_func(function (event) { 114 assert_true(event.track instanceof MediaStreamTrack); 115 addCallbackCalled = true; 116 }); 117 var onRemoveTrackCallback = 118 mediaStreamCallbacksTest.step_func(function (event) { 119 assert_true(event.track instanceof MediaStreamTrack); 120 assert_true(addCallbackCalled, 'Add should have been called after remove.'); 121 mediaStreamCallbacksTest.done(); 122 }); 123 var okCallback = mediaStreamCallbacksTest.step_func(function (stream) { 124 var videoTracks = stream.getVideoTracks(); 125 126 // Verify event handlers are working. 127 stream.onaddtrack = onAddTrackCallback; 128 stream.onremovetrack = onRemoveTrackCallback; 129 stream.removeTrack(videoTracks[0]); 130 stream.addTrack(videoTracks[0]); 131 }); 132 133 invokeGetUserMedia(mediaStreamCallbacksTest, okCallback); 134 }); 135 136 // TODO(phoglund): add test for onactive/oninactive. 137 138 // 4.3 MediaStreamTrack. 139 var mediaStreamTrackTest = async_test('4.3 MediaStreamTrack'); 140 141 function verifyTrack(type, track) { 142 test(function () { 143 assert_own_property(track, 'kind'); 144 assert_readonly(track, 'kind'); 145 assert_true(typeof track.kind === 'string', 146 'kind is an object (DOMString)'); 147 }, '[MediaStreamTrack (' + type + ')] kind attribute'); 148 149 test(function () { 150 assert_own_property(track, 'id'); 151 assert_readonly(track, 'id'); 152 assert_true(typeof track.id === 'string', 153 'id is an object (DOMString)'); 154 }, '[MediaStreamTrack (' + type + ')] id attribute'); 155 156 test(function () { 157 assert_own_property(track, 'label'); 158 assert_readonly(track, 'label'); 159 assert_true(typeof track.label === 'string', 160 'label is an object (DOMString)'); 161 }, '[MediaStreamTrack (' + type + ')] label attribute'); 162 163 test(function () { 164 assert_own_property(track, 'enabled'); 165 assert_true(typeof track.enabled === 'boolean'); 166 assert_true(track.enabled, 'enabled property must be true initially'); 167 }, '[MediaStreamTrack (' + type + ')] enabled attribute'); 168 169 test(function () { 170 assert_own_property(track, 'muted'); 171 assert_readonly(track, 'muted'); 172 assert_true(typeof track.muted === 'boolean'); 173 assert_false(track.muted, 'muted property must be false initially'); 174 }, '[MediaStreamTrack (' + type + ')] muted attribute'); 175 176 test(function () { 177 assert_own_property(track, 'onmute'); 178 assert_true(track.onmute === null); 179 }, '[MediaStreamTrack (' + type + ')] onmute EventHandler'); 180 181 test(function () { 182 assert_own_property(track, 'onunmute'); 183 assert_true(track.onunmute === null); 184 }, '[MediaStreamTrack (' + type + ')] onunmute EventHandler'); 185 186 test(function () { 187 assert_own_property(track, '_readonly'); 188 assert_readonly(track, '_readonly'); 189 assert_true(typeof track._readonly === 'boolean'); 190 }, '[MediaStreamTrack (' + type + ')] _readonly attribute'); 191 192 test(function () { 193 assert_own_property(track, 'remote'); 194 assert_readonly(track, 'remote'); 195 assert_true(typeof track.remote === 'boolean'); 196 }, '[MediaStreamTrack (' + type + ')] remote attribute'); 197 198 test(function () { 199 assert_own_property(track, 'readyState'); 200 assert_readonly(track, 'readyState'); 201 assert_true(typeof track.readyState === 'string'); 202 // TODO(kjellander): verify the initial state. 203 }, '[MediaStreamTrack (' + type + ')] readyState attribute'); 204 205 test(function () { 206 assert_own_property(track, 'onstarted'); 207 assert_true(track.onstarted === null); 208 }, '[MediaStreamTrack (' + type + ')] onstarted EventHandler'); 209 210 test(function () { 211 assert_own_property(track, 'onended'); 212 assert_true(track.onended === null); 213 }, '[MediaStreamTrack (' + type + ')] onended EventHandler'); 214 215 test(function () { 216 assert_inherits(track, 'getNativeSettings'); 217 assert_true(typeof track.capabilities === 'function'); 218 }, '[MediaStreamTrack (' + type + ')]: getNativeSettings function'); 219 220 test(function () { 221 assert_inherits(track, 'clone'); 222 assert_true(typeof track.clone === 'function'); 223 }, '[MediaStreamTrack (' + type + ')] clone function'); 224 225 test(function () { 226 assert_inherits(track, 'stop'); 227 assert_true(typeof track.stop === 'function'); 228 }, '[MediaStreamTrack (' + type + ')] stop function'); 229 230 test(function () { 231 assert_inherits(track, 'getCapabilities'); 232 assert_true(typeof track.capabilities === 'function'); 233 }, '[MediaStreamTrack (' + type + ')]: getCapabilities function'); 234 235 test(function () { 236 assert_inherits(track, 'getConstraints'); 237 assert_true(typeof track.constraints === 'function'); 238 }, '[MediaStreamTrack (' + type + ')]: getConstraints function'); 239 240 test(function () { 241 assert_inherits(track, 'getSettings'); 242 assert_true(typeof track.constraints === 'function'); 243 }, '[MediaStreamTrack (' + type + ')]: getSettings function'); 244 245 test(function () { 246 assert_inherits(track, 'applyConstraints'); 247 assert_true(typeof track.applyConstraints === 'function'); 248 }, '[MediaStreamTrack (' + type + ')]: applyConstraints function'); 249 }; 250 251 mediaStreamTrackTest.step(function() { 252 var okCallback = mediaStreamTrackTest.step_func(function (stream) { 253 verifyTrack('audio', stream.getAudioTracks()[0]); 254 verifyTrack('video', stream.getVideoTracks()[0]); 255 mediaStreamTrackTest.done(); 256 }); 257 invokeGetUserMedia(mediaStreamTrackTest, okCallback); 258 }); 259 260 mediaStreamTrackTest.step(function() { 261 var okCallback = mediaStreamTrackTest.step_func(function (stream) { 262 // Verify event handlers are working. 263 var track = stream.getVideoTracks()[0]; 264 track.onended = onEndedCallback 265 track.stop(); 266 mediaStreamTrackTest.done(); 267 }); 268 var onEndedCallback = mediaStreamTrackTest.step_func(function () { 269 assert_true(track.ended); 270 mediaStreamTrackTest.done(); 271 }); 272 invokeGetUserMedia(mediaStreamTrackTest, okCallback); 273 }); 274 275 // 4.4 MediaStreamTrackEvent tests. 276 var mediaStreamTrackEventTest = async_test('4.4 MediaStreamTrackEvent'); 277 mediaStreamTrackEventTest.step(function() { 278 var okCallback = mediaStreamTrackEventTest.step_func(function (stream) { 279 // TODO(kjellander): verify attributes. 280 mediaStreamTrackEventTest.done(); 281 }); 282 invokeGetUserMedia(mediaStreamTrackEventTest, okCallback); 283 }); 284 285 // 6. Media streams as media elements. 286 287 var playingInMediaElementTest = async_test( 288 '6.2 Loading and Playing a MediaStream in a Media Element'); 289 playingInMediaElementTest.step(function() { 290 var video = createInvisibleVideoTag(); 291 292 var okCallback = playingInMediaElementTest.step_func(function (stream) { 293 video.onplay = playingInMediaElementTest.step_func(function() { 294 // This depends on what webcam we're actually running with, but the 295 // resolution should at least be greater than or equal to QVGA. 296 assert_greater_than_equal(video.videoWidth, 320); 297 assert_greater_than_equal(video.videoHeight, 240); 298 299 playingInMediaElementTest.done(); 300 }); 301 video.srcObject = stream; 302 }); 303 invokeGetUserMedia(playingInMediaElementTest, okCallback); 304 }); 305 306 // Verifies a media element track (for instance belonging to a video tag) 307 // after it has been assigned a media stream. 308 function verifyOneMediaElementTrack(track, correspondingMediaStreamTrack) { 309 assert_equals(track.id, correspondingMediaStreamTrack.id); 310 assert_equals(track.kind, 'main'); 311 assert_equals(track.label, correspondingMediaStreamTrack.label); 312 assert_equals(track.language, ''); 313 } 314 315 var setsUpMediaTracksRightTest = async_test( 316 '6.2 Sets up <video> audio and video tracks right'); 317 setsUpMediaTracksRightTest.step(function() { 318 var video = createInvisibleVideoTag(); 319 320 var okCallback = setsUpMediaTracksRightTest.step_func(function (stream) { 321 video.onplay = setsUpMediaTracksRightTest.step_func(function() { 322 // Verify the requirements on the video tag's streams as outlined in 6.2. 323 // There could be any number of tracks depending on what device we have 324 // connected, so verify all of them. There should be at least one of audio 325 // and video each though. 326 assert_inherits(video, 'videoTracks', 327 'Browser missing videoTracks support on media elements.'); 328 assert_readonly(video, 'videoTracks'); 329 assert_greater_than_equal(video.videoTracks.length, 1); 330 assert_equals(video.videoTracks.length, stream.getVideoTracks().length); 331 332 for (var i = 0; i < video.videoTracks.length; i++) { 333 verifyOneMediaElementTrack(video.videoTracks[i], 334 stream.getVideoTracks()[i]); 335 } 336 337 assert_inherits(video, 'audioTracks', 338 'Browser missing audioTracks support on media elements.'); 339 assert_readonly(video, 'audioTracks'); 340 assert_greater_than_equal(video.audioTracks.length, 1); 341 assert_equals(video.audioTracks.length, stream.getAudioTracks().length); 342 343 for (var i = 0; i < video.audioTracks.length; i++) { 344 verifyOneMediaElementTrack(audio.audioTracks[i], 345 stream.getAudioTracks()[i]); 346 } 347 348 setsUpMediaTracksRightTest.done(); 349 }); 350 video.srcObject = stream; 351 }); 352 invokeGetUserMedia(setsUpMediaTracksRightTest, okCallback); 353 }); 354 355 var mediaElementsTest = 356 async_test('6.3 Media Element Attributes when Playing a MediaStream'); 357 358 function verifyVideoTagWithStream(videoTag) { 359 test(function () { 360 assert_equals(videoTag.currentSrc, ''); 361 }, '[Video tag] currentSrc attribute'); 362 363 test(function () { 364 assert_equals(videoTag.preload, 'none'); 365 }, '[Video tag] preload attribute'); 366 367 test(function () { 368 assert_equals(videoTag.buffered.length, 0); 369 }, '[Video tag] buffered attribute'); 370 371 test(function () { 372 // Where 1 is NETWORK_IDLE. 373 assert_equals(videoTag.networkState, 1); 374 }, '[Video tag] networkState attribute'); 375 376 test(function () { 377 // 0 is HAVE_NOTHING, 4 is HAVE_ENOUGH_DATA. 378 assert_true(videoTag.readyState == 0 || videoTag.readyState == 4); 379 }, '[Video tag] readyState attribute'); 380 381 test(function () { 382 assert_true(videoTag.currentTime >= 0); 383 assert_throws( 384 'InvalidStateError', function () { videoTag.currentTime = 1234; }, 385 'Attempts to modify currentTime shall throw InvalidStateError'); 386 }, '[Video tag] currentTime attribute'); 387 388 test(function () { 389 assert_equals(videoTag.duration, Infinity, 'videoTag.duration'); 390 }, '[Video tag] duration attribute'); 391 392 test(function () { 393 assert_false(videoTag.seeking, 'videoTag.seeking'); 394 }, '[Video tag] seeking attribute'); 395 396 test(function () { 397 assert_equals(videoTag.defaultPlaybackRate, 1.0); 398 assert_throws( 399 'DOMException', function () { videoTag.defaultPlaybackRate = 2.0; }, 400 'Attempts to alter videoTag.defaultPlaybackRate MUST fail'); 401 }, '[Video tag] defaultPlaybackRate attribute'); 402 403 test(function () { 404 assert_equals(videoTag.playbackRate, 1.0); 405 assert_throws( 406 'DOMException', function () { videoTag.playbackRate = 2.0; }, 407 'Attempts to alter videoTag.playbackRate MUST fail'); 408 }, '[Video tag] playbackRate attribute'); 409 410 test(function () { 411 assert_equals(videoTag.played.length, 1, 'videoTag.played.length'); 412 assert_equals(videoTag.played.start(0), 0); 413 assert_true(videoTag.played.end(0) >= videoTag.currentTime); 414 }, '[Video tag] played attribute'); 415 416 test(function () { 417 assert_equals(videoTag.seekable.length, 0); 418 // This is wrong in the standard: start() and end() must have arguments, but 419 // since the time range is empty as we assert in the line above, there is no 420 // valid argument with which we can call the methods. 421 // assert_equals(videoTag.seekable.start(), videoTag.currentTime); 422 // assert_equals(videoTag.seekable.end(), videoTag.currentTime); 423 }, '[Video tag] seekable attribute'); 424 425 test(function () { 426 assert_equals(videoTag.startDate, NaN, 'videoTag.startDate'); 427 }, '[Video tag] startDate attribute'); 428 429 test(function () { 430 assert_false(videoTag.loop); 431 }, '[Video tag] loop attribute'); 432 433 }; 434 435 mediaElementsTest.step(function() { 436 var okCallback = mediaElementsTest.step_func(function (stream) { 437 var video = createInvisibleVideoTag(); 438 video.srcObject = stream; 439 verifyVideoTagWithStream(video); 440 mediaElementsTest.done(); 441 }); 442 invokeGetUserMedia(mediaElementsTest, okCallback); 443 }); 444 445 // 9. Enumerating local media devices. 446 // TODO(phoglund): add tests. 447 448 // 10. Obtaining local multimedia content. 449 450 function testGetUserMedia(test, constraints) { 451 var okCallback = test.step_func(function (stream) { 452 assert_true(stream !== null); 453 test.done(); 454 }); 455 navigator.getUserMedia(constraints, okCallback, failedCallback(test)); 456 } 457 458 var getUserMediaTestAudioVideo = async_test('10.1.1 NavigatorUserMedia A/V'); 459 getUserMediaTestAudioVideo.step(function() { 460 testGetUserMedia(getUserMediaTestAudioVideo, { video: true, audio: true }); 461 }); 462 463 var getUserMediaTestVideo = async_test('10.1.1 NavigatorUserMedia V'); 464 getUserMediaTestVideo.step(function() { 465 testGetUserMedia(getUserMediaTestVideo, { video: true, audio: false }); 466 }); 467 468 var getUserMediaTestAudio = async_test('10.1.1 NavigatorUserMedia A'); 469 getUserMediaTestAudio.step(function() { 470 testGetUserMedia(getUserMediaTestAudio, { video: false, audio: true }); 471 }); 472 473 var getUserMediaTestNull = async_test('10.1.1 NavigatorUserMedia Null'); 474 getUserMediaTestNull.step(function() { 475 testGetUserMedia(getUserMediaTestNull, null); 476 }); 477 478 var getUserMediaTestPeerIdentity = 479 async_test('10.2 NavigatorUserMedia with peerIdentity'); 480 getUserMediaTestPeerIdentity.step(function() { 481 var peerIdentity = 'my_identity'; 482 var okCallback = getUserMediaTestPeerIdentity.step_func(function (stream) { 483 assert_true(stream !== null); 484 stream.getVideoTracks().forEach(function(track) { 485 assert_equals(track.peerIdentity, peerIdentity); 486 }); 487 stream.getAudioTracks().forEach(function(track) { 488 assert_equals(track.peerIdentity, peerIdentity); 489 }); 490 getUserMediaTestPeerIdentity.done(); 491 }); 492 navigator.getUserMedia( 493 {video: true, audio: true, peerIdentity: 'my_identity' }, 494 okCallback, failedCallback(getUserMediaTestPeerIdentity)); 495 }); 496 497 // 10.2 MediaStreamConstraints. 498 var constraintsTest = async_test('10.2 MediaStreamConstraints'); 499 constraintsTest.step(function() { 500 var okCallback = constraintsTest.step_func(function (stream) { 501 assert_true(stream !== null); 502 constraintsTest.done(); 503 }); 504 505 // See https://googlechrome.github.io/webrtc/samples/web/content/constraints/ 506 // for more examples of constraints. 507 // TODO(phoglund): test more constraints; the possibilities here are endless. 508 var constraints = {}; 509 constraints.audio = true; 510 constraints.video = { mandatory: {}, optional: [] }; 511 constraints.video.mandatory.minWidth = 640; 512 constraints.video.mandatory.minHeight = 480; 513 constraints.video.mandatory.minFrameRate = 15; 514 515 navigator.getUserMedia(constraints, okCallback, 516 failedCallback(constraintsTest)); 517 }); 518 519 // 10.4 NavigatorUserMediaSuccessCallback. 520 var successCallbackTest = 521 async_test('10.4 NavigatorUserMediaSuccessCallback'); 522 successCallbackTest.step(function() { 523 var okCallback = successCallbackTest.step_func(function (stream) { 524 assert_true(stream !== null); 525 successCallbackTest.done(); 526 }); 527 invokeGetUserMedia(successCallbackTest, okCallback); 528 }); 529 530 // 11. Constrainable Pattern. 531 // TODO(phoglund): add tests. 532