<!DOCTYPE HTML>
<html>
<head>
  <script type="application/javascript" src="pc.js"></script>
  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
  <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
  <script type="application/javascript" src="simulcast.js"></script>
  <script type="application/javascript" src="stats.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
  createHTML({
    bug: "1432793",
    title: "Simulcast with odd resolution",
    visible: true
  });

  runNetworkTest(async () => {
    const helper = new VideoStreamHelper();
    const emitter = new VideoFrameEmitter(helper.green, helper.red, 705, 528);

    async function checkVideoElement(senderElement, receiverElement, encoding) {
      info(`Waiting for receiver video element ${encoding.rid} to start playing`);
      await helper.checkVideoPlaying(receiverElement);
      const srcWidth = senderElement.videoWidth;
      const srcHeight = senderElement.videoHeight;
      info(`Source resolution is ${srcWidth}x${srcHeight}`);

      const scaleDownBy = encoding.scaleResolutionDownBy;
      const expectedWidth = srcWidth / scaleDownBy;
      const expectedHeight = srcHeight / scaleDownBy;
      const margin = srcWidth * 0.1;
      const width = receiverElement.videoWidth;
      const height = receiverElement.videoHeight;
      const rid = encoding.rid;
      ok(width >= expectedWidth - margin && width <= expectedWidth + margin,
        `Width ${width} should be within 10% of ${expectedWidth} for rid '${rid}'`);
      ok(height >= expectedHeight - margin && height <= expectedHeight + margin,
        `Height ${height} should be within 10% of ${expectedHeight} for rid '${rid}'`);
    }

    async function checkVideoElements(senderElement, receiverElements, encodings) {
      is(receiverElements.length, encodings.length, 'Number of video elements should match number of encodings');
      info('Waiting for sender video element to start playing');
      await helper.checkVideoPlaying(senderElement);
      for (let i = 0; i < encodings.length; i++) {
        await checkVideoElement(senderElement, receiverElements[i], encodings[i]);
      }
    }

    async function checkSenderStats(sender) {
      const senderStats = await sender.getStats();
      checkSenderStats(senderStats, encodings.length);
      checkExpectedFields(senderStats);
      pedanticChecks(senderStats);
    }

    async function waitForResizeEvents(elements) {
      return Promise.all(elements.map(elem => haveEvent(elem, 'resize')));
    }

    const encodings = [{ rid: "0", maxBitrate: 40000, scaleResolutionDownBy: 1.9 },
                       { rid: "1", maxBitrate: 40000, scaleResolutionDownBy: 3.5 },
                       { rid: "2", maxBitrate: 40000, scaleResolutionDownBy: 17.8 }];

    await pushPrefs(
        ['media.peerconnection.simulcast', true],
        // 180Kbps was determined empirically, set well-higher than
        // the 80Kbps+overhead needed for the two simulcast streams.
        // 100Kbps was apparently too low.
        ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);


    const offerer = new RTCPeerConnection();
    const answerer = new RTCPeerConnection();

    const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
    offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
    answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());

    const metadataToBeLoaded = [];
    answerer.ontrack = (e) => {
      metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
    };

    // One send transceiver, that will be used to send both simulcast streams
    const videoStream = emitter.stream();
    offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
    const senderElement = document.createElement('video');
    senderElement.autoplay = true;
    senderElement.srcObject = videoStream;
    senderElement.id = videoStream.id

    const sender = offerer.getSenders()[0];
    sender.setParameters({encodings});

    const offer = await offerer.createOffer();

    const mungedOffer = ridToMid(offer);
    info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);

    await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
    await offerer.setLocalDescription(offer);

    const rids = answerer.getTransceivers().map(t => t.mid);
    is(rids.length, 3, 'Should have 3 mids in offer');
    ok(rids[0], 'First mid should be non-empty');
    ok(rids[1], 'Second mid should be non-empty');
    ok(rids[2], 'Third mid should be non-empty');
    info(`rids: ${JSON.stringify(rids)}`);

    const answer = await answerer.createAnswer();

    const mungedAnswer = midToRid(answer);
    info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
    await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
    await answerer.setLocalDescription(answer);

    is(metadataToBeLoaded.length, 3, 'Offerer should have gotten 3 ontrack events');
    emitter.start();
    info('Waiting for 3 loadedmetadata events');
    const videoElems = await Promise.all(metadataToBeLoaded);
    await checkVideoElements(senderElement, videoElems, encodings);
    emitter.stop();

    await Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);

    info(`Changing source resolution to 1280x720`);
    emitter.size(1280, 720);
    emitter.start();
    await waitForResizeEvents([senderElement, ...videoElems]);
    await checkVideoElements(senderElement, videoElems, encodings);
    await checkSenderStats(sender);

    encodings[0].scaleResolutionDownBy = 1;
    encodings[1].scaleResolutionDownBy = 2;
    encodings[2].scaleResolutionDownBy = 3;
    info(`Changing encodings to ${JSON.stringify(encodings)}`);
    await sender.setParameters({encodings});
    await waitForResizeEvents(videoElems);
    await checkVideoElements(senderElement, videoElems, encodings);
    await checkSenderStats(sender);

    encodings[0].scaleResolutionDownBy = 6;
    encodings[1].scaleResolutionDownBy = 5;
    encodings[2].scaleResolutionDownBy = 4;
    info(`Changing encodings to ${JSON.stringify(encodings)}`);
    await sender.setParameters({encodings});
    await waitForResizeEvents(videoElems);
    await checkVideoElements(senderElement, videoElems, encodings);
    await checkSenderStats(sender);

    encodings[0].scaleResolutionDownBy = 4;
    encodings[1].scaleResolutionDownBy = 1;
    encodings[2].scaleResolutionDownBy = 2;
    info(`Changing encodings to ${JSON.stringify(encodings)}`);
    await sender.setParameters({encodings});
    await waitForResizeEvents(videoElems);
    await checkVideoElements(senderElement, videoElems, encodings);
    await checkSenderStats(sender);

    emitter.stop();
    videoStream.getVideoTracks()[0].stop();
    offerer.close();
    answerer.close();
  });

</script>
</pre>
</body>
</html>
