chromium/content/test/data/gpu/vc/videos_mxn.html

<!--
Copyright 2021 The Chromium Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<html>

<head>
  <title>MxN Video playbacks</title>
  <script>
    const _defaultRows = 7;
    const _defaultColumns = 7;
    const _totalVideoWidth = 1600;
    const _totalVideoHeight = 900;
    let codec = 'vp9';

    const parsedString = (function (names) {
      const pairs = {};
      for (let i = 0; i < names.length; ++i) {
        const keyValue = names[i].split('=', 2);
        if (keyValue.length === 1) {
          pairs[keyValue[0]] = '';
        } else {
          pairs[keyValue[0]] = decodeURIComponent(keyValue[1].replace(/\+/g, ' '));
        }
      }
      return pairs;
    })(window.location.search.substr(1).split('&'));

    function GetVideoSource(videoCount, index, codec) {
      switch (codec) {
        case 'vp8':
          if (videoCount <= 4) {
            return './teddy1_vp8_640x360_30fps.webm';
          } else {
            if (index < 4) {
              return './teddy3_vp8_320x180_30fps.webm';
            } else if (index < 16) {
              return './teddy2_vp8_320x180_15fps.webm';
            } else {
              return './teddy1_vp8_320x180_7fps.webm';
            }
          }
          break;

        case 'vp9':
        default:
          if (videoCount <= 4) {
            return './teddy1_vp9_640x360_30fps.webm';
          } else {
            if (index < 4) {
              return './teddy3_vp9_320x180_30fps.webm';
            } else if (index < 16) {
              return './teddy2_vp9_320x180_15fps.webm';
            } else {
              return './teddy1_vp9_320x180_7fps.webm';
            }
          }
          break;
      }
    }

    function startMxNVideos() {
      const container = document.getElementById('container');

      // Get the video row count and the column count from the string.
      // Example: videos_mxn.html?rows=9&columns=9
      let videoRows = parsedString['rows'];
      let videoColumns = parsedString['columns'];
      if (videoRows === undefined) {
        videoRows = _defaultRows;
      }
      if (videoColumns === undefined) {
        videoColumns = _defaultColumns;
      }

      // Get the video source option from the string.
      codecString = parsedString['codec'];
      if (codecString === 'vp8') {
        codec = 'vp8';
      } else if (codecString !== 'vp9' && codecString !== undefined) {
        console.warn('Unsupported video codec format! Switch to default VP9.');
      }

      // Limite the number of videos to 20x20.
      // These videos will not load when the number is too big.
      const maxColRow = Math.max(videoRows, videoColumns);
      if (maxColRow > 20) {
        const p = container.appendChild(document.createElement('p'));
        p.style.position = "absolute";
        p.style.top = 0;
        p.style.left = 0;
        p.style.width = _totalVideoWidth;
        p.style.height = _totalVideoHeight;
        p.innerHTML = "Cannot support videos more than 20 x 20!" + "<br />" +
          "Please change the number of rows/columns.";
        return;
      }

      // Calculate the video onscreen size
      const videoWidth = _totalVideoWidth / maxColRow;
      const videoHeight = _totalVideoHeight / maxColRow;
      const iconHeight = videoHeight / 8;

      // Create MxN videos.
      let animatedBar;
      let iconBottom;
      const videoCount = videoRows * videoColumns;
      for (let row = 0; row < videoRows; row++) {
        for (let column = 0; column < videoColumns; column++) {
          // Onscreen position.
          const videoTop = row * videoHeight;
          const videoLeft = column * videoWidth;

          // Video source.
          const i = row * videoColumns + column;
          const videoSrc = GetVideoSource(videoCount, i, codec);

          createOneVideo(videoTop, videoLeft, videoWidth, videoHeight,
            videoSrc, /*hasBorder=*/ i === 0);
          // Create an icon on top of each video.
          createOneIcon(videoTop, videoLeft, videoWidth, videoHeight, iconHeight);

          // For the voice animation on the last video.
          if (i === 0) {
            animatedBar = document.createElement('icon');
            iconBottom = videoTop + iconHeight * 2;
            createVoiceAnimationBar(animatedBar, videoTop, videoLeft,
              videoWidth, videoHeight, iconHeight);
          }
        }
      }

      // Create one small video at the upper right corner to similate the one
      // from the local camera (640x360).
      createLocalVideoAndIcon(videoWidth, videoHeight, codec);

      // Start the voice icon animation.
      const frameTime30Fps = 32;  // ms
      let lastTimestamp = performance.now();
      let barHeight = 0;

      // Voice bar animation at 30 FPS
      function voiceBarAnimation(timestamp) {
        const elapsed = timestamp - lastTimestamp;
        if (elapsed < frameTime30Fps) {
          window.requestAnimationFrame(voiceBarAnimation);
          return;
        }
        lastTimestamp = timestamp;

        const maxBarHeight = iconHeight - 4;
        barHeight = barHeight + (maxBarHeight / 10);
        if (barHeight > maxBarHeight)
          barHeight = maxBarHeight / 10;

        animatedBar.style.top = iconBottom - barHeight;
        animatedBar.style.height = barHeight;

        window.requestAnimationFrame(voiceBarAnimation);
      }

      window.requestAnimationFrame(voiceBarAnimation);
    }

    function createOneVideo(top, left, width, height, videoSrc, hasBorder) {
      const video = document.createElement('video');
      video.loop = true;
      video.autoplay = true;
      video.muted = true;
      video.src = videoSrc;
      video.width = width;
      video.height = height;
      video.style.position = "absolute";
      video.style.top = top;
      video.style.left = left;
      if (hasBorder) {
        const borderWidth = 3;
        video.style.borderWidth = borderWidth;
        video.width = width - borderWidth * 2;
        video.height = height - borderWidth * 2;
        video.style.borderStyle = "solid";
        video.style.borderColor = "rgba(90, 129, 193, 255)";
      }
      video.play();

      container.appendChild(video);
    }

    function createOneIcon(videoTop, videoLeft, videoWidth, videoHeight, iconHeight) {
      const icon = document.createElement('icon');

      icon.style.backgroundColor = "rgba(29, 110, 216, 255)";
      icon.style.position = "absolute";
      icon.style.width = iconHeight;
      icon.style.height = iconHeight;
      icon.style.top = videoTop + iconHeight;
      icon.style.left = videoLeft + videoWidth - iconHeight * 2;

      container.appendChild(icon);
    }

    function createLocalVideoAndIcon(videoWidth, videoHeight, codec) {
      const smallVideoWidth = videoWidth / 3;
      const smallVideoHeight = videoHeight / 3;
      const iconHeight = smallVideoHeight / 8;
      const videoSrc = GetVideoSource(1, 1, codec);

      createOneVideo(videoHeight - smallVideoHeight,
        _totalVideoWidth - smallVideoWidth, smallVideoWidth,
        smallVideoHeight, videoSrc, false);

      createOneIcon(videoHeight - smallVideoHeight, _totalVideoWidth -
        smallVideoWidth, smallVideoWidth, smallVideoHeight, iconHeight);
    }


    function createVoiceAnimationBar(bar, videoTop, videoLeft, videoWidth,
      videoHeight, iconHeight) {
      bar.style.backgroundColor = "white";
      bar.style.position = "absolute";
      bar.style.width = 5;
      bar.style.height = iconHeight - 4;
      bar.style.top = videoTop + iconHeight + 4;
      bar.style.left = videoLeft + videoWidth - iconHeight * 1.5 - 2;

      container.appendChild(bar);
    }


  </script>
</head>

<body>
  <div id="container" style="position:absolute; top:0px; left:0px">
  </div>
  <script>startMxNVideos();</script>
</body>

</html>