summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-helper.js
blob: 10cfd651554a259817cd34521ab44556063f4f44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
'use strict';

// This file depends on `webrtc/RTCPeerConnection-helper.js`
// which should be loaded from the main HTML file.

var kAbsCaptureTime =
    'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time';

function addHeaderExtensionToSdp(sdp, uri) {
  const extmap = new RegExp('a=extmap:(\\d+)');
  let sdpLines = sdp.split('\r\n');

  // This assumes at most one audio m= section and one video m= section.
  // If more are present, only the first section of each kind is munged.
  for (const section of ['audio', 'video']) {
    let found_section = false;
    let maxId = undefined;
    let maxIdLine = undefined;
    let extmapAllowMixed = false;

    // find the largest header extension id for section.
    for (let i = 0; i < sdpLines.length; ++i) {
      if (!found_section) {
        if (sdpLines[i].startsWith('m=' + section)) {
          found_section = true;
        }
        continue;
      } else {
        if (sdpLines[i].startsWith('m=')) {
          // end of section
          break;
        }
      }

      if (sdpLines[i] === 'a=extmap-allow-mixed') {
        extmapAllowMixed = true;
      }
      let result = sdpLines[i].match(extmap);
      if (result && result.length === 2) {
        if (maxId == undefined || result[1] > maxId) {
          maxId = parseInt(result[1]);
          maxIdLine = i;
        }
      }
    }

    if (maxId == 14 && !extmapAllowMixed) {
      // Reaching the limit of one byte header extension. Adding two byte header
      // extension support.
      sdpLines.splice(maxIdLine + 1, 0, 'a=extmap-allow-mixed');
    }
    if (maxIdLine !== undefined) {
      sdpLines.splice(maxIdLine + 1, 0,
                      'a=extmap:' + (maxId + 1).toString() + ' ' + uri);
    }
  }
  return sdpLines.join('\r\n');
}

// TODO(crbug.com/1051821): Use RTP header extension API instead of munging
// when the RTP header extension API is implemented.
async function addAbsCaptureTimeAndExchangeOffer(caller, callee) {
  let offer = await caller.createOffer();

  // Absolute capture time header extension may not be offered by default,
  // in such case, munge the SDP.
  offer.sdp = addHeaderExtensionToSdp(offer.sdp, kAbsCaptureTime);

  await caller.setLocalDescription(offer);
  return callee.setRemoteDescription(offer);
}

// TODO(crbug.com/1051821): Use RTP header extension API instead of munging
// when the RTP header extension API is implemented.
async function checkAbsCaptureTimeAndExchangeAnswer(caller, callee,
                                                    absCaptureTimeAnswered) {
  let answer = await callee.createAnswer();

  const extmap = new RegExp('a=extmap:\\d+ ' + kAbsCaptureTime + '\r\n', 'g');
  if (answer.sdp.match(extmap) == null) {
    // We expect that absolute capture time RTP header extension is answered.
    // But if not, there is no need to proceed with the test.
    assert_false(absCaptureTimeAnswered, 'Absolute capture time RTP ' +
        'header extension is not answered');
  } else {
    if (!absCaptureTimeAnswered) {
      // We expect that absolute capture time RTP header extension is not
      // answered, but it is, then we munge the answer to remove it.
      answer.sdp = answer.sdp.replace(extmap, '');
    }
  }

  await callee.setLocalDescription(answer);
  return caller.setRemoteDescription(answer);
}

async function exchangeOfferAndListenToOntrack(t, caller, callee,
                                               absCaptureTimeOffered) {
  const ontrackPromise = addEventListenerPromise(t, callee, 'track');
  // Absolute capture time header extension is expected not offered by default,
  // and thus munging is needed to enable it.
  await absCaptureTimeOffered
      ? addAbsCaptureTimeAndExchangeOffer(caller, callee)
      : exchangeOffer(caller, callee);
  return ontrackPromise;
}

async function initiateSingleTrackCall(t, cap, absCaptureTimeOffered,
                                       absCaptureTimeAnswered) {
  const caller = new RTCPeerConnection();
  t.add_cleanup(() => caller.close());
  const callee = new RTCPeerConnection();
  t.add_cleanup(() => callee.close());

  const stream = await getNoiseStream(cap);
  stream.getTracks().forEach(track => {
    caller.addTrack(track, stream);
    t.add_cleanup(() => track.stop());
  });

  // TODO(crbug.com/988432): `getSynchronizationSources() on the audio side
  // needs a hardware sink for the returned dictionary entries to get updated.
  const remoteVideo = document.getElementById('remote');

  callee.ontrack = e => {
    remoteVideo.srcObject = e.streams[0];
  }

  exchangeIceCandidates(caller, callee);

  await exchangeOfferAndListenToOntrack(t, caller, callee,
                                        absCaptureTimeOffered);

  // Exchange answer and check whether the absolute capture time RTP header
  // extension is answered.
  await checkAbsCaptureTimeAndExchangeAnswer(caller, callee,
                                             absCaptureTimeAnswered);

  return [caller, callee];
}