// Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file in the root of the source // tree. An additional intellectual property rights grant can be found // in the file PATENTS. All contributing project authors may // be found in the AUTHORS file in the root of the source tree. setup({timeout:10000}); // Helper functions to minimize code duplication. function failedCallback(test) { return test.step_func(function (error) { assert_unreached('Should not get an error callback'); }); } function invokeGetUserMedia(test, okCallback) { getUserMedia({ video: true, audio: true }, okCallback, failedCallback(test)); } // 4.2 MediaStream. var mediaStreamTest = async_test('4.2 MediaStream'); function verifyMediaStream(stream) { // TODO(kjellander): Add checks for default values where applicable. test(function () { assert_own_property(stream, 'id'); assert_true(typeof stream.id === 'string'); assert_readonly(stream, 'id'); }, '[MediaStream] id attribute'); test(function () { assert_inherits(stream, 'getAudioTracks'); assert_true(typeof stream.getAudioTracks === 'function'); }, '[MediaStream] getAudioTracks function'); test(function () { assert_inherits(stream, 'getVideoTracks'); assert_true(typeof stream.getVideoTracks === 'function'); }, '[MediaStream] getVideoTracks function'); test(function () { assert_inherits(stream, 'getTrackById'); assert_true(typeof stream.getTrackById === 'function'); }, '[MediaStream] getTrackById function'); test(function () { assert_inherits(stream, 'addTrack'); assert_true(typeof stream.addTrack === 'function'); }, '[MediaStream] addTrack function'); test(function () { assert_inherits(stream, 'removeTrack'); assert_true(typeof stream.removeTrack === 'function'); }, '[MediaStream] removeTrack function'); test(function () { // Missing in Chrome. assert_inherits(stream, 'clone'); assert_true(typeof stream.clone === 'function'); }, '[MediaStream] clone function'); test(function () { assert_own_property(stream, 'ended'); assert_true(typeof stream.ended === 'boolean'); assert_readonly(stream, 'ended'); }, '[MediaStream] ended attribute'); test(function () { assert_own_property(stream, 'onended'); assert_true(stream.onended === null); }, '[MediaStream] onended EventHandler'); test(function () { assert_own_property(stream, 'onaddtrack'); assert_true(stream.onaddtrack === null); }, '[MediaStream] onaddtrack EventHandler'); test(function () { assert_own_property(stream, 'onremovetrack'); assert_true(stream.onremovetrack === null); }, '[MediaStream] onremovetrack EventHandler'); } mediaStreamTest.step(function() { var okCallback = mediaStreamTest.step_func(function (stream) { verifyMediaStream(stream); var videoTracks = stream.getVideoTracks(); assert_true(videoTracks.length > 0); // Verify event handlers are working. stream.onaddtrack = onAddTrackCallback stream.onremovetrack = onRemoveTrackCallback stream.removeTrack(videoTracks[0]); stream.addTrack(videoTracks[0]); mediaStreamTest.done(); }); var onAddTrackCallback = mediaStreamTest.step_func(function () { // TODO(kjellander): verify number of tracks. mediaStreamTest.done(); }); var onRemoveTrackCallback = mediaStreamTest.step_func(function () { // TODO(kjellander): verify number of tracks. mediaStreamTest.done(); }); invokeGetUserMedia(mediaStreamTest, okCallback);; }); // 4.3 MediaStreamTrack. var mediaStreamTrackTest = async_test('4.3 MediaStreamTrack'); function verifyTrack(type, track) { test(function () { assert_own_property(track, 'kind'); assert_readonly(track, 'kind'); assert_true(typeof track.kind === 'string', 'kind is an object (DOMString)'); }, '[MediaStreamTrack (' + type + ')] kind attribute'); test(function () { assert_own_property(track, 'id'); assert_readonly(track, 'id'); assert_true(typeof track.id === 'string', 'id is an object (DOMString)'); }, '[MediaStreamTrack (' + type + ')] id attribute'); test(function () { assert_own_property(track, 'label'); assert_readonly(track, 'label'); assert_true(typeof track.label === 'string', 'label is an object (DOMString)'); }, '[MediaStreamTrack (' + type + ')] label attribute'); test(function () { assert_own_property(track, 'enabled'); assert_true(typeof track.enabled === 'boolean'); assert_true(track.enabled, 'enabled property must be true initially'); }, '[MediaStreamTrack (' + type + ')] enabled attribute'); test(function () { // Missing in Chrome. assert_own_property(track, 'muted'); assert_readonly(track, 'muted'); assert_true(typeof track.muted === 'boolean'); assert_false(track.muted, 'muted property must be false initially'); }, '[MediaStreamTrack (' + type + ')] muted attribute'); test(function () { assert_own_property(track, 'onmute'); assert_true(track.onmute === null); }, '[MediaStreamTrack (' + type + ')] onmute EventHandler'); test(function () { assert_own_property(track, 'onunmute'); assert_true(track.onunmute === null); }, '[MediaStreamTrack (' + type + ')] onunmute EventHandler'); test(function () { // Missing in Chrome. assert_own_property(track, '_readonly'); assert_readonly(track, '_readonly'); assert_true(typeof track._readonly === 'boolean'); }, '[MediaStreamTrack (' + type + ')] _readonly attribute'); test(function () { // Missing in Chrome. assert_own_property(track, 'remote'); assert_readonly(track, 'remote'); assert_true(typeof track.remote === 'boolean'); }, '[MediaStreamTrack (' + type + ')] remote attribute'); test(function () { assert_own_property(track, 'readyState'); assert_readonly(track, 'readyState'); assert_true(typeof track.readyState === 'string'); // TODO(kjellander): verify the initial state. }, '[MediaStreamTrack (' + type + ')] readyState attribute'); test(function () { // Missing in Chrome. assert_own_property(track, 'onstarted'); assert_true(track.onstarted === null); }, '[MediaStreamTrack (' + type + ')] onstarted EventHandler'); test(function () { assert_own_property(track, 'onended'); assert_true(track.onended === null); }, '[MediaStreamTrack (' + type + ')] onended EventHandler'); test(function () { // Missing in Chrome. assert_inherits(track, 'getSourceInfos'); assert_true(typeof track.getSourceInfos === 'function'); }, '[MediaStreamTrack (' + type + ')]: getSourceInfos function'); test(function () { // Missing in Chrome. assert_inherits(track, 'constraints'); assert_true(typeof track.constraints === 'function'); }, '[MediaStreamTrack (' + type + ')]: constraints function'); test(function () { // Missing in Chrome. assert_inherits(track, 'states'); assert_true(typeof track.states === 'function'); }, '[MediaStreamTrack (' + type + ')]: states function'); test(function () { // Missing in Chrome. assert_inherits(track, 'capabilities'); assert_true(typeof track.capabilities === 'function'); }, '[MediaStreamTrack (' + type + ')]: capabilities function'); test(function () { // Missing in Chrome. assert_inherits(track, 'applyConstraints'); assert_true(typeof track.applyConstraints === 'function'); }, '[MediaStreamTrack (' + type + ')]: applyConstraints function'); test(function () { // Missing in Chrome. assert_own_property(track, 'onoverconstrained'); assert_true(track.onoverconstrained === null); }, '[MediaStreamTrack (' + type + ')] onoverconstrained EventHandler'); test(function () { // Missing in Chrome. assert_inherits(track, 'clone'); assert_true(typeof track.clone === 'function'); }, '[MediaStreamTrack (' + type + ')] clone function'); test(function () { // Missing in Chrome. assert_inherits(track, 'stop'); assert_true(typeof track.stop === 'function'); }, '[MediaStreamTrack (' + type + ')] stop function'); }; mediaStreamTrackTest.step(function() { var okCallback = mediaStreamTrackTest.step_func(function (stream) { verifyTrack('audio', stream.getAudioTracks()[0]); verifyTrack('video', stream.getVideoTracks()[0]); mediaStreamTrackTest.done(); }); invokeGetUserMedia(mediaStreamTrackTest, okCallback); }); mediaStreamTrackTest.step(function() { var okCallback = mediaStreamTrackTest.step_func(function (stream) { // Verify event handlers are working. var track = stream.getVideoTracks()[0]; track.onended = onendedCallback track.stop(); mediaStreamTrackTest.done(); }); var onendedCallback = mediaStreamTrackTest.step_func(function () { assert_true(track.ended); mediaStreamTrackTest.done(); }); invokeGetUserMedia(mediaStreamTrackTest, okCallback); }); // 4.4 MediaStreamTrackEvent tests. var mediaStreamTrackEventTest = async_test('4.4 MediaStreamTrackEvent'); mediaStreamTrackEventTest.step(function() { var okCallback = mediaStreamTrackEventTest.step_func(function (stream) { // TODO(kjellander): verify attributes mediaStreamTrackEventTest.done(); }); invokeGetUserMedia(mediaStreamTrackEventTest, okCallback); }); // 4.5 Video and Audio Tracks tests. var avTracksTest = async_test('4.5 Video and Audio Tracks'); avTracksTest.step(function() { var okCallback = avTracksTest.step_func(function (stream) { // TODO(kjellander): verify attributes avTracksTest.done(); }); invokeGetUserMedia(avTracksTest, okCallback); }); // 5. The model: sources, sinks, constraints, and states // 6. Source states // 6.1 Dictionary MediaSourceStates Members // 7. Source capabilities // 7.1 Dictionary CapabilityRange Members // 7.2 CapabilityList array // 7.3 Dictionary AllVideoCapabilities Members // 7.4 Dictionary AllAudioCapabilities Members // 8. URL tests. var createObjectURLTest = async_test('8.1 URL createObjectURL method'); createObjectURLTest.step(function() { var okCallback = createObjectURLTest.step_func(function (stream) { var url = webkitURL.createObjectURL(stream); assert_true(typeof url === 'string'); createObjectURLTest.done(); }); invokeGetUserMedia(createObjectURLTest, okCallback); }); // 9. MediaStreams as Media Elements. var mediaElementsTest = async_test('9. MediaStreams as Media Elements'); function verifyVideoTagWithStream(videoTag) { test(function () { assert_equals(videoTag.buffered.length, 0); }, '[Video tag] buffered attribute'); test(function () { // Attempts to alter currentTime shall be ignored. assert_true(videoTag.currentTime >= 0); assert_throws('InvalidStateError', function () { videoTag.currentTime = 1234; }, 'Attempts to modify currentTime shall throw ' + 'InvalidStateError'); }, '[Video tag] currentTime attribute'); test(function () { assert_equals(videoTag.duration, Infinity, 'videoTag.duration'); }, '[Video tag] duration attribute'); test(function () { assert_false(videoTag.seeking, 'videoTag.seeking'); }, '[Video tag] seeking attribute'); test(function () { assert_equals(videoTag.defaultPlaybackRate, 1.0); assert_throws('DOMException', function () { videoTag.defaultPlaybackRate = 2.0; }, 'Attempts to alter videoTag.defaultPlaybackRate MUST fail'); }, '[Video tag] defaultPlaybackRate attribute'); test(function () { assert_equals(videoTag.playbackRate, 1.0); assert_throws('DOMException', function () { videoTag.playbackRate = 2.0; }, 'Attempts to alter videoTag.playbackRate MUST fail'); }, '[Video tag] playbackRate attribute'); test(function () { assert_equals(videoTag.played.length, 1, 'videoTag.played.length'); assert_equals(videoTag.played.start(0), 0); assert_true(videoTag.played.end(0) >= videoTag.currentTime); }, '[Video tag] played attribute'); test(function () { assert_equals(videoTag.seekable.length, 0); assert_equals(videoTag.seekable.start(), videoTag.currentTime); assert_equals(videoTag.seekable.end(), videoTag.currentTime); assert_equals(videoTag.startDate, NaN, 'videoTag.startDate'); }, '[Video tag] seekable attribute'); test(function () { assert_false(videoTag.loop); }, '[Video tag] loop attribute'); }; mediaElementsTest.step(function() { var okCallback = mediaElementsTest.step_func(function (stream) { var videoTag = document.getElementById('local-view'); // Call the polyfill wrapper to attach the media stream to this element. attachMediaStream(videoTag, stream); verifyVideoTagWithStream(videoTag); mediaElementsTest.done(); }); invokeGetUserMedia(mediaElementsTest, okCallback); }); // 11. Obtaining local multimedia content. // 11.1 NavigatorUserMedia. var getUserMediaTest = async_test('11.1 NavigatorUserMedia'); getUserMediaTest.step(function() { var okCallback = getUserMediaTest.step_func(function (stream) { assert_true(stream !== null); getUserMediaTest.done(); }); // boolean parameters, without failure callback: getUserMedia({ video: true, audio: true }, okCallback); getUserMedia({ video: true, audio: false }, okCallback); getUserMedia({ video: false, audio: true }, okCallback); // boolean parameters, with failure callback: getUserMedia({ video: true, audio: true }, okCallback, failedCallback(getUserMediaTest)); getUserMedia({ video: true, audio: false }, okCallback, failedCallback(getUserMediaTest)); getUserMedia({ video: false, audio: true }, okCallback, failedCallback(getUserMediaTest)); }); // 11.2 MediaStreamConstraints. var constraintsTest = async_test('11.2 MediaStreamConstraints'); constraintsTest.step(function() { var okCallback = constraintsTest.step_func(function (stream) { assert_true(stream !== null); constraintsTest.done(); }); // Constraints on video. // See http://webrtc.googlecode.com/svn/trunk/samples/js/demos/html/constraints-and-stats.html // for more examples of constraints. var constraints = {}; constraints.audio = true; constraints.video = { mandatory: {}, optional: [] }; constraints.video.mandatory.minWidth = 640; constraints.video.mandatory.minHeight = 480; constraints.video.mandatory.minFrameRate = 15; getUserMedia(constraints, okCallback, failedCallback(constraintsTest)); }); // 11.3 NavigatorUserMediaSuccessCallback. var successCallbackTest = async_test('11.3 NavigatorUserMediaSuccessCallback'); successCallbackTest.step(function() { var okCallback = successCallbackTest.step_func(function (stream) { assert_true(stream !== null); successCallbackTest.done(); }); invokeGetUserMedia(successCallbackTest, okCallback); }); // 11.4 NavigatorUserMediaError and NavigatorUserMediaErrorCallback. var errorCallbackTest = async_test('11.4 NavigatorUserMediaError and ' + 'NavigatorUserMediaErrorCallback'); errorCallbackTest.step(function() { var okCallback = errorCallbackTest.step_func(function (stream) { assert_unreached('Should not get a success callback'); }); var errorCallback = errorCallbackTest.step_func(function (error) { assert_own_property(error, 'name'); assert_readonly(error.name); assert_true(typeof error.name === 'string'); assert_equals(error.name, 'ConstraintNotSatisfiedError', 'error.name'); errorCallbackTest.done(); }); // Setting both audio and video to false triggers an error callback. // TODO(kjellander): Figure out if there's a way in the spec to trigger an // error callback. // TODO(kjellander): Investigate why the error callback is not called when // false/false is provided in Chrome. getUserMedia({ video: false, audio: false }, okCallback, errorCallback); });