| <!doctype html> |
| <meta charset=utf-8> |
| <meta name="timeout" content="long"> |
| <title>RTCPeerConnection.prototype.getStats</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="RTCPeerConnection-helper.js"></script> |
| <script src="dictionary-helper.js"></script> |
| <script src="RTCStats-helper.js"></script> |
| <script> |
| 'use strict'; |
| |
| // The following helper functions are called from RTCPeerConnection-helper.js: |
| // doSignalingHandshake |
| // getUserMediaTracksAndStreams |
| // waitForRtpAndRtcpStats |
| |
| // The following helper functions are called from RTCStats-helper.js |
| // (depends on dictionary-helper.js): |
| // validateRtcStats |
| |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let track; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| track = tracks[0]; |
| pc.addTrack(track, streams[0]); |
| return pc.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', track.id); |
| assert_true(trackStats != null, 'Has stats for track'); |
| // TODO(hbos): Here and elsewhere, validateRtcStats() only tests id, |
| // timestamp and type is correct type. Should validate based on stats type |
| // but it expects both audio and video members. |
| // https://github.com/web-platform-tests/wpt/issues/9010 |
| validateRtcStats(report, trackStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'addTrack() without setLocalDescription() yields track stats'); |
| |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let stream; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let track = tracks[0]; |
| stream = streams[0]; |
| pc.addTrack(track, stream); |
| return pc.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let streamStats = findStatsByTypeAndId(report, 'stream', stream.id); |
| assert_true(streamStats != null, 'Has stats for stream'); |
| validateRtcStats(report, streamStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'addTrack() without setLocalDescription() yields media stream stats'); |
| |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let track; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| track = tracks[0]; |
| pc.addTrack(track, streams[0]); |
| return pc.createOffer(); |
| })) |
| .then(t.step_func(offer => { |
| return pc.setLocalDescription(offer); |
| })) |
| .then(t.step_func(() => { |
| return pc.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', track.id); |
| assert_true(trackStats != null, 'Has stats for track'); |
| validateRtcStats(report, trackStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'addTrack() with setLocalDescription() yields track stats'); |
| |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let stream; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let track = tracks[0]; |
| stream = streams[0]; |
| pc.addTrack(track, stream); |
| return pc.createOffer(); |
| })) |
| .then(t.step_func(offer => { |
| return pc.setLocalDescription(offer); |
| })) |
| .then(t.step_func(() => { |
| return pc.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let streamStats = findStatsByTypeAndId(report, 'stream', stream.id); |
| assert_true(streamStats != null, 'Has stats for stream'); |
| validateRtcStats(report, streamStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'addTrack() with setLocalDescription() yields media stream stats'); |
| |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let track; |
| let stream; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| track = tracks[0]; |
| stream = streams[0]; |
| pc.addTrack(track, stream); |
| return pc.createOffer(); |
| })) |
| .then(t.step_func(offer => { |
| return pc.setLocalDescription(offer); |
| })) |
| .then(t.step_func(() => { |
| return pc.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', track.id); |
| let streamStats = findStatsByTypeAndId(report, 'stream', stream.id); |
| assert_true(trackStats != null && streamStats != null, |
| 'Has stats for track and stream'); |
| assert_array_equals(streamStats.trackIds, [ trackStats.id ], |
| 'streamStats.trackIds == [ trackStats.id ]'); |
| validateRtcStats(report, trackStats); |
| validateRtcStats(report, streamStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'addTrack(): Media stream stats references track stats'); |
| |
| async_test(t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let track; |
| let stream; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| track = tracks[0]; |
| stream = streams[0]; |
| stream.addTrack(track); |
| for (const track of stream.getTracks()) |
| pc.addTrack(track, stream); |
| return pc.createOffer(); |
| })) |
| .then(t.step_func(offer => { |
| return pc.setLocalDescription(offer); |
| })) |
| .then(t.step_func(() => { |
| return pc.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', track.id); |
| let streamStats = findStatsByTypeAndId(report, 'stream', stream.id); |
| assert_true(trackStats != null && streamStats != null, |
| 'Has stats for track and stream'); |
| assert_array_equals(streamStats.trackIds, [ trackStats.id ], |
| 'streamStats.trackIds == [ trackStats.id ]'); |
| validateRtcStats(report, trackStats); |
| validateRtcStats(report, streamStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'Media stream stats references track stats'); |
| |
| async_test(t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let sendingTrack; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| sendingTrack = tracks[0]; |
| caller.addTrack(sendingTrack, streams[0]); |
| return doSignalingHandshake(caller, callee); |
| })) |
| .then(t.step_func(() => { |
| return caller.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack.id); |
| assert_true(trackStats != null, 'Has stats for sending track'); |
| let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp', |
| 'trackId', trackStats.id); |
| assert_true(outboundStats != null, 'Has stats for outbound RTP stream'); |
| validateRtcStats(report, trackStats); |
| validateRtcStats(report, outboundStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'O/A exchange yields outbound RTP stream stats for sending track'); |
| |
| async_test(t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let receivingTrack; |
| callee.ontrack = trackEvent => { |
| assert_true(receivingTrack == undefined, 'ontrack has not fired before'); |
| receivingTrack = trackEvent.track; |
| }; |
| return getUserMediaTracksAndStreams(1) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| caller.addTrack(tracks[0], streams[0]); |
| return doSignalingHandshake(caller, callee); |
| })) |
| .then(t.step_func(() => { |
| return callee.getStats(); |
| })) |
| .then(t.step_func(report => { |
| assert_true(receivingTrack != null, 'Has a receiving track'); |
| let trackStats = findStatsByTypeAndId(report, 'track', receivingTrack.id); |
| assert_true(trackStats != null, 'Has stats for receiving track'); |
| let inboundStats = findStatsByTypeAndMember(report, 'inbound-rtp', |
| 'trackId', trackStats.id); |
| assert_true(inboundStats != null, 'Has stats for outbound RTP stream'); |
| validateRtcStats(report, trackStats); |
| validateRtcStats(report, inboundStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'O/A exchange yields inbound RTP stream stats for receiving track'); |
| |
| async_test(t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let sendingTrack1; |
| let sendingTrack2; |
| let sender; |
| return getUserMediaTracksAndStreams(2) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| sendingTrack1 = tracks[0]; |
| sendingTrack2 = tracks[1]; |
| sender = caller.addTrack(sendingTrack1, streams[0]); |
| return sender.replaceTrack(sendingTrack2); |
| })) |
| .then(t.step_func(() => { |
| return caller.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id); |
| assert_true(trackStats != null, 'Has stats for replaced track'); |
| validateRtcStats(report, trackStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'replaceTrack() before offer: new track attachment stats present'); |
| |
| async_test(t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let sendingTrack1; |
| let sendingTrack2; |
| let sender; |
| return getUserMediaTracksAndStreams(2) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| sendingTrack1 = tracks[0]; |
| sendingTrack2 = tracks[1]; |
| sender = caller.addTrack(sendingTrack1, streams[0]); |
| return exchangeOffer(caller, callee); |
| })) |
| .then(t.step_func(() => { |
| return sender.replaceTrack(sendingTrack2); |
| })) |
| .then(t.step_func(() => { |
| return caller.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id); |
| assert_true(trackStats != null, 'Has stats for replaced track'); |
| let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp', |
| 'trackId', trackStats.id); |
| assert_true(outboundStats != null, 'Has stats for outbound RTP stream'); |
| validateRtcStats(report, trackStats); |
| validateRtcStats(report, outboundStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'replaceTrack() after offer, before answer: new track attachment stats ' + |
| 'present'); |
| |
| async_test(t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let sendingTrack1; |
| let sendingTrack2; |
| let sender; |
| return getUserMediaTracksAndStreams(2) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| sendingTrack1 = tracks[0]; |
| sendingTrack2 = tracks[1]; |
| sender = caller.addTrack(sendingTrack1, streams[0]); |
| return doSignalingHandshake(caller, callee); |
| })) |
| .then(t.step_func(() => { |
| return sender.replaceTrack(sendingTrack2); |
| })) |
| .then(t.step_func(() => { |
| return caller.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack2.id); |
| assert_true(trackStats != null, 'Has stats for replaced track'); |
| let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp', |
| 'trackId', trackStats.id); |
| assert_true(outboundStats != null, 'Has stats for outbound RTP stream'); |
| validateRtcStats(report, trackStats); |
| validateRtcStats(report, outboundStats); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'replaceTrack() after answer: new track attachment stats present'); |
| |
| async_test(t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let sendingTrack1; |
| let sendingTrack2; |
| let sender; |
| return getUserMediaTracksAndStreams(2) |
| .then(t.step_func(([tracks, streams]) => { |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| sendingTrack1 = tracks[0]; |
| sendingTrack2 = tracks[1]; |
| sender = caller.addTrack(sendingTrack1, streams[0]); |
| return doSignalingHandshake(caller, callee); |
| })) |
| .then(t.step_func(() => { |
| return sender.replaceTrack(sendingTrack2); |
| })) |
| .then(t.step_func(() => { |
| return caller.getStats(); |
| })) |
| .then(t.step_func(report => { |
| let trackStats = findStatsByTypeAndId(report, 'track', sendingTrack1.id); |
| assert_true(trackStats != null, 'Has stats for original track'); |
| assert_true(trackStats.objectDeleted); |
| let outboundStats = findStatsByTypeAndMember(report, 'outbound-rtp', |
| 'trackId', trackStats.id); |
| assert_true(outboundStats == null, |
| 'The outbound RTP stream should no longer reference the ' + |
| 'original attachment'); |
| t.done(); |
| })) |
| .catch(t.step_func(reason => { |
| assert_unreached(reason); |
| })); |
| }, 'replaceTrack(): original track attachment stats present after replacing'); |
| |
| promise_test(async t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let [tracks, streams] = await getUserMediaTracksAndStreams(2); |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let sender = caller.addTrack(tracks[0], streams[0]); |
| callee.addTrack(tracks[1], streams[1]); |
| exchangeIceCandidates(caller, callee); |
| await doSignalingHandshake(caller, callee); |
| await listenToConnected(caller); |
| let receiver = caller.getReceivers()[0]; |
| |
| // Obtain inbound and outbound RTP stream stats on a full stats report. |
| let fullReport = await caller.getStats(); |
| let outboundTrackStats = findStatsByTypeAndId( |
| fullReport, 'track', sender.track.id); |
| let outboundStats = findStatsByTypeAndMember( |
| fullReport, 'outbound-rtp', 'trackId', outboundTrackStats.id); |
| assert_true(outboundStats != null, 'Has stats for outbound RTP stream'); |
| let inboundTrackStats = findStatsByTypeAndId( |
| fullReport, 'track', receiver.track.id); |
| let inboundStats = findStatsByTypeAndMember( |
| fullReport, 'inbound-rtp', 'trackId', inboundTrackStats.id); |
| assert_true(inboundStats != null, 'Has stats for inbound RTP stream'); |
| |
| // Perform stats selection algorithm with sender selector. The result should |
| // contain the outbound-rtp but not the inbound-rtp. |
| let senderReport = await sender.getStats(); |
| assert_true(senderReport.has(outboundStats.id)); |
| assert_false(senderReport.has(inboundStats.id)); |
| |
| // Validate the stats graph, ensuring all stats objects are reachable and |
| // valid from the outbound-rtp stats. |
| validateStatsGraph(senderReport, senderReport.get(outboundStats.id)); |
| // Ensure that the stats graph contains some expected dictionaries. |
| assert_equals(findStatsOfType(senderReport, 'track').length, 1, |
| 'senderReport should contain track stats'); |
| assert_equals(findStatsOfType(senderReport, 'transport').length, 1, |
| 'senderReport should contain transport stats'); |
| assert_equals(findStatsOfType(senderReport, 'candidate-pair').length, 1, |
| 'senderReport should contain candidate-pair stats'); |
| assert_equals(findStatsOfType(senderReport, 'local-candidate').length, 1, |
| 'senderReport should contain local-candidate stats'); |
| assert_equals(findStatsOfType(senderReport, 'remote-candidate').length, 1, |
| 'senderReport should contain remote-candidate stats'); |
| }, 'RTCRtpSender.getStats() contains only outbound-rtp and related stats'); |
| |
| promise_test(async t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let [tracks, streams] = await getUserMediaTracksAndStreams(2); |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let sender = caller.addTrack(tracks[0], streams[0]); |
| callee.addTrack(tracks[1], streams[1]); |
| exchangeIceCandidates(caller, callee); |
| await doSignalingHandshake(caller, callee); |
| await listenToConnected(caller); |
| let receiver = caller.getReceivers()[0]; |
| |
| // Obtain inbound and outbound RTP stream stats on a full stats report. |
| let fullReport = await caller.getStats(); |
| let outboundTrackStats = findStatsByTypeAndId( |
| fullReport, 'track', sender.track.id); |
| let outboundStats = findStatsByTypeAndMember( |
| fullReport, 'outbound-rtp', 'trackId', outboundTrackStats.id); |
| assert_true(outboundStats != null, 'Has stats for outbound RTP stream'); |
| let inboundTrackStats = findStatsByTypeAndId( |
| fullReport, 'track', receiver.track.id); |
| let inboundStats = findStatsByTypeAndMember( |
| fullReport, 'inbound-rtp', 'trackId', inboundTrackStats.id); |
| assert_true(inboundStats != null, 'Has stats for inbound RTP stream'); |
| |
| // Perform stats selection algorithm with receiver selector. The result |
| // should contain the inbound-rtp but not the outbound-rtp. |
| let receiverReport = await receiver.getStats(); |
| assert_true(receiverReport.has(inboundStats.id)); |
| assert_false(receiverReport.has(outboundStats.id)); |
| |
| // Validate the stats graph, ensuring all stats objects are reachable and |
| // valid from the outbound-rtp stats. |
| validateStatsGraph(receiverReport, receiverReport.get(inboundStats.id)); |
| // Ensure that the stats graph contains some expected dictionaries. |
| assert_equals(findStatsOfType(receiverReport, 'track').length, 1, |
| 'receiverReport should contain track stats'); |
| assert_equals(findStatsOfType(receiverReport, 'transport').length, 1, |
| 'receiverReport should contain transport stats'); |
| assert_equals(findStatsOfType(receiverReport, 'candidate-pair').length, 1, |
| 'receiverReport should contain candidate-pair stats'); |
| assert_equals(findStatsOfType(receiverReport, 'local-candidate').length, 1, |
| 'receiverReport should contain local-candidate stats'); |
| assert_equals(findStatsOfType(receiverReport, 'remote-candidate').length, 1, |
| 'receiverReport should contain remote-candidate stats'); |
| }, 'RTCRtpReceiver.getStats() contains only inbound-rtp and related stats'); |
| |
| promise_test(async t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let [tracks, streams] = await getUserMediaTracksAndStreams(2); |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let sender = caller.addTrack(tracks[0], streams[0]); |
| callee.addTrack(tracks[1], streams[1]); |
| exchangeIceCandidates(caller, callee); |
| await doSignalingHandshake(caller, callee); |
| await listenToIceConnected(caller); |
| |
| // Wait until RTCP has arrived so that it can not arrive between |
| // the two get stats calls. |
| await waitForRtpAndRtcpStats(caller); |
| |
| let senderReport = await sender.getStats(); |
| let trackReport = await caller.getStats(sender.track); |
| |
| // Verify the same stats objects are returned but don't compare each |
| // individual metric because timestamps and counters could have gone up |
| // between the two getStats() calls. |
| senderReport.forEach(senderReportStat => { |
| assert_true(trackReport.has(senderReportStat.id)); |
| }); |
| trackReport.forEach(trackReportStat => { |
| assert_true(senderReport.has(trackReportStat.id)); |
| }); |
| }, 'RTCPeerConnection.getStats(sendingTrack) is the same as ' + |
| 'RTCRtpSender.getStats()'); |
| |
| promise_test(async t => { |
| const caller = new RTCPeerConnection(); |
| t.add_cleanup(() => caller.close()); |
| const callee = new RTCPeerConnection(); |
| t.add_cleanup(() => callee.close()); |
| let [tracks, streams] = await getUserMediaTracksAndStreams(2); |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let sender = caller.addTrack(tracks[0], streams[0]); |
| callee.addTrack(tracks[1], streams[1]); |
| exchangeIceCandidates(caller, callee); |
| await doSignalingHandshake(caller, callee); |
| await listenToIceConnected(caller); |
| let receiver = caller.getReceivers()[0]; |
| |
| // Wait until RTCP has arrived so that it can not arrive between |
| // the two get stats calls. |
| await waitForRtpAndRtcpStats(caller); |
| |
| let receiverReport = await receiver.getStats(); |
| let trackReport = await caller.getStats(receiver.track); |
| |
| // Verify the same stats objects are returned but don't compare each |
| // individual metric because timestamps and counters could have gone up |
| // between the two getStats() calls. |
| receiverReport.forEach(receiverReportStat => { |
| assert_true(trackReport.has(receiverReportStat.id)); |
| }); |
| trackReport.forEach(trackReportStat => { |
| assert_true(receiverReport.has(trackReportStat.id)); |
| }); |
| }, 'RTCPeerConnection.getStats(receivingTrack) is the same as ' + |
| 'RTCRtpReceiver.getStats()'); |
| |
| promise_test(async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let [tracks, streams] = await getUserMediaTracksAndStreams(1); |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| await promise_rejects(t, 'InvalidAccessError', pc.getStats(tracks[0])); |
| }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' + |
| 'are zero senders or receivers for the track'); |
| |
| promise_test(async t => { |
| const pc = new RTCPeerConnection(); |
| t.add_cleanup(() => pc.close()); |
| let [tracks, streams] = await getUserMediaTracksAndStreams(2); |
| t.add_cleanup(() => tracks.forEach(track => track.stop())); |
| let sender1 = pc.addTrack(tracks[0], streams[0]); |
| let sender2 = pc.addTrack(tracks[1], streams[1]); |
| await sender2.replaceTrack(sender1.track); |
| await promise_rejects(t, 'InvalidAccessError', pc.getStats(sender1.track)); |
| }, 'RTCPeerConnection.getStats(track) throws InvalidAccessError when there ' + |
| 'are multiple senders for the track'); |
| |
| // Helpers. |
| |
| function findStatsByTypeAndId(report, type, identifier) { |
| return findStats(report, stats => { |
| return stats.type == type && stats[type + 'Identifier'] == identifier; |
| }); |
| } |
| |
| function findStatsByTypeAndMember(report, type, member, value) { |
| return findStats(report, stats => { |
| return stats.type == type && stats[member] == value; |
| }); |
| } |
| |
| function findStats(report, findFunc) { |
| for (let it = report.values(), n = it.next(); !n.done; n = it.next()) { |
| if (findFunc(n.value)) |
| return n.value; |
| } |
| return null; |
| } |
| |
| function findStatsOfType(report, type) { |
| let stats = []; |
| for (let it = report.values(), n = it.next(); !n.done; n = it.next()) { |
| if (n.value.type == type) |
| stats.push(n.value); |
| } |
| return stats; |
| } |
| |
| // Explores the stats graph starting from |stat|, validating each stat |
| // (validateRtcStats) and asserting that all stats of the report were visited. |
| function validateStatsGraph(report, stat) { |
| let visitedIds = new Set(); |
| validateStatsGraphRecursively(report, stat.id, visitedIds); |
| assert_equals(visitedIds.size, report.size, |
| 'Entire stats graph should have been explored.') |
| } |
| |
| function validateStatsGraphRecursively(report, currentId, visitedIds) { |
| if (visitedIds.has(currentId)) |
| return; |
| visitedIds.add(currentId); |
| assert_true(report.has(currentId), 'Broken reference.'); |
| let stat = report.get(currentId); |
| validateRtcStats(report, stat); |
| for (let member in stat) { |
| if (member.endsWith('Id')) { |
| validateStatsGraphRecursively(report, stat[member], visitedIds); |
| } else if (member.endsWith('Ids')) { |
| let ids = stat[member]; |
| for (let i = 0; i < ids.length; ++i) { |
| validateStatsGraphRecursively(report, ids[i], visitedIds); |
| } |
| } |
| } |
| } |
| |
| async function async_assert_throws(exceptionName, promise, description) { |
| try { |
| await promise; |
| } catch (e) { |
| assert_equals(e.name, exceptionName); |
| return; |
| } |
| assert_unreached('No exception was thrown.'); |
| } |
| |
| </script> |