| <!doctype html> | |
| <meta charset=utf-8> | |
| <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'; | |
| // Test is based on the following editor draft: | |
| // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html | |
| // https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html | |
| // The following helper function is called from RTCPeerConnection-helper.js | |
| // getTrackFromUserMedia | |
| // The following helper function is called from RTCStats-helper.js | |
| // validateStatsReport | |
| // assert_stats_report_has_stats | |
| /* | |
| 8.2. RTCPeerConnection Interface Extensions | |
| partial interface RTCPeerConnection { | |
| Promise<RTCStatsReport> getStats(optional MediaStreamTrack? selector = null); | |
| }; | |
| 8.3. RTCStatsReport Object | |
| interface RTCStatsReport { | |
| readonly maplike<DOMString, object>; | |
| }; | |
| 8.4. RTCStats Dictionary | |
| dictionary RTCStats { | |
| DOMHighResTimeStamp timestamp; | |
| RTCStatsType type; | |
| DOMString id; | |
| }; | |
| id | |
| Two RTCStats objects, extracted from two different RTCStatsReport objects, MUST | |
| have the same id if they were produced by inspecting the same underlying object. | |
| 8.2. getStats | |
| 1. Let selectorArg be the method's first argument. | |
| 2. Let connection be the RTCPeerConnection object on which the method was invoked. | |
| 3. If selectorArg is neither null nor a valid MediaStreamTrack, return a promise | |
| rejected with a newly created TypeError. | |
| 5. Let p be a new promise. | |
| 6. Run the following steps in parallel: | |
| 1. Gather the stats indicated by selector according to the stats selection algorithm. | |
| 2. Resolve p with the resulting RTCStatsReport object, containing the gathered stats. | |
| */ | |
| promise_test(() => { | |
| const pc = new RTCPeerConnection(); | |
| return pc.getStats(); | |
| }, 'getStats() with no argument should succeed'); | |
| promise_test(() => { | |
| const pc = new RTCPeerConnection(); | |
| return pc.getStats(null); | |
| }, 'getStats(null) should succeed'); | |
| /* | |
| 8.2. getStats | |
| 4. Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track | |
| member matches selectorArg. If no such sender or receiver exists, return a promise | |
| rejected with a newly created InvalidAccessError. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return getTrackFromUserMedia('audio') | |
| .then(([track, mediaStream]) => { | |
| return promise_rejects(t, 'InvalidAccessError', pc.getStats(track)); | |
| }); | |
| }, 'getStats() with track not added to connection should reject with InvalidAccessError'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return getTrackFromUserMedia('audio') | |
| .then(([track, mediaStream]) => { | |
| pc.addTrack(track, mediaStream); | |
| return pc.getStats(track); | |
| }); | |
| }, 'getStats() with track added via addTrack should succeed'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const track = generateMediaStreamTrack(); | |
| pc.addTransceiver(track); | |
| return pc.getStats(track); | |
| }, 'getStats() with track added via addTransceiver should succeed'); | |
| /* | |
| 8.2. getStats | |
| 4. Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track | |
| member matches selectorArg. If more than one sender or receiver fit this criteria, | |
| return a promise rejected with a newly created InvalidAccessError. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return getTrackFromUserMedia('audio') | |
| .then(([track, mediaStream]) => { | |
| // addTransceiver allows adding same track multiple times | |
| const transceiver1 = pc.addTransceiver(track); | |
| const transceiver2 = pc.addTransceiver(track); | |
| assert_not_equals(transceiver1, transceiver2); | |
| assert_not_equals(transceiver1.sender, transceiver2.sender); | |
| assert_equals(transceiver1.sender.track, transceiver2.sender.track); | |
| return promise_rejects(t, 'InvalidAccessError', pc.getStats(track)); | |
| }); | |
| }, `getStats() with track associated with more than one sender should reject with InvalidAccessError`); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const transceiver1 = pc.addTransceiver('audio'); | |
| // Create another transceiver that resends what | |
| // is being received, kind of like echo | |
| const transceiver2 = pc.addTransceiver(transceiver1.receiver.track); | |
| assert_equals(transceiver1.receiver.track, transceiver2.sender.track); | |
| return promise_rejects(t, 'InvalidAccessError', pc.getStats(transceiver1.receiver.track)); | |
| }, 'getStats() with track associated with both sender and receiver should reject with InvalidAccessError'); | |
| /* | |
| 8.5. The stats selection algorithm | |
| 2. If selector is null, gather stats for the whole connection, add them to result, | |
| return result, and abort these steps. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return pc.getStats() | |
| .then(statsReport => { | |
| validateStatsReport(statsReport); | |
| assert_stats_report_has_stats(statsReport, ['peer-connection']); | |
| }); | |
| }, 'getStats() with no argument should return stats report containing peer-connection stats'); | |
| /* | |
| 8.5. The stats selection algorithm | |
| 3. If selector is an RTCRtpSender, gather stats for and add the following objects | |
| to result: | |
| - All RTCOutboundRTPStreamStats objects corresponding to selector. | |
| - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats | |
| objects added. | |
| */ | |
| promise_test(() => { | |
| const pc = new RTCPeerConnection(); | |
| return getTrackFromUserMedia('audio') | |
| .then(([track, mediaStream]) => { | |
| pc.addTrack(track, mediaStream); | |
| return pc.getStats(track) | |
| .then(statsReport => { | |
| validateStatsReport(statsReport); | |
| assert_stats_report_has_stats(statsReport, ['outbound-rtp']); | |
| }); | |
| }); | |
| }, `getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats`); | |
| /* | |
| 8.5. The stats selection algorithm | |
| 4. If selector is an RTCRtpReceiver, gather stats for and add the following objects | |
| to result: | |
| - All RTCInboundRTPStreamStats objects corresponding to selector. | |
| - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats | |
| added. | |
| */ | |
| promise_test(() => { | |
| const pc = new RTCPeerConnection(); | |
| const transceiver = pc.addTransceiver('audio'); | |
| return pc.getStats(transceiver.receiver.track) | |
| .then(statsReport => { | |
| validateStatsReport(statsReport); | |
| assert_stats_report_has_stats(statsReport, ['inbound-rtp']); | |
| }); | |
| }, `getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats`); | |
| </script> |