chromium/media/test/data/hls/hls_support_matrix.html

<html>
<head>
  <style>
    #player {
      width:320px;
      height:180px;
      position:fixed;
      top:0;
      left:0;
      overflow: hidden;
    }
    #support_list {
      width:320px;
      position:fixed;
      top:180px;
      left:0;
      font-size:10px;
      overflow:hidden;
      white-space:nowrap;
    }
    #charts {
      position:fixed;
      top:0;
      left:320px;
    }
    video {
      width:320px;
      height:180px;
    }
    ul {margin:0; padding:0;}
    .green { background-color: green; }
    .red { background-color: red; }
    .chart {
      display: grid;
      font-size: 10px;
    }
  </style>
</head>
<body>
  <div id="player"></div>
  <ul id="support_list"></ul>
  <template id="entry">
    <li class="entry"></li>
  </template>
  <div id="charts">
    <div class="chart" id="istypesupported"></div>
    <div class="chart" id="canplaytype"></div>
  </div>
</body>
<script>
  const ok_event = "canplaythrough";

  const content_definitions = [
    {
      type: "audio",
      name: "vod_aac_0AAC",
      location: "vod_aac_0AAC/chunklist.m3u8"
    },
    {
      type: "video",
      name: "vod_fmp4_0AAC",
      location: "vod_fmp4_0AAC/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_fmp4_0AC3",
      location: "vod_fmp4_0AC3/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_fmp4_0FLAC",
      location: "vod_fmp4_0FLAC/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_fmp4_0H264",
      location: "vod_fmp4_0H264/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_fmp4_0H265",
      location: "vod_fmp4_0H265/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_fmp4_0VP9",
      location: "vod_fmp4_0VP9/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_mpegts_0AAC",
      location: "vod_mpegts_0AAC/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_mpegts_0AC3",
      location: "vod_mpegts_0AC3/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_mpegts_0FLAC",
      location: "vod_mpegts_0FLAC/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_mpegts_0H264",
      location: "vod_mpegts_0H264/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_mpegts_0H265",
      location: "vod_mpegts_0H265/playlist.m3u8"
    },
    {
      type: "audio",
      name: "vod_aac_0AAC",
      location: "vod_aac_0AAC/playlist.m3u8"
    },
    {
      type: "video",
      name: "vod_aes128_mpegts_AAC_H264",
      location: "https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/m3u8s/11331.m3u8",
    },
    {
      type: "video",
      name: "vod_aes128_fmp4_AAC_H264",
      location: "https://storage.googleapis.com/shaka-demo-assets/sintel-fmp4-aes/master.m3u8",
    },
    {
      type: "video",
      name: "vod_aes128rot_fmp4_AAC_H264",
      location: "https://storage.googleapis.com/shaka-demo-assets/sintel-ts-aes-key-rotation/master.m3u8",
    },
    {
      type: "video",
      name: "vod_wv_fmp4_MP4A_H264",
      location: "https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8",
    },
    {
      type: "video",
      name: "vod_ck_fmp4_MP4A_H264",
      location: "https://storage.googleapis.com/shaka-demo-assets/angel-one-sample-aes-ctr-multiple-key/manifest.m3u8",
    },
  ];

  function attach_listener(element, event, fn) {
    if (element.listeners === undefined) {
      element.listeners = {};
    }
    element.listeners[event] = fn;
    element.addEventListener(event, fn);
  }

  function detach_listener(element, event) {
    if (element.listeners === undefined) return;
    if (!(event in element.listeners)) return;
    element.removeEventListener(event, element.listeners[event]);
  }

  function create_video_element(name, src, element, done) {
    const video = document.createElement(element);
    attach_listener(video, "error", add_entry.bind(null, video, name, "red", done));
    var on_seek = add_entry.bind(null, video, name, "green", done);
    if (navigator.userAgent.match(/Android/i)) {
      on_seek = attach_listener.bind(null, video, "durationchange", on_seek);
    }
    attach_listener(video, "seeked", on_seek);
    video.src = src;
    video.currentTime = 0.1;
    return video;
  }

  function add_entry(video, name, color, done, error) {
    detach_listener(video, "error");
    detach_listener(video, "seeked");
    detach_listener(video, "durationchange");
    const template = document.getElementById("entry");
    const clone = template.cloneNode(true);
    const entry = clone.content.querySelector("li");
    entry.textContent = name;
    entry.classList.add(color);
    document.getElementById("support_list").appendChild(entry);
    setTimeout(done, 10);
  }

  function load_video(name, src, element, done) {
    const container = document.getElementById("player");
    container.innerHTML = '';
    container.appendChild(create_video_element(name, src, element, done));
  }

  function load_sequential_video(content, done) {
    if (content.length === 0) return done();
    const spec = content.pop();
    load_video(spec.name, spec.location, spec.type,
               load_sequential_video.bind(null, content, done))
  }

  function load_videos() {
    const urlParams = new URLSearchParams(window.location.search);
    const video_param = urlParams.get('video');
    let videos = content_definitions;
    if (video_param != null) {
      videos = videos.filter((v) => {
        return v.name == video_param;
      });
    }
    load_sequential_video(videos, () => {
      console.log('DONE!');
    });
  }

  load_videos();

  function genchart(element, spec) {
      var columngap_px = 0;
      var rowgap_px = 0;
      const xlabel_count = spec["xaxis"]["labels"].length;
      const ylabel_count = spec["yaxis"]["labels"].length;

      const invisibox11 = document.createElement("div");
      invisibox11.style.borderRight = "1px dashed #000";
      invisibox11.style.borderBottom = "1px dashed #000";
      invisibox11.style.borderLeft = "1px solid #444";
      invisibox11.style.borderTop = "1px solid #444";
      invisibox11.style.gridColumn = "2";
      invisibox11.style.gridRow = "2";
      invisibox11.style.textAlign = "center";
      invisibox11.textContent = spec["title"];

      const ytitle_box = document.createElement("span");
      ytitle_box.textContent = spec["yaxis"]["title"];
      ytitle_box.style.gridColumn = "1";
      ytitle_box.style.gridRow = `3 / ${ylabel_count+3}`;
      ytitle_box.style.writingMode = "vertical-lr";
      ytitle_box.style.textAlign = "center";
      ytitle_box.style.lineHeight = "2em";
      ytitle_box.style.borderRight = "1px solid #444";

      const xtitle_box = document.createElement("span");
      xtitle_box.textContent = spec["xaxis"]["title"];
      xtitle_box.style.gridColumn = `3 / ${xlabel_count+3}`;
      xtitle_box.style.gridRow = "1";
      xtitle_box.style.textAlign = "center";
      xtitle_box.style.lineHeight = "2em";
      xtitle_box.style.borderBottom = "1px solid #444";
      xtitle_box.style.writingMode = "horizontal-tb";

      element.appendChild(ytitle_box);
      element.appendChild(xtitle_box);
      element.appendChild(invisibox11);

      for (var i = 0; i<spec["yaxis"]["labels"].length; i++) {
        const ylabel = document.createElement("span");
        ylabel.textContent = spec["yaxis"]["labels"][i];
        ylabel.style.gridColumn = "2";
        ylabel.style.gridRow = `${i+3}`;
        ylabel.style.lineHeight = "2em";
        ylabel.style.textAlign = "right";
        ylabel.style.borderBottom = "1px dashed #000";
        ylabel.style.borderRight = "1px dashed #000";
        ylabel.style.textAlign = "center";
        element.appendChild(ylabel);
        columngap_px = Math.max(columngap_px, spec["yaxis"]["labels"][i].length * 1);
      }

      for (var i = 0; i<spec["xaxis"]["labels"].length; i++) {
        const xlabel = document.createElement("span");
        xlabel.textContent = spec["xaxis"]["labels"][i];
        xlabel.style.gridRow = "2";
        xlabel.style.gridColumn = `${i+3}`;
        xlabel.style.lineHeight = "2em";
        xlabel.style.textAlign = "right";
        xlabel.style.writingMode = "vertical-lr";
        xlabel.style.borderRight = "1px dashed #000";
        xlabel.style.borderBottom = "1px dashed #000";
        xlabel.style.textAlign = "center";
        element.appendChild(xlabel);
        rowgap_px = Math.max(rowgap_px, spec["xaxis"]["labels"][i].length * 1);
      }

      element.style.gridTemplateRows = `2em ${rowgap_px}ch repeat(${ylabel_count}, 2em)`;
      element.style.gridTemplateColumns = `2em ${columngap_px}ch repeat(${xlabel_count}, 2em)`;

      var element_grid_result = []

      for (var x = 0; x<spec["xaxis"]["labels"].length; x++) {
        var element_column_result = [];
        for (var y = 0; y<spec["yaxis"]["labels"].length; y++) {
          const box = document.createElement("div");
          box.style.gridRow = `${y+3}`;
          box.style.gridColumn = `${x+3}`;
          box.style.borderBottom = "1px dashed #000";
          box.style.borderRight = "1px dashed #000";

          spec["test"](spec["xaxis"]["labels"][x], spec["yaxis"]["labels"][y],
            (color="#3F5") => {
              box.style.backgroundColor = color;
            }, () => {
              box.style.backgroundColor = "#F21";
            })

          element_column_result.push(box);
          element.appendChild(box);
        }
        element_grid_result.push(element_column_result);
      }

      return element_grid_result;
    }

    const xaxis = {
      "title": "codecs",
      "labels": [
        // Taken from https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
        "mp4a.40",     // AAC indeterminate profile
        "mp4a.40.2",   // AAC-LC
        "mp4a.40.5",   // HE-AAC
        "mp4a.40.34",  // MP3
        "avc1",        // h264 indeterminate profile
        "avc1.42001e", // h264 baseline 3.0
        "avc1.66.30",
        "avc1.42001f", // h264 baseline 3.1
        "avc1.4d001e", // h264 main 3.0
        "avc1.77.30",
        "avc1.4d001f", // h264 main 3.1
        "avc1.4d0028", // h264 main 4.0
        "avc1.64001f", // h264 high 3.1
        "avc1.640028", // h264 high 4.0
        "avc1.640029", // h264 high 4.1
        "avc1.42E01E", // h264 constrained 3.0
        "hev1.1.6.L93.B0", // hevc (requires hardware support)
      ],
    };

    const yaxis = {
      "title": "container",
      "labels": ["application/vnd.apple.mpegurl", "application/x-mpegURL", "vnd.apple.mpegURL", "video/hls", "video/m3u8", "video/mp4", "video/mp2t"]
    };

    genchart(document.getElementById("istypesupported"), {
      "title": "MS::isTypeSupported",
      "xaxis": xaxis,
      "yaxis": yaxis,
      "test": function(xcodec, ycontainer, pass, fail) {
        const query = `${ycontainer};codecs="${xcodec}"`;
        console.log(`isTypeSupported('${query}')`);
        if (MediaSource.isTypeSupported(query)) {
          pass();
        } else {
          fail();
        }
      }
    });


    genchart(document.getElementById("canplaytype"), {
      "title": "CanPlayType",
      "xaxis": xaxis,
      "yaxis": yaxis,
      "test": function(xcodec, ycontainer, pass, fail) {
        const tester = document.createElement("video");
        const query = `${ycontainer};codecs="${xcodec}"`;
        console.log(`canPlayType('${query}')`);
        switch (tester.canPlayType(query)) {
          case "probably": {
            pass();
            break;
          }
          case "maybe": {
            pass("#EF2");
            break;
          }
          default: {
            fail();
          }
        }
      }
    });
</script>
</html>