[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  'extends': 'google',\n  'parserOptions': {\n    'ecmaVersion': 2017,\n    'sourceType': 'module',\n  },\n  'env': {\n    'browser': true,\n    'es6': true,\n    'node': true,\n    'jest': true\n  },\n  'rules': {\n    'max-len': 'off',\n    'require-jsdoc': 'off',\n    'arrow-parens': 'off',\n    'comma-dangle': 'off',\n    'no-throw-literal': 'off',\n    'camelcase': 'off',\n    'prefer-rest-params': 'off',\n    'no-invalid-this': 'off',\n    'eol-last': 'off',\n    'no-undef': 2,\n  },\n  \"globals\": {\n    \"adapter\": true,\n    \"browserSupportsIPHandlingPolicy\": true,\n    \"browserSupportsNonProxiedUdpBoolean\": true,\n    \"chrome\": true,\n    \"ga\": true,\n    \"getPolicyFromBooleans\": true,\n    \"importScripts\": true,\n    // From WebGPU specification\n    \"GPUBufferUsage\": true,\n    \"GPUTextureUsage\": true,\n    // From Streams specification\n    \"TransformStream\": true,\n    // From WebCodec specification\n    \"AudioData\": true,\n    \"AudioEncoder\": true,\n    \"AudioDecoder\": true,\n    \"VideoFrame\": true,\n    \"VideoEncoder\": true,\n    \"VideoDecoder\": true,\n  },\n  \"plugins\": [\"jest\"]\n};\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n# Please read first!\nPlease use [discuss-webrtc](https://groups.google.com/forum/#!forum/discuss-webrtc) for general technical discussions and questions.\nIf you have found an issue/bug with the native `libwebrtc` SDK or a browser's behaviour around WebRTC please create an issue in the relevant bug tracker. You can find more information on how to submit a bug and do so in the right place [here](https://webrtc.googlesource.com/src/+/refs/heads/main/docs/bug-reporting.md)\n\n- [ ] I understand that issues created here are _only_ relevant to the samples in this repo - not browser or SDK bugs \n- [ ] I have provided steps to reproduce\n- [ ] I have provided browser name and version\n- [ ] I have provided a link to the sample here or a modified version thereof\n\n**Note: If the checkboxes above are not checked (which you do after the issue is posted), the issue will be closed.**\n\n## Browser affected\n\n**Browser name including version (e.g. Chrome 64.0.3282.119)**\n\n\n## Description\n\n\n## Steps to reproduce\n\n\n## Expected results\n\n\n## Actual results\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Please use the discuss-webrtc mailing list for general questions\n    url: https://groups.google.com/g/discuss-webrtc\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "**Description**\n\n\n**Purpose**\n"
  },
  {
    "path": ".github/workflows/interop-tests.yml",
    "content": "on:\r\n  schedule:\r\n    - cron: \"30 5 * * *\"\r\n\r\njobs:\r\n  interop:\r\n    runs-on: ubuntu-22.04\r\n    timeout-minutes: 5\r\n    strategy:\r\n      fail-fast: false\r\n      matrix:\r\n        browserA: [chrome, firefox]\r\n        browserB: [firefox, chrome]\r\n        bver: [unstable]\r\n    steps:\r\n    - uses: actions/checkout@v4\r\n    - uses: actions/setup-node@v4\r\n    - run: npm install\r\n    - run: sudo rm /usr/bin/chromedriver /usr/bin/geckodriver # remove preinstalled github chromedriver/geckodriver from $PATH\r\n    - run: Xvfb :99 &\r\n    - run: BROWSER_A=${{matrix.browserA}} BROWSER_B=${{matrix.browserB}} BVER=${{matrix.bver}} DISPLAY=:99.0 node test/download-browsers.js\r\n    - run: BROWSER_A=${{matrix.browserA}} BROWSER_B=${{matrix.browserB}} BVER=${{matrix.bver}} DISPLAY=:99.0 node_modules/.bin/jest --retries=3 test/interop/\r\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: lint-and-test\n\non: [pull_request]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - uses: actions/setup-node@v4\n    - run: npm install\n    - run: npm run eslint\n    - run: npm run stylelint\n  test:\n    needs: lint\n    runs-on: ubuntu-22.04\n    timeout-minutes: 5\n    strategy:\n      matrix:\n        browser: [chrome]\n        version: [stable]\n    steps:\n    - uses: actions/checkout@v4\n    - uses: actions/setup-node@v4\n    - run: npm install\n    - run: sudo rm /usr/bin/chromedriver # remove preinstalled github chromedriver from $PATH\n    - run: Xvfb :99 &\n    - run: BROWSER=${{matrix.browser}} BVER=${{matrix.version}} DISPLAY=:99.0 npm run jest -- --retries=3\n"
  },
  {
    "path": ".gitignore",
    "content": "browsers*\n.eslintcache\nfirefox-*.tar.bz2\nnode_modules\n.DS_Store\nvalidation-report.json\nvalidation-status.json\n.idea\nfirefox_profile/*\ntests_output\n*.log\n*~\n\\#*#"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": ".stylelintrc",
    "content": "{\n  \"extends\": \"stylelint-config-recommended\"\n}"
  },
  {
    "path": "AUTHORS",
    "content": "The WebRTC Project Authors\nThe Chromium Authors\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# WebRTC welcomes patches/pulls for features and bug fixes!\n\nFor contributors external to Google, follow the instructions given in the\n[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual).\nIn all cases, contributors must sign a contributor license agreement before a contribution can be accepted.\nPlease complete the agreement for an [individual](https://developers.google.com/open-source/cla/individual) or\na [corporation](https://developers.google.com/open-source/cla/corporate) as appropriate.\n\nIf you plan to add a new sample or make significant changes to an existing sample, we recommend that you start by creating\na [new issue](https://github.com/webrtc/samples/issues/new) where we can discuss the details.\n\n# How to start developing a patch, new feature or bug fix\n## Clone the repo in desired folder\n```bash\ngit clone https://github.com/webrtc/samples.git\n```\n\n## Install npm dependencies\n```bash\nnpm install\n```\n\n## Start web server for development\n```bash\nnpm start\n```\n\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2014, The WebRTC project authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n  * Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n\n  * Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in\n    the documentation and/or other materials provided with the\n    distribution.\n\n  * Neither the name of Google nor the names of its contributors may\n    be used to endorse or promote products derived from this software\n    without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# WebRTC Code Samples\n\nThis is a repository for the WebRTC JavaScript code samples. All of the samples can be tested from [webrtc.github.io/samples](https://webrtc.github.io/samples).\n\nTo run the samples locally\n```\nnpm install && npm start\n```\nand open your browser on the page indicated.\n\n## Contributing\nWe welcome contributions and bugfixes. Please see [CONTRIBUTING.md](https://github.com/webrtc/samples/blob/gh-pages/CONTRIBUTING.md) for details.\n\n## Bugs\n\nIf you encounter a bug or problem with one of the samples, please submit a\n[new issue](https://github.com/webrtc/samples/issues/new) so we know about it and can fix it.\n\nPlease avoid submitting issues on this repository for general problems you have with WebRTC. If you have found a bug in\nthe WebRTC APIs, please see [webrtc.org/bugs](https://webrtc.org/support/bug-reporting) for how to submit bugs on the affected browsers.\nIf you need support on how to implement your own WebRTC-based application, please see the\n[discuss-webrtc](https://groups.google.com/forum/#!forum/discuss-webrtc) Google Group.\n\n"
  },
  {
    "path": "google1b7eb21c5b594ba0.html",
    "content": "google-site-verification: google1b7eb21c5b594ba0.html"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC Javascript code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"src/images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>WebRTC samples</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"src/images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"src/css/main.css\"/>\n\n    <style>\n        h2 {\n            font-size: 1.5em;\n            font-weight: 500;\n        }\n\n        h3 {\n            border-top: none;\n        }\n\n        section {\n            border-bottom: 1px solid #eee;\n            margin: 0 0 1.5em 0;\n            padding: 0 0 1.5em 0;\n        }\n\n        section:last-child {\n            border-bottom: none;\n            margin: 0;\n            padding: 0;\n        }\n    </style>\n</head>\n\n<body>\n<div id=\"container\">\n\n    <h1>WebRTC samples</h1>\n\n    <section>\n\n        <p>\n            This is a collection of small samples demonstrating various parts of the <a\n                href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API\">WebRTC APIs</a>. The code for all\n            samples are available in the <a href=\"https://github.com/webrtc/samples\">GitHub repository</a>.\n        </p>\n\n        <p>Most of the samples use <a href=\"https://github.com/webrtc/adapter\">adapter.js</a>, a shim to insulate apps\n            from spec changes and prefix differences.</p>\n\n        <p><a href=\"https://webrtc.org/getting-started/testing\" title=\"Command-line flags for WebRTC testing\">https://webrtc.org/getting-started/testing</a>\n            lists command line flags useful for development and testing with Chrome.</p>\n\n        <p>Patches and issues welcome! See <a href=\"https://github.com/webrtc/samples/blob/gh-pages/CONTRIBUTING.md\">CONTRIBUTING.md</a>\n            for instructions.</p>\n\n        <p class=\"warning\"><strong>Warning:</strong> It is highly recommended to use headphones when testing these\n            samples, as it will otherwise risk loud audio feedback on your system.</p>\n    </section>\n\n    <section>\n\n        <h2 id=\"getusermedia\"><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\">getUserMedia():</a>\n        </h2>\n        <p class=\"description\">Access media devices</p>\n        <ul>\n            <li><a href=\"src/content/getusermedia/gum/\">Basic getUserMedia demo</a></li>\n\n            <li><a href=\"src/content/getusermedia/canvas/\">Use getUserMedia with canvas</a></li>\n\n            <li><a href=\"src/content/getusermedia/filter/\">Use getUserMedia with canvas and CSS filters</a></li>\n\n            <li><a href=\"src/content/getusermedia/resolution/\">Choose camera resolution</a></li>\n\n            <li><a href=\"src/content/getusermedia/audio/\">Audio-only getUserMedia() output to local audio element</a>\n            </li>\n\n            <li><a href=\"src/content/getusermedia/volume/\">Audio-only getUserMedia() displaying volume</a></li>\n\n            <li><a href=\"src/content/getusermedia/record/\">Record stream</a></li>\n\n            <li><a href=\"src/content/getusermedia/getdisplaymedia/\">Screensharing with getDisplayMedia</a></li>\n\n            <li><a href=\"src/content/getusermedia/pan-tilt-zoom/\">Control camera pan, tilt, and zoom</a></li>\n\n            <li><a href=\"src/content/getusermedia/exposure/\">Control exposure</a></li>\n        </ul>\n        <h2 id=\"devices\">Devices:</h2>\n        <p class=\"description\">Query media devices</p>\n        <ul>\n            <li><a href=\"src/content/devices/input-output/\">Choose camera, microphone and speaker</a></li>\n\n            <li><a href=\"src/content/devices/multi/\">Choose media source and audio output</a></li>\n        </ul>\n\n        <h2 id=\"capture\">Stream capture:</h2>\n        <p class=\"description\">Stream from canvas or video elements</p>\n        <ul>\n\n            <li><a href=\"src/content/capture/video-video/\">Stream from a video element to a video element</a></li>\n\n            <li><a href=\"src/content/capture/video-pc/\">Stream from a video element to a peer connection</a></li>\n\n            <li><a href=\"src/content/capture/canvas-video/\">Stream from a canvas element to a video element</a></li>\n\n            <li><a href=\"src/content/capture/canvas-pc/\">Stream from a canvas element to a peer connection</a></li>\n\n            <li><a href=\"src/content/capture/canvas-record/\">Record a stream from a canvas element</a></li>\n\n            <li><a href=\"src/content/capture/video-contenthint/\">Guiding video encoding with content hints</a></li>\n        </ul>\n\n        <h2 id=\"peerconnection\"><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection:</a>\n        </h2>\n        <p class=\"description\">Controlling peer connectivity</p>\n        <ul>\n            <li><a href=\"src/content/peerconnection/pc1/\">Basic peer connection demo in a single tab</a></li>\n\n            <li><a href=\"src/content/peerconnection/channel/\">Basic peer connection demo between two tabs</a></li>\n\n            <li><a href=\"src/content/peerconnection/always-negotiate-datachannels/\">Always negotiate datachannels</a></li>\n\n            <li><a href=\"src/content/peerconnection/perfect-negotiation/\">Peer connection using Perfect Negotiation</a></li>\n\n            <li><a href=\"src/content/peerconnection/audio/\">Audio-only peer connection demo</a></li>\n\n            <li><a href=\"src/content/peerconnection/bandwidth/\">Change bandwidth on the fly</a></li>\n\n            <li><a href=\"src/content/peerconnection/change-codecs/\">Change codecs before the call</a></li>\n\n            <li><a href=\"src/content/peerconnection/upgrade/\">Upgrade a call and turn video on</a></li>\n\n            <li><a href=\"src/content/peerconnection/multiple/\">Multiple peer connections at once</a></li>\n\n            <li><a href=\"src/content/peerconnection/multiple-relay/\">Forward the output of one PC into another</a></li>\n\n            <li><a href=\"src/content/peerconnection/munge-sdp/\">Munge SDP parameters</a></li>\n\n            <li><a href=\"src/content/peerconnection/pr-answer/\">Use pranswer when setting up a peer connection</a></li>\n\n            <li><a href=\"src/content/peerconnection/constraints/\">Constraints and stats</a></li>\n\n            <li><a href=\"src/content/peerconnection/per-frame-callback/\">RTCPeerConnection and requestVideoFrameCallback()</a></li>\n\n            <li><a href=\"src/content/peerconnection/create-offer/\">Display createOffer output for various scenarios</a>\n            </li>\n\n            <li><a href=\"src/content/peerconnection/dtmf/\">Use RTCDTMFSender</a></li>\n\n            <li><a href=\"src/content/peerconnection/states/\">Display peer connection states</a></li>\n\n            <li><a href=\"src/content/peerconnection/trickle-ice/\">ICE candidate gathering from STUN/TURN servers</a>\n            </li>\n\n            <li><a href=\"src/content/peerconnection/restart-ice/\">Do an ICE restart</a></li>\n\n            <li><a href=\"src/content/peerconnection/webaudio-input/\">Web Audio output as input to peer connection</a>\n            </li>\n\n            <li><a href=\"src/content/peerconnection/webaudio-output/\">Peer connection as input to Web Audio</a></li>\n            <li><a href=\"src/content/peerconnection/negotiate-timing/\">Measure how long renegotiation takes</a></li>\n            <li><a href=\"src/content/extensions/svc/\">Choose scalablilityMode before call - Scalable Video Coding (SVC) Extension </a></li>\n        </ul>\n        <h2 id=\"datachannel\"><a\n                href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel\">RTCDataChannel:</a></h2>\n        <p class=\"description\">Send arbitrary data over peer connections</p>\n        <ul>\n            <li><a href=\"src/content/datachannel/basic/\">Transmit text</a></li>\n\n            <li><a href=\"src/content/datachannel/filetransfer/\">Transfer a file</a></li>\n\n            <li><a href=\"src/content/datachannel/datatransfer/\">Transfer data</a></li>\n\n            <li><a href=\"src/content/datachannel/channel/\">Basic datachannel demo between two tabs</a></li>\n\n            <li><a href=\"src/content/datachannel/messaging/\">Messaging</a></li>\n        </ul>\n\n        <h2 id=\"videoChat\">Video chat:</h2>\n        <p class=\"description\">Full featured WebRTC application</p>\n        <ul>\n\n            <li><a href=\"https://github.com/webrtc/apprtc/\">AppRTC video chat client</a> that you can run out of a Docker image</li>\n\n        </ul>\n\n        <h2 id=\"capture\">Insertable Streams:</h2>\n        <p class=\"description\">API for processing media</p>\n        <ul>\n            <li><a href=\"src/content/insertable-streams/endtoend-encryption\">End to end encryption using WebRTC Insertable Streams</a></li> (Experimental)\n            <li><a href=\"src/content/insertable-streams/video-analyzer\">Video analyzer using WebRTC Insertable Streams</a></li> (Experimental)\n            <li><a href=\"src/content/insertable-streams/video-processing\">Video processing using MediaStream Insertable Streams</a></li> (Experimental)\n            <li><a href=\"src/content/insertable-streams/audio-processing\">Audio processing using MediaStream Insertable Streams</a></li> (Experimental)\n            <li><a href=\"src/content/insertable-streams/video-crop\">Video cropping using MediaStream Insertable Streams in a Worker</a></li> (Experimental)\n            <li><a href=\"src/content/insertable-streams/webgpu\">Integrations with WebGPU for custom video rendering:</a></li> (Experimental)\n        </ul>\n\n    </section>\n\n</div>\n\n<script src=\"src/js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"webrtc-samples\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"description\": \"Project checking for WebRTC GitHub samples repo\",\n  \"keywords\": [\n    \"webrtc\"\n  ],\n  \"homepage\": \"https://webrtc.github.io/samples/\",\n  \"bugs\": {\n    \"url\": \"https://github.com/webrtc/samples/issues\"\n  },\n  \"license\": \"BSD-3-Clause\",\n  \"author\": \"The WebRTC project authors\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/webrtc/samples.git\"\n  },\n  \"scripts\": {\n    \"start\": \"http-server . -c-1 -a 127.0.0.1\",\n    \"test\": \"npm run eslint && npm run stylelint\",\n    \"eslint\": \"eslint 'test/**.js' 'src/content/**/*.js'\",\n    \"jest\": \"node test/download-browsers.js && jest --testTimeout 5000 --maxWorkers=1 src/content/\",\n    \"stylelint\": \"stylelint 'src/**/*.css'\"\n  },\n  \"eslintIgnore\": [\n    \"'**/third_party/*.js'\"\n  ],\n  \"devDependencies\": {\n    \"@puppeteer/browsers\": \"^2.2.0\",\n    \"eslint\": \"^8.9.0\",\n    \"eslint-config-google\": \"^0.14.0\",\n    \"eslint-plugin-jest\": \"^27.4.0\",\n    \"http-server\": \"^14.1.0\",\n    \"jest\": \"^29.7.0\",\n    \"selenium-webdriver\": \"^4.19.0\",\n    \"stylelint\": \"^14.5.3\",\n    \"stylelint-config-recommended\": \"^7.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/content/capture/canvas-filter/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\ncanvas {\n    background-color: #ccc;\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    vertical-align: top;\n}\n\nvideo {\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    object-fit: cover;\n}\n"
  },
  {
    "path": "src/content/capture/canvas-filter/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Video to Canvas to video</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Stream camera via a canvas to a video element</span>\n    </h1>\n\n    <video id=\"source\" autoplay></video>\n    <canvas id=\"canvas-source\" style=\"display:none\"></canvas>\n    <canvas id=\"canvas-result\" style=\"display:none\"></canvas>\n\n    <video id=\"result\" autoplay></video>\n\n    <p>The camera is captured to a video element, which is mapped onto a\n      canvas, and a red square is added.</p>\n    <p>The canvas is then captured to an ImageData object, and painted\n      onto a second canvas.</p>\n    <p>A stream is captured from the second canvas element using its\n        <code>captureStream()</code> method and set as the <code>srcObject</code> of the video element.</p>\n\n    <p>The <code>inputStream</code>, <code>source</code>,\n      <code>canvasIn</code>, <code>canvasOut</code>,\n      <code>result</code>, and <code>stream</code> variables are in global\n      scope, so you can\n      inspect them from the browser console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/canvas-filter\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/canvas-filter/js/main.js",
    "content": "/*\n*  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\nconst source = document.querySelector('#source');\n// TODO(hta): Use OffscreenCanvas for the intermediate canvases.\nconst canvasIn = document.querySelector('#canvas-source');\nconst canvasOut = document.querySelector('#canvas-result');\nconst result = document.querySelector('#result');\n\nconst stream = canvasOut.captureStream();\nlet inputStream = null;\nlet imageData = null;\n\nresult.srcObject = stream;\n\nfunction loop() {\n  if (source.videoWidth > 0 && source.videoHeight > 0) {\n    canvasIn.width = source.videoWidth;\n    canvasIn.height = source.videoHeight;\n    const ctx = canvasIn.getContext('2d');\n    ctx.drawImage(source, 0, 0);\n    // Put a red square into the image, to mark it as \"processed\".\n    ctx.fillStyle = '#FF0000';\n    ctx.fillRect(10, 10, 80, 80);\n    imageData = ctx.getImageData(0, 0, canvasIn.width, canvasIn.height);\n    // At this point, we have data that can be transferred.\n    // We paint it on the second canvas.\n    canvasOut.width = source.videoWidth;\n    canvasOut.height = source.videoHeight;\n    const outCtx = canvasOut.getContext('2d');\n    outCtx.putImageData(imageData, 0, 0);\n  }\n  window.requestAnimationFrame(loop);\n}\n\n(async () => {\n  inputStream = await navigator.mediaDevices.getUserMedia({video: true});\n  source.srcObject = inputStream;\n  source.play();\n  result.play();\n  window.requestAnimationFrame(loop);\n})();\n"
  },
  {
    "path": "src/content/capture/canvas-pc/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\ncanvas {\n  background-color: #ccc;\n  --width: calc(45%);\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 1em;\n  vertical-align: top;\n}\n\nvideo {\n  --width: calc(45%);\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 1em;\n  object-fit: cover;\n}\n\n#autoplay {\n  display: none;\n}\n"
  },
  {
    "path": "src/content/capture/canvas-pc/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Canvas to peer connection</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1>\n        <a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Stream from canvas to peer connection</span>\n    </h1>\n\n    <canvas></canvas>\n    <video playsinline autoplay muted></video>\n    <div id=\"autoplay\">Due to autoplay policy the video seems not to be playing. Clicking the left teapot usually resolves this.</div>\n\n    <p>Click and drag on the canvas element (on the left) to move the teapot.</p>\n\n    <p>This demo requires Firefox 47 or Chrome 52 (or later).</p>\n\n    <p>The teapot is drawn on the canvas element using WebGL. A stream is captured from the canvas using its <code>captureStream()</code>\n        method and streamed via a peer connection to the video element on the right.</p>\n\n    <p>View the browser console to see logging.</p>\n\n    <p>Several variables are in global scope, so you can inspect them from the console: <code>canvas</code>,\n        <code>video</code>, <code>pc1</code>, <code>pc2</code> and <code>stream</code>.\n    </p>\n\n    <p>For more demos and information about <code>captureStream()</code>, see <a\n            href=\"https://docs.google.com/document/d/1JmWfOtUP6ZqsYJ--U8y0OtHkBt-VyjX4N-JqIjb1t78\"\n            title=\"Implementation overview doc\">Media Capture from Canvas Implementation</a>.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/canvas-pc\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<!-- Teapot code -->\n<script src=\"../../../js/third_party/webgl_teapot/webgl-utils.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/webgl-debug.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/matrix4x4.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/cameracontroller.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/teapot-streams.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/demo.js\"></script>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/canvas-pc/js/main.js",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n/* global main */\n\n'use strict';\n\nconst canvas = document.querySelector('canvas');\nconst video = document.querySelector('video');\n\nlet pc1;\nlet pc2;\n\nlet startTime;\n\nvideo.addEventListener('loadedmetadata', function() {\n  document.getElementById('autoplay').style.display = 'none';\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nvideo.onresize = () => {\n  console.log(`Remote video size changed to ${video.videoWidth}x${video.videoHeight}`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log(`Setup time: ${elapsedTime.toFixed(3)}ms`);\n    startTime = null;\n  }\n};\n\n// Call main() in demo.js\nmain();\n\nconst stream = canvas.captureStream();\nconsole.log('Got stream from canvas');\n\ncall();\n\nfunction call() {\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = stream.getVideoTracks();\n  const audioTracks = stream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);\n  pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  stream.getTracks().forEach(\n      track => {\n        pc1.addTrack(\n            track,\n            stream\n        );\n      }\n  );\n  console.log('Added local stream to pc1');\n\n  console.log('pc1 createOffer start');\n  pc1.createOffer(onCreateOfferSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  pc1.setLocalDescription(desc, () => onSetLocalSuccess(pc1), onSetSessionDescriptionError);\n  console.log('pc2 setRemoteDescription start');\n  pc2.setRemoteDescription(desc, () => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  pc2.createAnswer(onCreateAnswerSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  if (video.srcObject !== e.streams[0]) {\n    video.srcObject = e.streams[0];\n    console.log('pc2 received remote stream');\n    if (video.paused) {\n      document.getElementById('autoplay').style.display = 'block';\n    }\n  }\n}\n\nfunction onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  pc2.setLocalDescription(desc, () => onSetLocalSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc1 setRemoteDescription start');\n  pc1.setRemoteDescription(desc, () => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc).addIceCandidate(event.candidate)\n      .then(\n          () => onAddIceCandidateSuccess(pc),\n          err => onAddIceCandidateError(pc, err)\n      );\n  console.log(`${getName(pc)} ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n  }\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n"
  },
  {
    "path": "src/content/capture/canvas-record/css/main.css",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n    margin: 0 3px 10px 0;\n    padding-left: 2px;\n    padding-right: 2px;\n    width: 99px;\n}\n\nbutton:last-of-type {\n    margin: 0;\n}\n\np.borderBelow {\n    margin: 0 0 20px 0;\n    padding: 0 0 20px 0;\n}\n\ncanvas {\n    background-color: #ccc;\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    vertical-align: top;\n}\n\nvideo {\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    object-fit: cover;\n}\n\n"
  },
  {
    "path": "src/content/capture/canvas-record/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Record canvas stream</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Record stream from a canvas</span>\n    </h1>\n\n    <canvas></canvas>\n    <video id=\"recorded\" playsinline loop></video>\n\n    <div>\n        <button id=\"record\">Start Recording</button>\n        <button id=\"play\" disabled>Play</button>\n        <button id=\"download\" disabled>Download</button>\n    </div>\n\n    <p>Click and drag on the canvas (on the left) to move the teapot.</p>\n\n    <p>This demo requires Firefox 43 or above, Chrome 51 or above, or Chrome 50 with <strong>Experimental Web Platform\n        features</strong> enabled.</p>\n\n    <p>The teapot is drawn on the canvas element using WebGL. A stream is captured from the canvas element using its\n        <code>captureStream()</code> method then recorded using the <code>MediaRecorder</code> API.</p>\n\n    <p>The <code>canvas</code>, <code>video</code>, and <code>stream</code> variables are in global scope, so you can\n        inspect them from the browser console.</p>\n\n    <p>For more demos and information about <code>captureStream()</code>, see <a\n            href=\"https://docs.google.com/document/d/1JmWfOtUP6ZqsYJ--U8y0OtHkBt-VyjX4N-JqIjb1t78\"\n            title=\"Implementation overview doc\">Media Capture from Canvas Implementation</a>.</p>\n\n    <p>For more information see the MediaStream Recording API <a\n            href=\"http://w3c.github.io/mediacapture-record/MediaRecorder.html\"\n            title=\"W3C MediaStream Recording API Editor's Draft\">Editor's&nbsp;Draft</a>.</p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/canvas-record\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<!-- Teapot code -->\n<script src=\"../../../js/third_party/webgl_teapot/webgl-utils.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/webgl-debug.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/matrix4x4.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/cameracontroller.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/teapot-streams.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/demo.js\"></script>\n\n<!-- include adapter for srcObject shim -->\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/canvas-record/js/main.js",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\n/* globals main */\n\n// This code is adapted from\n// https://rawgit.com/Miguelao/demos/master/mediarecorder.html\n\n/* globals main, MediaRecorder */\n\nconst mediaSource = new MediaSource();\nmediaSource.addEventListener('sourceopen', handleSourceOpen, false);\nlet mediaRecorder;\nlet recordedBlobs;\nlet sourceBuffer;\n\nconst canvas = document.querySelector('canvas');\nconst video = document.querySelector('video');\n\nconst recordButton = document.querySelector('button#record');\nconst playButton = document.querySelector('button#play');\nconst downloadButton = document.querySelector('button#download');\nrecordButton.onclick = toggleRecording;\nplayButton.onclick = play;\ndownloadButton.onclick = download;\n\n// Start the GL teapot on the canvas\nmain();\n\nconst stream = canvas.captureStream(); // frames per second\nconsole.log('Started stream capture from canvas element: ', stream);\n\nfunction handleSourceOpen(event) {\n  console.log('MediaSource opened');\n  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs=\"vp8\"');\n  console.log('Source buffer: ', sourceBuffer);\n}\n\nfunction handleDataAvailable(event) {\n  if (event.data && event.data.size > 0) {\n    recordedBlobs.push(event.data);\n  }\n}\n\nfunction handleStop(event) {\n  console.log('Recorder stopped: ', event);\n  const superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});\n  video.src = window.URL.createObjectURL(superBuffer);\n}\n\nfunction toggleRecording() {\n  if (recordButton.textContent === 'Start Recording') {\n    startRecording();\n  } else {\n    stopRecording();\n    recordButton.textContent = 'Start Recording';\n    playButton.disabled = false;\n    downloadButton.disabled = false;\n  }\n}\n\n// The nested try blocks will be simplified when Chrome 47 moves to Stable\nfunction startRecording() {\n  let options = {mimeType: 'video/webm'};\n  recordedBlobs = [];\n  try {\n    mediaRecorder = new MediaRecorder(stream, options);\n  } catch (e0) {\n    console.log('Unable to create MediaRecorder with options Object: ', e0);\n    try {\n      options = {mimeType: 'video/webm,codecs=vp9'};\n      mediaRecorder = new MediaRecorder(stream, options);\n    } catch (e1) {\n      console.log('Unable to create MediaRecorder with options Object: ', e1);\n      try {\n        options = 'video/vp8'; // Chrome 47\n        mediaRecorder = new MediaRecorder(stream, options);\n      } catch (e2) {\n        alert('MediaRecorder is not supported by this browser.\\n\\n' +\n          'Try Firefox 29 or later, or Chrome 47 or later, ' +\n          'with Enable experimental Web Platform features enabled from chrome://flags.');\n        console.error('Exception while creating MediaRecorder:', e2);\n        return;\n      }\n    }\n  }\n  console.log('Created MediaRecorder', mediaRecorder, 'with options', options);\n  recordButton.textContent = 'Stop Recording';\n  playButton.disabled = true;\n  downloadButton.disabled = true;\n  mediaRecorder.onstop = handleStop;\n  mediaRecorder.ondataavailable = handleDataAvailable;\n  mediaRecorder.start(100); // collect 100ms of data\n  console.log('MediaRecorder started', mediaRecorder);\n}\n\nfunction stopRecording() {\n  mediaRecorder.stop();\n  console.log('Recorded Blobs: ', recordedBlobs);\n  video.controls = true;\n}\n\nfunction play() {\n  video.play();\n}\n\nfunction download() {\n  const blob = new Blob(recordedBlobs, {type: 'video/webm'});\n  const url = window.URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.style.display = 'none';\n  a.href = url;\n  a.download = 'test.webm';\n  document.body.appendChild(a);\n  a.click();\n  setTimeout(() => {\n    document.body.removeChild(a);\n    window.URL.revokeObjectURL(url);\n  }, 100);\n}\n"
  },
  {
    "path": "src/content/capture/canvas-video/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\ncanvas {\n    background-color: #ccc;\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    vertical-align: top;\n}\n\nvideo {\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    object-fit: cover;\n}\n"
  },
  {
    "path": "src/content/capture/canvas-video/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Canvas to video</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Stream from canvas to video element</span>\n    </h1>\n\n    <canvas></canvas>\n\n    <video playsinline autoplay muted></video>\n\n    <p>Click and drag on the canvas (on the left) to move the teapot.</p>\n\n    <p>This demo requires Firefox 47 or Chrome 52 (or later).</p>\n\n    <p>The teapot is drawn on the canvas element using WebGL. A stream is captured from the canvas element using its\n        <code>captureStream()</code> method and set as the <code>srcObject</code> of the video element.</p>\n\n    <p>The <code>canvas</code>, <code>video</code>, and <code>stream</code> variables are in global scope, so you can\n        inspect them from the browser console.</p>\n\n    <p>For more demos and information about <code>captureStream()</code>, see <a\n            href=\"https://docs.google.com/document/d/1JmWfOtUP6ZqsYJ--U8y0OtHkBt-VyjX4N-JqIjb1t78\"\n            title=\"Implementation overview doc\">Media Capture from Canvas Implementation</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/canvas-video\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<!-- Teapot code -->\n<script src=\"../../../js/third_party/webgl_teapot/webgl-utils.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/webgl-debug.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/matrix4x4.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/cameracontroller.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/teapot-streams.js\"></script>\n<script src=\"../../../js/third_party/webgl_teapot/demo.js\"></script>\n\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/canvas-video/js/main.js",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n/* global main */\n\n'use strict';\n\n// Call main() in demo.js\nmain();\n\nconst canvas = document.querySelector('canvas');\nconst video = document.querySelector('video');\n\nconst stream = canvas.captureStream();\nvideo.srcObject = stream;\n"
  },
  {
    "path": "src/content/capture/video-contenthint/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\nvideo {\n  --width: calc(45%);\n  width: var(--width);\n  height: calc(var(--width) * 9 / 16);\n  margin: 1em;\n  object-fit: cover;\n}\n\n.video-container {\n  border-bottom: 1px solid grey;\n  font-style: italic;\n  margin: 20px;\n}\n\n#videos {\n  text-align: center;\n  width: 100%;\n}\n"
  },
  {
    "path": "src/content/capture/video-contenthint/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>MediaStreamTrack Content Hints</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Guiding video encoding with content hints</span>\n    </h1>\n\n    <div id=\"videos\">\n        <div class=\"video-container\">\n            <h2>Source video file (high bitrate)</h2>\n            <video id=\"srcVideo\" playsinline controls muted loop>\n                <source src=\"../../../video/mixed-content.webm\" type=\"video/webm\"/>\n                <p>This browser does not support the video element.</p>\n            </video>\n        </div>\n        <div class=\"video-container\">\n            <h2>\"motion\" video @ 50kbps</h2>\n            <video id=\"motionVideo\" playsinline autoplay muted></video>\n        </div>\n        <div class=\"video-container\">\n            <h2>\"detail\" video @ 50kbps</h2>\n            <video id=\"detailVideo\" playsinline autoplay muted></video>\n        </div>\n    </div>\n\n    <p>This demo requires Chrome 57.0.2957.0 or later with <strong>Experimental Web Platform features</strong> enabled\n        from <tt>chrome://flags</tt>.</p>\n\n    <p>A stream is captured from the source video using the <code>captureStream()</code> method. The stream is cloned\n        and transmitted via two separate PeerConnections using 50kbps of video bandwidth. This is insufficient to\n        generate good quality in the encoded bitstream, so trade-offs have to be made.</p>\n\n    <p>The transmitted stream tracks are using <a href=\"https://www.w3.org/TR/mst-content-hint/\">MediaStreamTrack\n        Content Hints</a> to indicate characteristics in the video stream, which informs PeerConnection on how to encode\n        the track (to prefer motion or individual frame detail).</p>\n\n    <p>The text part of the clip shows a clear case for when <tt>'detail'</tt> is better, and the fighting scene shows a\n        clear case for when <tt>'motion'</tt> is better. The spinning model however shows a case where <tt>'motion'</tt>\n        or <tt>'detail'</tt> are not clear-cut decisions and even with good content detection what's preferred depends\n        on what the user prefers.</p>\n\n    <p>Other MediaStreamTrack consumers such as MediaStreamRecorder can also make use of this information to guide\n        encoding parameters for the stream without additional extensions to the MediaStreamRecorder specification, but\n        this is currently not implemented in Chromium.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/video-contenthint\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/video-contenthint/js/main.js",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\nconst srcVideo = document.getElementById('srcVideo');\nconst motionVideo = document.getElementById('motionVideo');\nconst detailVideo = document.getElementById('detailVideo');\n\nlet srcStream;\nlet motionStream;\nlet detailStream;\n\nconst offerOptions = {\n  offerToReceiveAudio: 0,\n  offerToReceiveVideo: 1\n};\n\nfunction maybeCreateStream() {\n  if (srcStream) {\n    return;\n  }\n  if (srcVideo.captureStream) {\n    srcStream = srcVideo.captureStream();\n    call();\n  } else {\n    console.log('captureStream() not supported');\n  }\n}\n\n// Video tag capture must be set up after video tracks are enumerated.\nsrcVideo.oncanplay = maybeCreateStream;\nif (srcVideo.readyState >= 3) { // HAVE_FUTURE_DATA\n  // Video is already ready to play, call maybeCreateStream in case oncanplay\n  // fired before we registered the event handler.\n  maybeCreateStream();\n}\n\nsrcVideo.play();\n\nfunction setVideoTrackContentHints(stream, hint) {\n  const tracks = stream.getVideoTracks();\n  tracks.forEach(track => {\n    if ('contentHint' in track) {\n      track.contentHint = hint;\n      if (track.contentHint !== hint) {\n        console.log('Invalid video track contentHint: \\'' + hint + '\\'');\n      }\n    } else {\n      console.log('MediaStreamTrack contentHint attribute not supported');\n    }\n  });\n}\n\nfunction call() {\n  // This creates multiple independent PeerConnections instead of multiple\n  // streams on a single PeerConnection object so that b=AS (the bitrate\n  // constraints) can be applied independently.\n  motionStream = srcStream.clone();\n  // TODO(pbos): Remove fluid when no clients use it, motion is the newer name.\n  setVideoTrackContentHints(motionStream, 'fluid');\n  setVideoTrackContentHints(motionStream, 'motion');\n  establishPC(motionVideo, motionStream);\n  detailStream = srcStream.clone();\n  // TODO(pbos): Remove detailed when no clients use it, detail is the newer\n  // name.\n  setVideoTrackContentHints(detailStream, 'detailed');\n  setVideoTrackContentHints(detailStream, 'detail');\n  establishPC(detailVideo, detailStream);\n}\n\nfunction establishPC(videoTag, stream) {\n  const pc1 = new RTCPeerConnection(null);\n  const pc2 = new RTCPeerConnection(null);\n  pc1.onicecandidate = e => {\n    onIceCandidate(pc1, pc2, e);\n  };\n  pc2.onicecandidate = e => {\n    onIceCandidate(pc2, pc1, e);\n  };\n  pc2.ontrack = event => {\n    if (videoTag.srcObject !== event.streams[0]) {\n      videoTag.srcObject = event.streams[0];\n    }\n  };\n\n  stream.getTracks().forEach(track => pc1.addTrack(track, stream));\n\n  pc1.createOffer(offerOptions)\n      .then(desc => {\n        pc1.setLocalDescription(desc)\n            .then(() => pc2.setRemoteDescription(desc))\n            .then(() => pc2.createAnswer())\n            .then(answerDesc => onCreateAnswerSuccess(pc1, pc2, answerDesc))\n            .catch(onSetSessionDescriptionError);\n      })\n      .catch(e => console.log('Failed to create session description: ' + e.toString()));\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log('Failed to set session description: ' + error.toString());\n}\n\nfunction onCreateAnswerSuccess(pc1, pc2, desc) {\n  // Hard-code video bitrate to 50kbps.\n  desc.sdp = desc.sdp.replace(/a=mid:(.*)\\r\\n/g, 'a=mid:$1\\r\\nb=AS:' + 50 + '\\r\\n');\n  pc2.setLocalDescription(desc)\n      .then(() => pc1.setRemoteDescription(desc))\n      .catch(onSetSessionDescriptionError);\n}\n\nfunction onIceCandidate(pc, otherPc, event) {\n  otherPc.addIceCandidate(event.candidate);\n}\n"
  },
  {
    "path": "src/content/capture/video-pc/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\nvideo {\n  margin: 0 10px 0 0;\n  width: calc(50% - 7px);\n}\n\nvideo:last-of-type {\n  margin-right: 0;\n}\n\n@media screen and (max-width: 400px) {\n  video {\n    margin: 0 5px 20px 0;\n    width: calc(50% - 5px);\n  }\n}\n"
  },
  {
    "path": "src/content/capture/video-pc/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Video to peer connection</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Stream from a video to a peer connection</span>\n    </h1>\n\n    <video id=\"leftVideo\" playsinline controls muted>\n        <!-- NB CORS: https://bugzilla.mozilla.org/show_bug.cgi?id=1177793 -->\n        <source src=\"../../../video/chrome.webm\" type=\"video/webm\"/>\n        <source src=\"../../../video/chrome.mp4\" type=\"video/mp4\"/>\n        <p>This browser does not support the video element.</p>\n    </video>\n\n    <video id=\"rightVideo\" playsinline autoplay controls></video>\n\n    <p>This demo requires Firefox 47, Chrome 53 with <strong>Experimental Web Platform features</strong> enabled from\n        <tt>chrome://flags</tt>.</p>\n\n    <p>A stream is captured from the video on the left using the <code>captureStream()</code> method, and streamed via a\n        peer connection to the video element on the right.</p>\n\n    <p>View the browser console to see logging.</p>\n\n    <p>Several variables are in global scope, so you can inspect them from the console: <code>pc1</code>,\n        <code>pc2</code> and <code>stream</code>.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/video-pc\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/video-pc/js/main.js",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\nconst leftVideo = document.getElementById('leftVideo');\nconst rightVideo = document.getElementById('rightVideo');\n\nlet stream;\n\nlet pc1;\nlet pc2;\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nlet startTime;\n\nfunction maybeCreateStream() {\n  if (stream) {\n    return;\n  }\n  if (leftVideo.captureStream) {\n    stream = leftVideo.captureStream();\n    console.log('Captured stream from leftVideo with captureStream',\n        stream);\n    call();\n  } else if (leftVideo.mozCaptureStream) {\n    stream = leftVideo.mozCaptureStream();\n    console.log('Captured stream from leftVideo with mozCaptureStream()',\n        stream);\n    call();\n  } else {\n    console.log('captureStream() not supported');\n  }\n}\n\n// Video tag capture must be set up after video tracks are enumerated.\nleftVideo.oncanplay = maybeCreateStream;\nif (leftVideo.readyState >= 3) { // HAVE_FUTURE_DATA\n  // Video is already ready to play, call maybeCreateStream in case oncanplay\n  // fired before we registered the event handler.\n  maybeCreateStream();\n}\n\nleftVideo.play();\n\nrightVideo.onloadedmetadata = () => {\n  console.log(`Remote video videoWidth: ${rightVideo.videoWidth}px,  videoHeight: ${rightVideo.videoHeight}px`);\n};\n\nrightVideo.onresize = () => {\n  console.log(`Remote video size changed to ${rightVideo.videoWidth}x${rightVideo.videoHeight}`);\n  // We'll use the first onresize callback as an indication that\n  // video has started playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');\n    startTime = null;\n  }\n};\n\nfunction call() {\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = stream.getVideoTracks();\n  const audioTracks = stream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);\n  pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  stream.getTracks().forEach(track => pc1.addTrack(track, stream));\n  console.log('Added local stream to pc1');\n\n  console.log('pc1 createOffer start');\n  pc1.createOffer(onCreateOfferSuccess, onCreateSessionDescriptionError, offerOptions);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  pc1.setLocalDescription(desc, () => onSetLocalSuccess(pc1), onSetSessionDescriptionError);\n  console.log('pc2 setRemoteDescription start');\n  pc2.setRemoteDescription(desc, () => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  pc2.createAnswer(onCreateAnswerSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(event) {\n  if (rightVideo.srcObject !== event.streams[0]) {\n    rightVideo.srcObject = event.streams[0];\n    console.log('pc2 received remote stream', event);\n  }\n}\n\nfunction onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  pc2.setLocalDescription(desc, () => onSetLocalSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc1 setRemoteDescription start');\n  pc1.setRemoteDescription(desc, () => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc).addIceCandidate(event.candidate)\n      .then(\n          () => onAddIceCandidateSuccess(pc),\n          err => onAddIceCandidateError(pc, err)\n      );\n  console.log(`${getName(pc)} ICE candidate: \n${event.candidate ?\n    event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n  }\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n"
  },
  {
    "path": "src/content/capture/video-video/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\nvideo {\n  margin: 0 10px 0 0;\n  width: calc(50% - 7px);\n}\n\nvideo:last-of-type {\n  margin-right: 0;\n}\n\n@media screen and (max-width: 400px) {\n  video {\n    margin: 0 5px 20px 0;\n    width: calc(50% - 5px);\n  }\n}\n"
  },
  {
    "path": "src/content/capture/video-video/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>captureStream(): video to video</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>captureStream(): video to video</span>\n    </h1>\n\n    <video id=\"leftVideo\" playsinline controls loop muted>\n        <source src=\"../../../video/chrome.webm\" type=\"video/webm\"/>\n        <source src=\"../../../video/chrome.mp4\" type=\"video/mp4\"/>\n        <p>This browser does not support the video element.</p>\n    </video>\n\n    <video id=\"rightVideo\" playsinline autoplay muted></video>\n\n    <p>Press play on the left video to start the demo.</p>\n\n    <p>A stream is captured from the video element on the left using its <code>captureStream()</code> method and set as\n        the <code>srcObject</code> of the video element on the right.</p>\n\n    <p>The <code>stream</code> variable are in global scope, so you can inspect them from the browser console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/video-video\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/video-video/js/main.js",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n'use strict';\n\nconst leftVideo = document.getElementById('leftVideo');\nconst rightVideo = document.getElementById('rightVideo');\n\nleftVideo.addEventListener('canplay', () => {\n  let stream;\n  const fps = 0;\n  if (leftVideo.captureStream) {\n    stream = leftVideo.captureStream(fps);\n  } else if (leftVideo.mozCaptureStream) {\n    stream = leftVideo.mozCaptureStream(fps);\n  } else {\n    console.error('Stream capture is not supported');\n    stream = null;\n  }\n  rightVideo.srcObject = stream;\n});\n"
  },
  {
    "path": "src/content/capture/worker-process/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\ncanvas {\n    background-color: #ccc;\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    vertical-align: top;\n}\n\nvideo {\n    --width: calc(45%);\n    width: var(--width);\n    height: calc(var(--width) * 0.75);\n    margin: 1em;\n    object-fit: cover;\n}\n"
  },
  {
    "path": "src/content/capture/worker-process/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Video processing in a Worker</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Processing video data with a Worker</span>\n    </h1>\n\n    <video id=\"source\" autoplay></video>\n    <canvas id=\"canvas-source\" style=\"display:none\"></canvas>\n    <canvas id=\"canvas-result\" style=\"display:none\"></canvas>\n\n    <video id=\"result\" autoplay></video>\n\n    <p>The camera is captured to a MediaStreamTrack, which is turned into a\n      WHATWG Stream of ImageData objects by means of a canvas, and a red\n      square is added.</p>\n    <p>The stream is sent to a Worker, which returns a new stream containing\n      the same video data.</p>\n    <p>This is then mapped back to a MediaStream using another canvas.</p>\n\n    <p>The chief purpose of the demo is to demonstrate that this is doable,\n      but that performance can be improved significantly.</p>\n    <p>NOTE: This works only on Chrome 76 and above with experimental Web\n      features enabled, since it depends on transferable Streams.</p>\n    <p>A similar demo, without the worker process, is on the <a href=\"../canvas-filter/\">canvas filter</a> demo.</p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/capture/worker-process\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/capture/worker-process/js/main.js",
    "content": "/*\n*  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\nconst source = document.querySelector('#source');\n// TODO(hta): Use OffscreenCanvas for the intermediate canvases.\nconst canvasIn = document.querySelector('#canvas-source');\nconst canvasOut = document.querySelector('#canvas-result');\nconst result = document.querySelector('#result');\n\nconst stream = canvasOut.captureStream();\nlet inputStream = null;\nlet imageData = null;\nlet transformStream = null;\nlet writer = null;\nlet reader = null;\n\nresult.srcObject = stream;\n\nfunction loop() {\n  if (source.videoWidth > 0 && source.videoHeight > 0) {\n    canvasIn.width = source.videoWidth;\n    canvasIn.height = source.videoHeight;\n    const ctx = canvasIn.getContext('2d');\n    ctx.drawImage(source, 0, 0);\n    // Put a red square into the image, to mark it as \"processed\".\n    ctx.fillStyle = '#FF0000';\n    ctx.fillRect(10, 10, 80, 80);\n    imageData = ctx.getImageData(0, 0, canvasIn.width, canvasIn.height);\n    // At this point, we have data that can be transferred.\n    writer.write(imageData);\n  }\n  window.requestAnimationFrame(loop);\n}\n\n// The read function paints the incoming data on the second canvas.\nconst readData = async () => {\n  const result = await reader.read();\n  if (!result.done) {\n    canvasOut.width = source.videoWidth;\n    canvasOut.height = source.videoHeight;\n    const outCtx = canvasOut.getContext('2d');\n    outCtx.putImageData(result.value, 0, 0);\n    readData();\n  }\n};\n\n(async () => {\n  inputStream = await navigator.mediaDevices.getUserMedia({video: true});\n  source.srcObject = inputStream;\n  transformStream = new TransformStream();\n  writer = transformStream.writable.getWriter();\n\n  const myWorker = new Worker('js/worker.js');\n  myWorker.onmessage = function(e) {\n    reader = e.data[1].getReader();\n    // Start the flow of data.\n    readData();\n    window.requestAnimationFrame(loop);\n  };\n  myWorker.postMessage(['stream', transformStream.readable],\n      [transformStream.readable]);\n  source.play();\n  result.play();\n})();\n"
  },
  {
    "path": "src/content/capture/worker-process/js/worker.js",
    "content": "/*\n*  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\nonmessage = function(e) {\n  const command = e.data[0];\n  if (command == 'stream') {\n    const inputStream = e.data[1];\n    const transformStream = new TransformStream();\n    inputStream.pipeTo(transformStream.writable);\n    postMessage(['response', transformStream.readable],\n        [transformStream.readable]);\n  }\n};\n"
  },
  {
    "path": "src/content/datachannel/basic/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 1em 1em 0;\n  width: 90px;\n}\ndiv#buttons {\n  margin: 0 0 1em 0;\n}\ndiv#send {\n  margin: 0 20px 1em 0;\n}\ndiv#sendReceive {\n  border-bottom: 1px solid #eee;\n  margin: 0;\n  padding: 0 0 10px 0;\n}\ndiv#sendReceive > div {\n  display: inline-block;\n  width: calc(50% - 20px);\n}\nform {\n  margin: 0 0 1em 0;\n  white-space: nowrap;\n}\nform span {\n  font-weight: 300;\n  margin: 0 1em 0 0;\n  white-space: normal;\n}\ntextarea {\n  color: #444;\n  font-size: 0.9em;\n  font-weight: 300;\n  height: 7.0em;\n  padding: 5px;\n  width: calc(100% - 10px);\n}\n"
  },
  {
    "path": "src/content/datachannel/basic/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Transmit text</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Transmit text</span></h1>\n\n    <div id=\"buttons\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"sendButton\" disabled>Send</button>\n        <button id=\"closeButton\" disabled>Stop</button>\n    </div>\n\n    <div id=\"sendReceive\">\n        <div id=\"send\">\n            <h2>Send</h2>\n            <textarea id=\"dataChannelSend\" disabled\n                      placeholder=\"Press Start, enter some text, then press Send.\"></textarea>\n        </div>\n        <div id=\"receive\">\n            <h2>Receive</h2>\n            <textarea id=\"dataChannelReceive\" disabled></textarea>\n        </div>\n    </div>\n\n    <p>View the console to see logging.</p>\n\n    <p>The <code>RTCPeerConnection</code> objects <code>pc1</code> and <code>pc2</code> are in\n        global scope, so you can inspect them in the console as well.</p>\n\n    <p>For more information about RTCDataChannel, see <a\n            href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel\"\n            title=\"RTCDataChannel section of HTML5 Rocks article about WebRTC\">Getting Started With WebRTC</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/basic\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/datachannel/basic/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nlet pc1;\nlet pc2;\nlet sendChannel;\nlet receiveChannel;\nconst dataChannelSend = document.querySelector('textarea#dataChannelSend');\nconst dataChannelReceive = document.querySelector('textarea#dataChannelReceive');\nconst startButton = document.querySelector('button#startButton');\nconst sendButton = document.querySelector('button#sendButton');\nconst closeButton = document.querySelector('button#closeButton');\n\nstartButton.onclick = createConnection;\nsendButton.onclick = sendData;\ncloseButton.onclick = closeDataChannels;\n\nfunction enableStartButton() {\n  startButton.disabled = false;\n}\n\nfunction disableSendButton() {\n  sendButton.disabled = true;\n}\n\nfunction createConnection() {\n  dataChannelSend.placeholder = '';\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n\n  sendChannel = pc1.createDataChannel('sendDataChannel');\n  console.log('Created send data channel');\n\n  pc1.onicecandidate = e => {\n    onIceCandidate(pc1, e);\n  };\n  sendChannel.onopen = onSendChannelStateChange;\n  sendChannel.onclose = onSendChannelStateChange;\n\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n\n  pc2.onicecandidate = e => {\n    onIceCandidate(pc2, e);\n  };\n  pc2.ondatachannel = receiveChannelCallback;\n\n  pc1.createOffer().then(\n      gotDescription1,\n      onCreateSessionDescriptionError\n  );\n  startButton.disabled = true;\n  closeButton.disabled = false;\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log('Failed to create session description: ' + error.toString());\n}\n\nfunction sendData() {\n  const data = dataChannelSend.value;\n  sendChannel.send(data);\n  console.log('Sent Data: ' + data);\n}\n\nfunction closeDataChannels() {\n  console.log('Closing data channels');\n  sendChannel.close();\n  console.log('Closed data channel with label: ' + sendChannel.label);\n  receiveChannel.close();\n  console.log('Closed data channel with label: ' + receiveChannel.label);\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  console.log('Closed peer connections');\n  startButton.disabled = false;\n  sendButton.disabled = true;\n  closeButton.disabled = true;\n  dataChannelSend.value = '';\n  dataChannelReceive.value = '';\n  dataChannelSend.disabled = true;\n  disableSendButton();\n  enableStartButton();\n}\n\nfunction gotDescription1(desc) {\n  pc1.setLocalDescription(desc);\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  pc2.setRemoteDescription(desc);\n  pc2.createAnswer().then(\n      gotDescription2,\n      onCreateSessionDescriptionError\n  );\n}\n\nfunction gotDescription2(desc) {\n  pc2.setLocalDescription(desc);\n  console.log(`Answer from pc2\\n${desc.sdp}`);\n  pc1.setRemoteDescription(desc);\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(\n          onAddIceCandidateSuccess,\n          onAddIceCandidateError\n      );\n  console.log(`${getName(pc)} ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log(`Failed to add Ice Candidate: ${error.toString()}`);\n}\n\nfunction receiveChannelCallback(event) {\n  console.log('Receive Channel Callback');\n  receiveChannel = event.channel;\n  receiveChannel.onmessage = onReceiveMessageCallback;\n  receiveChannel.onopen = onReceiveChannelStateChange;\n  receiveChannel.onclose = onReceiveChannelStateChange;\n}\n\nfunction onReceiveMessageCallback(event) {\n  console.log('Received Message');\n  dataChannelReceive.value = event.data;\n}\n\nfunction onSendChannelStateChange() {\n  const readyState = sendChannel.readyState;\n  console.log('Send channel state is: ' + readyState);\n  if (readyState === 'open') {\n    dataChannelSend.disabled = false;\n    dataChannelSend.focus();\n    sendButton.disabled = false;\n    closeButton.disabled = false;\n  } else {\n    dataChannelSend.disabled = true;\n    sendButton.disabled = true;\n    closeButton.disabled = true;\n  }\n}\n\nfunction onReceiveChannelStateChange() {\n  const readyState = receiveChannel.readyState;\n  console.log(`Receive channel state is: ${readyState}`);\n}\n"
  },
  {
    "path": "src/content/datachannel/basic/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/datachannel/basic/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('datachannel basic', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('transfers text', async () => {\n    const text = 'Hello world';\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    await Promise.all([\n      driver.wait(() => driver.executeScript(() => {\n        return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n    await driver.wait(() => driver.findElement(webdriver.By.id('sendButton')).isEnabled());\n\n    await driver.findElement(webdriver.By.id('dataChannelSend'))\n        .sendKeys(text);\n    await driver.findElement(webdriver.By.id('sendButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('dataChannelReceive').value.length > 0;\n    }));\n\n    const value = await driver.findElement(webdriver.By.id('dataChannelReceive')).getAttribute('value');\n    expect(value).toBe(text);\n  });\n});\n"
  },
  {
    "path": "src/content/datachannel/channel/css/main.css",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 1em 1em 0;\n  width: 90px;\n}\ndiv#buttons {\n  margin: 0 0 1em 0;\n}\ndiv#send {\n  margin: 0 20px 1em 0;\n}\ndiv#sendReceive {\n  border-bottom: 1px solid #eee;\n  margin: 0;\n  padding: 0 0 10px 0;\n}\ndiv#sendReceive > div {\n  display: inline-block;\n  width: calc(50% - 20px);\n}\nform {\n  margin: 0 0 1em 0;\n  white-space: nowrap;\n}\nform span {\n  font-weight: 300;\n  margin: 0 1em 0 0;\n  white-space: normal;\n}\ntextarea {\n  color: #444;\n  font-size: 0.9em;\n  font-weight: 300;\n  height: 7.0em;\n  padding: 5px;\n  width: calc(100% - 10px);\n}\n"
  },
  {
    "path": "src/content/datachannel/channel/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Transmit text (between two tabs)</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Transmit text</span></h1>\n\n    <div id=\"buttons\">\n        <button id=\"startButton\" disabled>Start</button>\n        <button id=\"sendButton\" disabled>Send</button>\n        <button id=\"closeButton\" disabled>Stop</button>\n    </div>\n\n    <div id=\"sendReceive\">\n        <div id=\"send\">\n            <h2>Send</h2>\n            <textarea id=\"dataChannelSend\" disabled\n                      placeholder=\"Press Start, enter some text, then press Send.\"></textarea>\n        </div>\n        <div id=\"receive\">\n            <h2>Receive</h2>\n            <textarea id=\"dataChannelReceive\" disabled></textarea>\n        </div>\n    </div>\n\n    <p>This sample shows how to setup a datachannel connection between two peers in different tabs using\n        <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection</a>\n        and <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API\">Broadcast Channel</a>\n    </p>\n    <p>Open the sample in two tabs (of the same browser), then click start in the first tab and\n    send messages back and forth.</p>\n\n    <p>For more information about RTCDataChannel, see <a\n            href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel\"\n            title=\"RTCDataChannel section of HTML5 Rocks article about WebRTC\">Getting Started With WebRTC</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/channel\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/datachannel/channel/js/main.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst closeButton = document.getElementById('closeButton');\nconst sendButton = document.getElementById('sendButton');\nsendButton.onclick = sendData;\n\nconst dataChannelSend = document.querySelector('textarea#dataChannelSend');\nconst dataChannelReceive = document.querySelector('textarea#dataChannelReceive');\n\nlet pc;\nlet sendChannel;\nlet receiveChannel;\n\nconst signaling = new BroadcastChannel('webrtc');\nsignaling.onmessage = e => {\n  switch (e.data.type) {\n    case 'offer':\n      handleOffer(e.data);\n      break;\n    case 'answer':\n      handleAnswer(e.data);\n      break;\n    case 'candidate':\n      handleCandidate(e.data);\n      break;\n    case 'ready':\n      // A second tab joined. This tab will enable the start button unless in a call already.\n      if (pc) {\n        console.log('already in call, ignoring');\n        return;\n      }\n      startButton.disabled = false;\n      break;\n    case 'bye':\n      if (pc) {\n        hangup();\n      }\n      break;\n    default:\n      console.log('unhandled', e);\n      break;\n  }\n};\nsignaling.postMessage({type: 'ready'});\n\nstartButton.onclick = async () => {\n  startButton.disabled = true;\n  closeButton.disabled = false;\n\n  await createPeerConnection();\n  sendChannel = pc.createDataChannel('sendDataChannel');\n  sendChannel.onopen = onSendChannelStateChange;\n  sendChannel.onmessage = onSendChannelMessageCallback;\n  sendChannel.onclose = onSendChannelStateChange;\n\n  const offer = await pc.createOffer();\n  signaling.postMessage({type: 'offer', sdp: offer.sdp});\n  await pc.setLocalDescription(offer);\n};\n\ncloseButton.onclick = async () => {\n  hangup();\n  signaling.postMessage({type: 'bye'});\n};\n\nasync function hangup() {\n  if (pc) {\n    pc.close();\n    pc = null;\n  }\n  sendChannel = null;\n  receiveChannel = null;\n  console.log('Closed peer connections');\n  startButton.disabled = false;\n  sendButton.disabled = true;\n  closeButton.disabled = true;\n  dataChannelSend.value = '';\n  dataChannelReceive.value = '';\n  dataChannelSend.disabled = true;\n};\n\nfunction createPeerConnection() {\n  pc = new RTCPeerConnection();\n  pc.onicecandidate = e => {\n    const message = {\n      type: 'candidate',\n      candidate: null,\n    };\n    if (e.candidate) {\n      message.candidate = e.candidate.candidate;\n      message.sdpMid = e.candidate.sdpMid;\n      message.sdpMLineIndex = e.candidate.sdpMLineIndex;\n    }\n    signaling.postMessage(message);\n  };\n}\n\nasync function handleOffer(offer) {\n  if (pc) {\n    console.error('existing peerconnection');\n    return;\n  }\n  await createPeerConnection();\n  pc.ondatachannel = receiveChannelCallback;\n  await pc.setRemoteDescription(offer);\n\n  const answer = await pc.createAnswer();\n  signaling.postMessage({type: 'answer', sdp: answer.sdp});\n  await pc.setLocalDescription(answer);\n}\n\nasync function handleAnswer(answer) {\n  if (!pc) {\n    console.error('no peerconnection');\n    return;\n  }\n  await pc.setRemoteDescription(answer);\n}\n\nasync function handleCandidate(candidate) {\n  if (!pc) {\n    console.error('no peerconnection');\n    return;\n  }\n  if (!candidate.candidate) {\n    await pc.addIceCandidate(null);\n  } else {\n    await pc.addIceCandidate(candidate);\n  }\n}\n\nfunction sendData() {\n  const data = dataChannelSend.value;\n  if (sendChannel) {\n    sendChannel.send(data);\n  } else {\n    receiveChannel.send(data);\n  }\n  console.log('Sent Data: ' + data);\n}\n\nfunction receiveChannelCallback(event) {\n  console.log('Receive Channel Callback');\n  receiveChannel = event.channel;\n  receiveChannel.onmessage = onReceiveChannelMessageCallback;\n  receiveChannel.onopen = onReceiveChannelStateChange;\n  receiveChannel.onclose = onReceiveChannelStateChange;\n}\n\nfunction onReceiveChannelMessageCallback(event) {\n  console.log('Received Message');\n  dataChannelReceive.value = event.data;\n}\n\nfunction onSendChannelMessageCallback(event) {\n  console.log('Received Message');\n  dataChannelReceive.value = event.data;\n}\n\nfunction onSendChannelStateChange() {\n  const readyState = sendChannel.readyState;\n  console.log('Send channel state is: ' + readyState);\n  if (readyState === 'open') {\n    dataChannelSend.disabled = false;\n    dataChannelSend.focus();\n    sendButton.disabled = false;\n    closeButton.disabled = false;\n  } else {\n    dataChannelSend.disabled = true;\n    sendButton.disabled = true;\n    closeButton.disabled = true;\n  }\n}\n\nfunction onReceiveChannelStateChange() {\n  const readyState = receiveChannel.readyState;\n  console.log(`Receive channel state is: ${readyState}`);\n  if (readyState === 'open') {\n    dataChannelSend.disabled = false;\n    sendButton.disabled = false;\n    closeButton.disabled = false;\n  } else {\n    dataChannelSend.disabled = true;\n    sendButton.disabled = true;\n    closeButton.disabled = true;\n  }\n}\n"
  },
  {
    "path": "src/content/datachannel/channel/js/test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/datachannel/channel/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('datachannel and broadcast channels', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(async () => {\n    await driver.get(url);\n  });\n\n  it('establishes a connection and sends a message back and forth', async () => {\n    const firstHello = 'First tab to second tab';\n    const secondHello = 'Second tab to first tab';\n\n    const firstTab = await driver.getWindowHandle();\n\n    // Create a second tab, switch to it.\n    await driver.switchTo().newWindow('tab');\n    const secondTab = await driver.getWindowHandle();\n    await driver.get(url);\n\n    await driver.switchTo().window(firstTab);\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    // Assert state in first tab.\n    await driver.switchTo().window(firstTab);\n    await driver.wait(() => driver.executeScript(() => {\n      return pc && pc.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n\n    // Assert state in second tab.\n    await driver.switchTo().window(secondTab);\n    await driver.wait(() => driver.executeScript(() => {\n      return pc && pc.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n\n    // Send a message from the first tab to the second tab.\n    await driver.switchTo().window(firstTab);\n    await driver.wait(() => driver.findElement(webdriver.By.id('sendButton')).isEnabled());\n\n    await driver.findElement(webdriver.By.id('dataChannelSend'))\n        .sendKeys(firstHello);\n    await driver.findElement(webdriver.By.id('sendButton')).click();\n\n    // Assert it was received.\n    await driver.switchTo().window(secondTab);\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('dataChannelReceive').value.length > 0;\n    }));\n    const fromFirst= await driver.findElement(webdriver.By.id('dataChannelReceive')).getAttribute('value');\n    expect(fromFirst).toBe(firstHello);\n\n    // Send a message from the second tab to the first tab.\n    await driver.switchTo().window(secondTab);\n    await driver.wait(() => driver.findElement(webdriver.By.id('sendButton')).isEnabled());\n\n    await driver.findElement(webdriver.By.id('dataChannelSend'))\n        .sendKeys(secondHello);\n    await driver.findElement(webdriver.By.id('sendButton')).click();\n\n    // Assert it was received.\n    await driver.switchTo().window(firstTab);\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('dataChannelReceive').value.length > 0;\n    }));\n    const fromSecond = await driver.findElement(webdriver.By.id('dataChannelReceive')).getAttribute('value');\n    expect(fromSecond).toBe(secondHello);\n  });\n});\n\n"
  },
  {
    "path": "src/content/datachannel/datatransfer/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\ndiv.progress {\n  margin: 0 0 1em 0;\n}\n\ndiv.progress div.label {\n  display: inline-block;\n  font-weight: 400;\n  width: 8.2em;\n}\n\ndiv.input {\n  margin: 0 0 1em 0;\n}\n\nprogress {\n  width: calc(100% - 8.5em);\n}\n"
  },
  {
    "path": "src/content/datachannel/datatransfer/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Generate and transfer data</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"https://webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Generate and transfer data</span>\n    </h1>\n    <section>\n\n        <p>This page generates and sends the specified amount of data via WebRTC datachannels.</p>\n\n        <p>To accomplish this in an interoperable way, the data is split into chunks which are then transferred via the\n            datachannel. The datachannel is reliable and ordered by default which is well-suited to filetransfers.</p>\n\n        <p>Send and receive progress is monitored using HTML5 <i>progress</i> elements.</p>\n\n    </section>\n\n    <section>\n        <div id=\"button\">\n            <button id=\"sendTheData\" type=\"button\">Generate and send data</button>\n        </div>\n        <div class=\"input\">\n            <input type=\"number\" id=\"megsToSend\" min=\"1\" name=\"megs\" value=\"16\"/>\n            <label for=\"megsToSend\">MB <b>(warning: very large values will potentially cause memory problems)</b></label>\n            <div id=\"errorMsg\"></div>\n        </div>\n        <div class=\"input\">\n            <input type=\"checkbox\" id=\"ordered\" checked>\n            <label for=\"ordered\">Ordered mode</label>\n        </div>\n        <div class=\"progress\">\n            <div class=\"label\">Send progress:</div>\n            <progress id=\"sendProgress\" max=\"0\" value=\"0\"></progress>\n        </div>\n\n        <div class=\"progress\">\n            <div class=\"label\">Receive progress:</div>\n            <progress id=\"receiveProgress\" max=\"0\" value=\"0\"></progress>\n        </div>\n\n        <div>\n            <span id=\"transferStatus\"></span>\n        </div>\n    </section>\n\n    <section>\n        <p>View the console to see logging.</p>\n\n        <p>The <code>RTCPeerConnection</code> objects <code>pc1</code> and <code>pc2</code> are\n            in global scope, so you can inspect them in the console as well.</p>\n\n        <p>For more information about RTCDataChannel, see <a\n                href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel\"\n                title=\"RTCDataChannel section of HTML5 Rocks article about WebRTC\">Getting Started With WebRTC</a>.</p>\n    </section>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/datatransfer\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/datachannel/datatransfer/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\nconst MAX_CHUNK_SIZE = 262144;\n\nlet pc1;\nlet pc2;\nlet sendChannel;\nlet receiveChannel;\nlet chunkSize;\nlet lowWaterMark;\nlet highWaterMark;\nlet dataString;\nlet timeoutHandle = null;\nconst megsToSend = document.querySelector('input#megsToSend');\nconst sendButton = document.querySelector('button#sendTheData');\nconst orderedCheckbox = document.querySelector('input#ordered');\nconst sendProgress = document.querySelector('progress#sendProgress');\nconst receiveProgress = document.querySelector('progress#receiveProgress');\nconst errorMessage = document.querySelector('div#errorMsg');\nconst transferStatus = document.querySelector('span#transferStatus');\n\nlet bytesToSend = 0;\nlet totalTimeUsedInSend = 0;\nlet numberOfSendCalls = 0;\nlet maxTimeUsedInSend = 0;\nlet sendStartTime = 0;\nlet currentThroughput = 0;\n\nsendButton.addEventListener('click', createConnection);\n\n// Prevent data sent to be set to 0.\nmegsToSend.addEventListener('change', function() {\n  const number = this.value;\n  if (Number.isNaN(number)) {\n    errorMessage.innerHTML = `Invalid value for MB to send: ${number}`;\n  } else if (number <= 0) {\n    sendButton.disabled = true;\n    errorMessage.innerHTML = '<p>Please enter a number greater than zero.</p>';\n  } else if (number > 64) {\n    sendButton.disabled = true;\n    errorMessage.innerHTML = '<p>Please enter a number lower or equal than 64.</p>';\n  } else {\n    errorMessage.innerHTML = '';\n    sendButton.disabled = false;\n  }\n});\n\nasync function createConnection() {\n  sendButton.disabled = true;\n  megsToSend.disabled = true;\n\n  const servers = null;\n\n  const number = Number.parseInt(megsToSend.value);\n  bytesToSend = number * 1024 * 1024;\n\n  pc1 = new RTCPeerConnection(servers);\n\n  // Let's make a data channel!\n  const dataChannelParams = {ordered: false};\n  if (orderedCheckbox.checked) {\n    dataChannelParams.ordered = true;\n  }\n  sendChannel = pc1.createDataChannel('sendDataChannel', dataChannelParams);\n  sendChannel.addEventListener('open', onSendChannelOpen);\n  sendChannel.addEventListener('close', onSendChannelClosed);\n  console.log('Created send data channel: ', sendChannel);\n\n  console.log('Created local peer connection object pc1: ', pc1);\n\n  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));\n\n  pc2 = new RTCPeerConnection(servers);\n  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));\n  pc2.addEventListener('datachannel', receiveChannelCallback);\n\n  try {\n    const localOffer = await pc1.createOffer();\n    await handleLocalDescription(localOffer);\n  } catch (e) {\n    console.error('Failed to create session description: ', e);\n  }\n\n  transferStatus.innerHTML = 'Peer connection setup complete.';\n}\n\nfunction sendData() {\n  // Stop scheduled timer if any (part of the workaround introduced below)\n  if (timeoutHandle !== null) {\n    clearTimeout(timeoutHandle);\n    timeoutHandle = null;\n  }\n\n  let bufferedAmount = sendChannel.bufferedAmount;\n  while (sendProgress.value < sendProgress.max) {\n    transferStatus.innerText = 'Sending data...';\n    const timeBefore = performance.now();\n    sendChannel.send(dataString);\n    const timeUsed = performance.now() - timeBefore;\n    if (timeUsed > maxTimeUsedInSend) {\n      maxTimeUsedInSend = timeUsed;\n      totalTimeUsedInSend += timeUsed;\n    }\n    numberOfSendCalls += 1;\n    bufferedAmount += chunkSize;\n    sendProgress.value += chunkSize;\n\n    // Pause sending if we reach the high water mark\n    if (bufferedAmount >= highWaterMark) {\n      // This is a workaround due to the bug that all browsers are incorrectly calculating the\n      // amount of buffered data. Therefore, the 'bufferedamountlow' event would not fire.\n      if (sendChannel.bufferedAmount < lowWaterMark) {\n        timeoutHandle = setTimeout(() => sendData(), 0);\n      }\n      console.log(`Paused sending, buffered amount: ${bufferedAmount} (announced: ${sendChannel.bufferedAmount})`);\n      break;\n    }\n  }\n\n  if (sendProgress.value === sendProgress.max) {\n    transferStatus.innerHTML = 'Data transfer completed successfully!';\n  }\n}\n\nfunction startSendingData() {\n  transferStatus.innerHTML = 'Start sending data.';\n  sendProgress.max = bytesToSend;\n  receiveProgress.max = sendProgress.max;\n  sendProgress.value = 0;\n  receiveProgress.value = 0;\n  sendStartTime = performance.now();\n  maxTimeUsedInSend = 0;\n  totalTimeUsedInSend = 0;\n  numberOfSendCalls = 0;\n  sendData();\n}\n\nfunction maybeReset() {\n  if (pc1 === null && pc2 === null) {\n    sendButton.disabled = false;\n    megsToSend.disabled = false;\n  }\n}\n\nasync function handleLocalDescription(desc) {\n  pc1.setLocalDescription(desc);\n  console.log('Offer from pc1:\\n', desc.sdp);\n  pc2.setRemoteDescription(desc);\n  try {\n    const remoteAnswer = await pc2.createAnswer();\n    handleRemoteAnswer(remoteAnswer);\n  } catch (e) {\n    console.error('Error when creating remote answer: ', e);\n  }\n}\n\nfunction handleRemoteAnswer(desc) {\n  pc2.setLocalDescription(desc);\n  console.log('Answer from pc2:\\n', desc.sdp);\n  pc1.setRemoteDescription(desc);\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function onIceCandidate(pc, event) {\n  const candidate = event.candidate;\n  if (candidate === null) {\n    return;\n  } // Ignore null candidates\n  try {\n    await getOtherPc(pc).addIceCandidate(candidate);\n    console.log('AddIceCandidate successful: ', candidate);\n  } catch (e) {\n    console.error('Failed to add Ice Candidate: ', e);\n  }\n}\n\nfunction receiveChannelCallback(event) {\n  console.log('Receive Channel Callback');\n  receiveChannel = event.channel;\n  receiveChannel.binaryType = 'arraybuffer';\n  receiveChannel.addEventListener('close', onReceiveChannelClosed);\n  receiveChannel.addEventListener('message', onReceiveMessageCallback);\n}\n\nfunction onReceiveMessageCallback(event) {\n  receiveProgress.value += event.data.length;\n  currentThroughput = receiveProgress.value / (performance.now() - sendStartTime);\n  console.log('Current Throughput is:', currentThroughput, 'bytes/sec');\n\n  // Workaround for a bug in Chrome which prevents the closing event from being raised by the\n  // remote side. Also a workaround for Firefox which does not send all pending data when closing\n  // the channel.\n  if (receiveProgress.value === receiveProgress.max) {\n    sendChannel.close();\n    receiveChannel.close();\n  }\n}\n\nfunction onSendChannelOpen() {\n  console.log('Send channel is open');\n\n  chunkSize = Math.min(pc1.sctp.maxMessageSize, MAX_CHUNK_SIZE);\n  console.log('Determined chunk size: ', chunkSize);\n  dataString = new Array(chunkSize).fill('X').join('');\n  lowWaterMark = chunkSize; // A single chunk\n  highWaterMark = Math.max(chunkSize * 8, 1048576); // 8 chunks or at least 1 MiB\n  console.log('Send buffer low water threshold: ', lowWaterMark);\n  console.log('Send buffer high water threshold: ', highWaterMark);\n  sendChannel.bufferedAmountLowThreshold = lowWaterMark;\n  sendChannel.addEventListener('bufferedamountlow', (e) => {\n    console.log('BufferedAmountLow event:', e);\n    sendData();\n  });\n\n  startSendingData();\n}\n\nfunction onSendChannelClosed() {\n  console.log('Send channel is closed');\n  pc1.close();\n  pc1 = null;\n  console.log('Closed local peer connection');\n  maybeReset();\n  console.log('Average time spent in send() (ms): ' +\n              totalTimeUsedInSend / numberOfSendCalls);\n  console.log('Max time spent in send() (ms): ' + maxTimeUsedInSend);\n  const spentTime = performance.now() - sendStartTime;\n  console.log('Total time spent: ' + spentTime);\n  console.log('MBytes/Sec: ' + (bytesToSend / 1000) / spentTime);\n}\n\nfunction onReceiveChannelClosed() {\n  console.log('Receive channel is closed');\n  pc2.close();\n  pc2 = null;\n  console.log('Closed remote peer connection');\n  maybeReset();\n}\n"
  },
  {
    "path": "src/content/datachannel/datatransfer/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/datachannel/datatransfer/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('datachannel datatransfer', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('transfers data', async () => {\n    const megsToSend = 4;\n    await driver.findElement(webdriver.By.id('megsToSend'))\n        .clear();\n    await driver.findElement(webdriver.By.id('megsToSend'))\n        .sendKeys(megsToSend + '\\n');\n\n    await driver.findElement(webdriver.By.id('sendTheData')).click();\n\n    await Promise.all([\n      driver.wait(() => driver.executeScript(() => {\n        return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n\n    // the remote connection gets closed when it is done.\n    await driver.wait(() => driver.executeScript(() => {\n      return pc2 === null; // eslint-disable-line no-undef\n    }));\n\n    const transferred = await driver.findElement(webdriver.By.id('receiveProgress')).getAttribute('value');\n    expect(transferred >>> 0).toBe(megsToSend * 1024 * 1024);\n  });\n});\n"
  },
  {
    "path": "src/content/datachannel/filetransfer/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\ndiv.progress, div#bitrate {\n  margin: 0 0 1em 0;\n}\n\ndiv.progress div.label {\n  display: inline-block;\n  font-weight: 400;\n  width: 8.2em;\n}\n\nform {\n  margin: 0 0 1em 0;\n  white-space: nowrap;\n}\n\nprogress {\n  width: calc(100% - 8.5em);\n}\n"
  },
  {
    "path": "src/content/datachannel/filetransfer/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n  <meta charset=\"utf-8\">\n  <meta name=\"description\" content=\"WebRTC code samples\">\n  <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n  <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n  <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n  <meta itemprop=\"name\" content=\"WebRTC code samples\">\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n  <base target=\"_blank\">\n\n  <title>Transfer a file</title>\n\n  <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n  <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n  <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n  <link rel=\"stylesheet\" href=\"css/main.css\" />\n\n</head>\n\n<body>\n\n  <div id=\"container\">\n\n    <h1><a href=\"https://webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Transfer a file</span></h1>\n    <section>\n\n      <p>This page shows how to transfer a file via WebRTC datachannels.</p>\n\n      <p>To accomplish this in an interoperable way, the file is split into chunks which are then transferred via the datachannel. The datachannel is reliable and ordered by default which is well-suited to filetransfers.</p>\n\n      <p>Send and receive progress is monitored using HTML5 <i>progress</i> elements.</p>\n\n      <p>At the receiver, the file is reassembled using the Blob API and made available for download.</p>\n\n      <p>Note: real-world applications require a file transfer protocol to send metadata about the file (such as the filename, type, size, last modification date, hash, ...).This information can be conveyed either via the signaling channel or in-band. The demo elides this by assuming knowledge of the file size at the receiver and closes both the datachannel and the peerconnection when the correct amount of bytes has been received.</p>\n\n    </section>\n\n    <section>\n      <div >\n        <form id=\"fileInfo\">\n          <input type=\"file\" id=\"fileInput\" name=\"files\"/>\n        </form>\n        <button disabled id=\"sendFile\">Send</button>\n        <button disabled id=\"abortButton\">Abort</button>\n      </div>\n\n      <div class=\"progress\">\n        <div class=\"label\">Send progress: </div>\n        <progress id=\"sendProgress\" max=\"0\" value=\"0\"></progress>\n      </div>\n\n      <div class=\"progress\">\n        <div class=\"label\">Receive progress: </div>\n        <progress id=\"receiveProgress\" max=\"0\" value=\"0\"></progress>\n      </div>\n\n      <div id=\"bitrate\"></div>\n      <a id=\"download\"></a>\n      <span id=\"status\"></span>\n\n    </section>\n\n    <section>\n      <p>View the console to see logging.</p>\n\n      <p>The <code>RTCPeerConnection</code> objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as well.</p>\n\n      <p>For more information about RTCDataChannel, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel\" title=\"RTCDataChannel section of HTML5 Rocks article about WebRTC\">Getting Started With WebRTC</a>.</p>\n    </section>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/filetransfer\" title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n  </div>\n\n  <script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n  <script src=\"js/main.js\"></script>\n\n  <script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/datachannel/filetransfer/js/main.js",
    "content": "/* eslint no-unused-expressions: 0 */\n/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n'use strict';\n\nlet pc1;\nlet pc2;\nlet sendChannel;\nlet receiveChannel;\nlet fileReader;\nconst bitrateDiv = document.querySelector('div#bitrate');\nconst fileInput = document.querySelector('input#fileInput');\nconst abortButton = document.querySelector('button#abortButton');\nconst downloadAnchor = document.querySelector('a#download');\nconst sendProgress = document.querySelector('progress#sendProgress');\nconst receiveProgress = document.querySelector('progress#receiveProgress');\nconst statusMessage = document.querySelector('span#status');\nconst sendFileButton = document.querySelector('button#sendFile');\n\nlet receiveBuffer = [];\nlet receivedSize = 0;\n\nlet bytesPrev = 0;\nlet timestampPrev = 0;\nlet timestampStart;\nlet statsInterval = null;\nlet bitrateMax = 0;\n\nsendFileButton.addEventListener('click', () => createConnection());\nfileInput.addEventListener('change', handleFileInputChange, false);\nabortButton.addEventListener('click', () => {\n  if (fileReader && fileReader.readyState === 1) {\n    console.log('Abort read!');\n    fileReader.abort();\n  }\n});\n\nasync function handleFileInputChange() {\n  const file = fileInput.files[0];\n  if (!file) {\n    console.log('No file chosen');\n  } else {\n    sendFileButton.disabled = false;\n  }\n}\n\nasync function createConnection() {\n  abortButton.disabled = false;\n  sendFileButton.disabled = true;\n  pc1 = new RTCPeerConnection();\n  console.log('Created local peer connection object pc1');\n\n  sendChannel = pc1.createDataChannel('sendDataChannel');\n  sendChannel.binaryType = 'arraybuffer';\n  console.log('Created send data channel');\n\n  sendChannel.addEventListener('open', onSendChannelStateChange);\n  sendChannel.addEventListener('close', onSendChannelStateChange);\n  sendChannel.addEventListener('error', onError);\n\n  pc1.addEventListener('icecandidate', async event => {\n    console.log('Local ICE candidate: ', event.candidate);\n    await pc2.addIceCandidate(event.candidate);\n  });\n\n  pc2 = new RTCPeerConnection();\n  console.log('Created remote peer connection object pc2');\n\n  pc2.addEventListener('icecandidate', async event => {\n    console.log('Remote ICE candidate: ', event.candidate);\n    await pc1.addIceCandidate(event.candidate);\n  });\n  pc2.addEventListener('datachannel', receiveChannelCallback);\n\n  try {\n    const offer = await pc1.createOffer();\n    await gotLocalDescription(offer);\n  } catch (e) {\n    console.log('Failed to create session description: ', e);\n  }\n\n  fileInput.disabled = true;\n}\n\nfunction sendData() {\n  const file = fileInput.files[0];\n  console.log(`File is ${[file.name, file.size, file.type, file.lastModified].join(' ')}`);\n\n  // Handle 0 size files.\n  statusMessage.textContent = '';\n  downloadAnchor.textContent = '';\n  if (file.size === 0) {\n    bitrateDiv.innerHTML = '';\n    statusMessage.textContent = 'File is empty, please select a non-empty file';\n    closeDataChannels();\n    return;\n  }\n  sendProgress.max = file.size;\n  receiveProgress.max = file.size;\n  const chunkSize = 16384;\n  fileReader = new FileReader();\n  let offset = 0;\n  fileReader.addEventListener('error', error => console.error('Error reading file:', error));\n  fileReader.addEventListener('abort', event => console.log('File reading aborted:', event));\n  fileReader.addEventListener('load', e => {\n    console.log('FileRead.onload ', e);\n    sendChannel.send(e.target.result);\n    offset += e.target.result.byteLength;\n    sendProgress.value = offset;\n    if (offset < file.size) {\n      readSlice(offset);\n    }\n  });\n  const readSlice = o => {\n    console.log('readSlice ', o);\n    const slice = file.slice(offset, o + chunkSize);\n    fileReader.readAsArrayBuffer(slice);\n  };\n  readSlice(0);\n}\n\nfunction closeDataChannels() {\n  console.log('Closing data channels');\n  sendChannel.close();\n  console.log(`Closed data channel with label: ${sendChannel.label}`);\n  sendChannel = null;\n  if (receiveChannel) {\n    receiveChannel.close();\n    console.log(`Closed data channel with label: ${receiveChannel.label}`);\n    receiveChannel = null;\n  }\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  console.log('Closed peer connections');\n\n  // re-enable the file select\n  fileInput.disabled = false;\n  abortButton.disabled = true;\n  sendFileButton.disabled = false;\n}\n\nasync function gotLocalDescription(desc) {\n  await pc1.setLocalDescription(desc);\n  console.log(`Offer from pc1\\n ${desc.sdp}`);\n  await pc2.setRemoteDescription(desc);\n  try {\n    const answer = await pc2.createAnswer();\n    await gotRemoteDescription(answer);\n  } catch (e) {\n    console.log('Failed to create session description: ', e);\n  }\n}\n\nasync function gotRemoteDescription(desc) {\n  await pc2.setLocalDescription(desc);\n  console.log(`Answer from pc2\\n ${desc.sdp}`);\n  await pc1.setRemoteDescription(desc);\n}\n\nfunction receiveChannelCallback(event) {\n  console.log('Receive Channel Callback');\n  receiveChannel = event.channel;\n  receiveChannel.binaryType = 'arraybuffer';\n  receiveChannel.onmessage = onReceiveMessageCallback;\n  receiveChannel.onopen = onReceiveChannelStateChange;\n  receiveChannel.onclose = onReceiveChannelStateChange;\n\n  receivedSize = 0;\n  bitrateMax = 0;\n  downloadAnchor.textContent = '';\n  downloadAnchor.removeAttribute('download');\n  if (downloadAnchor.href) {\n    URL.revokeObjectURL(downloadAnchor.href);\n    downloadAnchor.removeAttribute('href');\n  }\n}\n\nfunction onReceiveMessageCallback(event) {\n  console.log(`Received Message ${event.data.byteLength}`);\n  receiveBuffer.push(event.data);\n  receivedSize += event.data.byteLength;\n  receiveProgress.value = receivedSize;\n\n  // we are assuming that our signaling protocol told\n  // about the expected file size (and name, hash, etc).\n  const file = fileInput.files[0];\n  if (receivedSize === file.size) {\n    const received = new Blob(receiveBuffer);\n    receiveBuffer = [];\n\n    downloadAnchor.href = URL.createObjectURL(received);\n    downloadAnchor.download = file.name;\n    downloadAnchor.textContent =\n      `Click to download '${file.name}' (${file.size} bytes)`;\n    downloadAnchor.style.display = 'block';\n\n    const bitrate = Math.round(receivedSize * 8 /\n      ((new Date()).getTime() - timestampStart));\n    bitrateDiv.innerHTML =\n      `<strong>Average Bitrate:</strong> ${bitrate} kbits/sec (max: ${bitrateMax} kbits/sec)`;\n\n    if (statsInterval) {\n      clearInterval(statsInterval);\n      statsInterval = null;\n    }\n\n    closeDataChannels();\n  }\n}\n\nfunction onSendChannelStateChange() {\n  if (sendChannel) {\n    const {readyState} = sendChannel;\n    console.log(`Send channel state is: ${readyState}`);\n    if (readyState === 'open') {\n      sendData();\n    }\n  }\n}\n\nfunction onError(error) {\n  if (sendChannel) {\n    console.error('Error in sendChannel:', error);\n    return;\n  }\n  console.log('Error in sendChannel which is already closed:', error);\n}\n\nasync function onReceiveChannelStateChange() {\n  if (receiveChannel) {\n    const readyState = receiveChannel.readyState;\n    console.log(`Receive channel state is: ${readyState}`);\n    if (readyState === 'open') {\n      timestampStart = (new Date()).getTime();\n      timestampPrev = timestampStart;\n      statsInterval = setInterval(displayStats, 500);\n      await displayStats();\n    }\n  }\n}\n\n// display bitrate statistics.\nasync function displayStats() {\n  if (pc2 && pc2.iceConnectionState === 'connected') {\n    const stats = await pc2.getStats();\n    let activeCandidatePair;\n    stats.forEach(report => {\n      if (report.type === 'transport') {\n        activeCandidatePair = stats.get(report.selectedCandidatePairId);\n      }\n    });\n    if (activeCandidatePair) {\n      if (timestampPrev === activeCandidatePair.timestamp) {\n        return;\n      }\n      // calculate current bitrate\n      const bytesNow = activeCandidatePair.bytesReceived;\n      const bitrate = Math.round((bytesNow - bytesPrev) * 8 /\n        (activeCandidatePair.timestamp - timestampPrev));\n      bitrateDiv.innerHTML = `<strong>Current Bitrate:</strong> ${bitrate} kbits/sec`;\n      timestampPrev = activeCandidatePair.timestamp;\n      bytesPrev = bytesNow;\n      if (bitrate > bitrateMax) {\n        bitrateMax = bitrate;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/datachannel/filetransfer/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/datachannel/filetransfer/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('datachannel filetransfer', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('transfers a file', async () => {\n    await driver.findElement(webdriver.By.id('fileInput'))\n        .sendKeys(process.cwd() + '/src/content/devices/multi/images/poster.jpg');\n    await driver.wait(() => driver.findElement(webdriver.By.id('sendFile')).isEnabled());\n    await driver.findElement(webdriver.By.id('sendFile')).click();\n\n    // the remote connection gets closed when it is done.\n    await driver.wait(() => driver.executeScript(() => {\n      return pc2 === null; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.findElement(webdriver.By.id('download')).isEnabled());\n  });\n});\n\n"
  },
  {
    "path": "src/content/datachannel/messaging/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Send messages with datachannel</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"main.css\"/>\n\n    <script src=\"https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-loader.js\"></script>\n    <script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n    <script src=\"main.js\" type=\"module\"></script>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"https://webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Send messages with datachannel</span>\n    </h1>\n    <section>\n\n        <p>This page show how to send text messages via WebRTC datachannels.</p>\n\n        <p>Enter a message in one text box and press send and it will be transferred to the \"remote\" peer over a\n            datachannel.</p>\n\n    </section>\n\n    <messaging-sample></messaging-sample>\n\n    <section>\n        <p>View the console to see logging.</p>\n\n        <p>For more information about RTCDataChannel, see <a\n                href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel\"\n                title=\"RTCDataChannel section of HTML5 Rocks article about WebRTC\">Getting Started With WebRTC</a>.</p>\n    </section>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/datachannel/messaging\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/datachannel/messaging/main.css",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\ndiv.messageBox {\n    width: 100%;\n}\n\ntextarea.message {\n    width: 100%;\n    height: 5em;\n    resize: none;\n    display: block;\n    box-sizing: border-box;\n    margin: 1em;\n}\n\nlabel {\n    font-weight: 400;\n}\n\n"
  },
  {
    "path": "src/content/datachannel/messaging/main.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint no-unused-expressions: 0 */\n\n'use strict';\nimport {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@0.6.2?module';\n\nclass MessagingSample extends LitElement {\n  constructor() {\n    super();\n    this.connected = false;\n    this.localMessages = '';\n    this.remoteMessages = '';\n  }\n\n  disconnect() {\n    this._pc1.close();\n    this._pc2.close();\n  }\n\n  async connect() {\n    console.log('connect!');\n    try {\n      const dataChannelParams = {ordered: true};\n      this._pc1 = new RTCPeerConnection();\n      this._pc1.addEventListener('icecandidate', async e => {\n        console.log('local connection ICE candidate: ', e.candidate);\n        await this._pc2.addIceCandidate(e.candidate);\n      });\n      this._pc2 = new RTCPeerConnection();\n      this._pc2.addEventListener('icecandidate', async e => {\n        console.log('remote connection ICE candidate: ', e.candidate);\n        await this._pc1.addIceCandidate(e.candidate);\n      });\n\n      window.localChannel = this._localChannel = this._pc1\n          .createDataChannel('messaging-channel', dataChannelParams);\n      this._localChannel.binaryType = 'arraybuffer';\n      this._localChannel.addEventListener('open', () => {\n        console.log('Local channel open!');\n        this.connected = true;\n      });\n      this._localChannel.addEventListener('close', () => {\n        console.log('Local channel closed!');\n        this.connected = false;\n      });\n      this._localChannel.addEventListener('message', this._onLocalMessageReceived.bind(this));\n\n      this._pc2.addEventListener('datachannel', this._onRemoteDataChannel.bind(this));\n\n      const initLocalOffer = async () => {\n        const localOffer = await this._pc1.createOffer();\n        console.log(`Got local offer ${JSON.stringify(localOffer)}`);\n        const localDesc = this._pc1.setLocalDescription(localOffer);\n        const remoteDesc = this._pc2.setRemoteDescription(localOffer);\n        return Promise.all([localDesc, remoteDesc]);\n      };\n\n      const initRemoteAnswer = async () => {\n        const remoteAnswer = await this._pc2.createAnswer();\n        console.log(`Got remote answer ${JSON.stringify(remoteAnswer)}`);\n        const localDesc = this._pc2.setLocalDescription(remoteAnswer);\n        const remoteDesc = this._pc1.setRemoteDescription(remoteAnswer);\n        return Promise.all([localDesc, remoteDesc]);\n      };\n\n      await initLocalOffer();\n      await initRemoteAnswer();\n    } catch (e) {\n      console.log(e);\n    }\n  }\n\n  _onLocalMessageReceived(event) {\n    console.log(`Remote message received by local: ${event.data}`);\n    this.localMessages += event.data + '\\n';\n  }\n\n  _onRemoteDataChannel(event) {\n    console.log(`onRemoteDataChannel: ${JSON.stringify(event)}`);\n    window.remoteChannel = this._remoteChannel = event.channel;\n    this._remoteChannel.binaryType = 'arraybuffer';\n    this._remoteChannel.addEventListener('message', this._onRemoteMessageReceived.bind(this));\n    this._remoteChannel.addEventListener('close', () => {\n      console.log('Remote channel closed!');\n      this.connected = false;\n    });\n  }\n\n  _onRemoteMessageReceived(event) {\n    console.log(`Local message received by remote: ${event.data}`);\n    this.remoteMessages += event.data + '\\n';\n  }\n\n  static get properties() {\n    return {\n      connected: {type: Boolean},\n      localMessages: {type: String},\n      remoteMessages: {type: String}\n    };\n  }\n\n  render() {\n    return html`<section>\n  <style>\n  @import \"../../../css/main.css\";\n  @import \"main.css\";\n  </style>\n  <div>\n      <button ?disabled=\"${this.connected}\" @click=\"${this.connect.bind(this)}\">Connect</button>\n      <button ?disabled=\"${!this.connected}\" @click=\"${this.disconnect.bind(this)}\">Disconnect</button>\n  </div>\n\n  <div class=\"messageBox\">\n      <label for=\"localOutgoing\">Local outgoing message:</label>\n      <textarea class=\"message\" id=\"localOutgoing\" \n                placeholder=\"Local outgoing message goes here.\"></textarea>\n      <button ?disabled=\"${!this.connected}\" @click=\"${e => this._sendMessage('#localOutgoing', this._localChannel)} \n      id=\"sendLocal\">Send message from local</button>\n  </div>\n  <div class=\"messageBox\">\n      <label for=\"localIncoming\">Local incoming messages:</label>\n      <textarea class=\"message\" id=\"localIncoming\" disabled \n                placeholder=\"Local incoming messages arrive here.\">${this.localMessages}</textarea>\n  </div>\n\n  <div class=\"messageBox\">\n      <label for=\"remoteOutgoing\">Remote outgoing message:</label>\n      <textarea class=\"message\" id=\"remoteOutgoing\" \n                placeholder=\"Remote outgoing message goes here.\"></textarea>\n      <button ?disabled=\"${!this.connected}\" @click=\"${e => this._sendMessage('#remoteOutgoing', this._remoteChannel)}\" \n      id=\"sendRemote\">Send message from remote</button>\n  </div>\n  <div class=\"messageBox\">\n      <label for=\"remoteIncoming\">Remote incoming messages:</label>\n      <textarea class=\"message\" id=\"remoteIncoming\" disabled\n                placeholder=\"Remote incoming messages arrive here.\">${this.remoteMessages}</textarea>\n  </div>\n</section>`;\n  }\n\n  _sendMessage(selector, channel) {\n    const textarea = this.shadowRoot.querySelector(selector);\n    const value = textarea.value;\n    if (value === '') {\n      console.log('Not sending empty message!');\n      return;\n    }\n    console.log('Sending remote message: ', value);\n    channel.send(value);\n    textarea.value = '';\n  }\n}\n\ncustomElements.define('messaging-sample', MessagingSample);\n"
  },
  {
    "path": "src/content/devices/input-output/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Select audio and video sources</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n    <style>\n        div.select {\n            display: inline-block;\n            margin: 0 0 1em 0;\n        }\n\n        p.small {\n            font-size: 0.7em;\n        }\n\n        label {\n            width: 12em;\n            display: inline-block;\n        }\n    </style>\n\n</head>\n\n<body>\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a><span>Select sources &amp; outputs</span>\n    </h1>\n\n    <p>Get available audio, video sources and audio output devices from <code>mediaDevices.enumerateDevices()</code>\n        then set the source for <code>getUserMedia()</code> using a <code>deviceId</code> constraint.</p>\n    <p><b>Note:</b> without permission, the browser will restrict the available devices to at most one per type.</p>\n\n    <div class=\"select\">\n        <label for=\"audioSource\">Audio input source: </label><select id=\"audioSource\"></select>\n    </div>\n\n    <div class=\"select\">\n        <label for=\"audioOutput\">Audio output destination: </label><select id=\"audioOutput\"></select>\n    </div>\n\n    <div class=\"select\">\n        <label for=\"videoSource\">Video source: </label><select id=\"videoSource\"></select>\n    </div>\n\n    <video id=\"video\" playsinline autoplay></video>\n\n    <p><b>Note:</b> If you hear a reverb sound your microphone is picking up the output of your\n        speakers/headset, lower the volume and/or move the microphone further away from your speakers/headset.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/devices/input-output\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/devices/input-output/js/main.js",
    "content": "/*\n*  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n'use strict';\n\nconst videoElement = document.querySelector('video');\nconst audioInputSelect = document.querySelector('select#audioSource');\nconst audioOutputSelect = document.querySelector('select#audioOutput');\nconst videoSelect = document.querySelector('select#videoSource');\nconst selectors = [audioInputSelect, audioOutputSelect, videoSelect];\nlet hasMic = false;\nlet hasCamera = false;\nlet openMic = undefined;\nlet openCamera = undefined;\nlet hasPermission = false;\n\naudioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype);\n\nfunction getDevices() {\n  navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);\n}\n\nfunction gotDevices(deviceInfos) {\n  console.log('gotDevices', deviceInfos);\n  hasMic = false;\n  hasCamera = false;\n  hasPermission = false;\n  // Handles being called several times to update labels. Preserve values.\n  const values = selectors.map(select => select.value);\n  selectors.forEach(select => {\n    while (select.firstChild) {\n      select.removeChild(select.firstChild);\n    }\n  });\n  for (let i = 0; i !== deviceInfos.length; ++i) {\n    const deviceInfo = deviceInfos[i];\n    if (deviceInfo.deviceId == '') {\n      continue;\n    }\n    // If we get at least one deviceId, that means user has granted user\n    // media permissions.\n    hasPermission = true;\n    const option = document.createElement('option');\n    option.value = deviceInfo.deviceId;\n    if (deviceInfo.kind === 'audioinput') {\n      hasMic = true;\n      option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;\n      audioInputSelect.appendChild(option);\n    } else if (deviceInfo.kind === 'audiooutput') {\n      option.text = deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`;\n      audioOutputSelect.appendChild(option);\n    } else if (deviceInfo.kind === 'videoinput') {\n      hasCamera = true;\n      option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;\n      videoSelect.appendChild(option);\n    } else {\n      console.log('Some other kind of source/device: ', deviceInfo);\n    }\n  }\n  selectors.forEach((select, selectorIndex) => {\n    if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {\n      select.value = values[selectorIndex];\n    }\n  });\n  start();\n}\n\n// Attach audio output device to video element using device/sink ID.\nfunction attachSinkId(element, sinkId) {\n  if (typeof element.sinkId !== 'undefined') {\n    element.setSinkId(sinkId)\n        .then(() => {\n          console.log(`Success, audio output device attached: ${sinkId}`);\n        })\n        .catch(error => {\n          let errorMessage = error;\n          if (error.name === 'SecurityError') {\n            errorMessage = `You need to use HTTPS for selecting audio output device: ${error}`;\n          }\n          console.error(errorMessage);\n          // Jump back to first output device in the list as it's the default.\n          audioOutputSelect.selectedIndex = 0;\n        });\n  } else {\n    console.warn('Browser does not support output device selection.');\n  }\n}\n\nfunction changeAudioDestination() {\n  const audioDestination = audioOutputSelect.value;\n  attachSinkId(videoElement, audioDestination);\n}\n\nfunction gotStream(stream) {\n  window.stream = stream; // make stream available to console\n  videoElement.srcObject = stream;\n  if (stream.getVideoTracks()[0]) {\n    openCamera = stream.getVideoTracks()[0].getSettings().deviceId;\n  }\n  if (stream.getAudioTracks()[0]) {\n    openMic = stream.getAudioTracks()[0].getSettings().deviceId;\n  }\n  // Refresh list in case labels have become available\n  return getDevices();\n}\n\nfunction handleError(error) {\n  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);\n}\n\nfunction start() {\n  const audioSource = audioInputSelect.value || undefined;\n  const videoSource = videoSelect.value || undefined;\n  // Don't open the same devices again.\n  if (hasPermission && openMic == audioSource && openCamera == videoSource) {\n    return;\n  }\n  // Close existng streams.\n  if (window.stream) {\n    window.stream.getTracks().forEach(track => {\n      track.stop();\n    });\n    openCamera = undefined;\n    openMic = undefined;\n  }\n  const constraints = {\n    audio: true,\n    video: true\n  };\n  if (hasMic) {\n    constraints['audio'] = {deviceId: audioSource ? {exact: audioSource} : undefined};\n  }\n  if (hasCamera) {\n    constraints['video'] = {deviceId: videoSource ? {exact: videoSource} : undefined};\n  }\n  console.log('start', constraints);\n  if (!hasPermission || hasCamera || hasMic) {\n    navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);\n  }\n}\n\naudioInputSelect.onchange = start;\naudioOutputSelect.onchange = changeAudioDestination;\nvideoSelect.onchange = start;\nnavigator.mediaDevices.ondevicechange = getDevices;\n\ngetDevices();\n"
  },
  {
    "path": "src/content/devices/input-output/js/test.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n'use strict';\n\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/devices/input-output/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('input-output', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('shows at least one audio input device', async () => {\n    await driver.wait(driver.executeScript(() => {\n      return document.getElementById('audioSource').childElementCount > 0;\n    }));\n  });\n\n  it('shows at least one video input device', async () => {\n    await driver.wait(driver.executeScript(() => {\n      return document.getElementById('videoSource').childElementCount > 0;\n    }));\n  });\n\n  it('shows at least one audio output device device', async function() {\n    if (process.env.BROWSER === 'firefox') {\n      this.skip();\n    }\n    await driver.wait(driver.executeScript(() => {\n      return document.getElementById('audioOutput').childElementCount > 0;\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/devices/multi/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\naudio {\n  margin: 0 0 1.5em 0;\n  width: 100%;\n}\n\ndiv#sources > div {\n  float: left;\n  margin: 0 1em 0 0;\n  width: calc(50% - 0.5em);\n}\n\ndiv#sources > div:last-of-type {\n  margin: 0;\n}\n\nselect {\n  margin: 0 0 0.5em 0;\n}\n\nvideo {\n  background: black;\n  height: 234px;\n}\n"
  },
  {
    "path": "src/content/devices/multi/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>getUserMedia: output device selection</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>getUserMedia: output device selection</span>\n    </h1>\n\n    <div id=\"sources\">\n\n        <div>\n            <h2>getUserMedia():</h2>\n            <div>\n                <video class=\"gum\" title=\"audio and video stream from getUserMedia()\" playsinline autoplay controls></video>\n                <div class=\"outputSelector\">\n                    <label>Select audio output: </label>\n                    <select></select>\n                </div>\n            </div>\n            <div>\n                <audio class=\"gum\" title=\"audio stream from getUserMedia()\" controls></audio>\n                <div class=\"outputSelector\">\n                    <label>Select audio output: </label>\n                    <select></select>\n                </div>\n            </div>\n        </div>\n\n        <div>\n            <h2>Local media files:</h2>\n            <div>\n                <video title=\"local video file\" playsinline controls loop poster=\"images/poster.jpg\" preload=\"metadata\">\n                    <source src=\"video/chrome.webm\" type=\"video/webm\"/>\n                    <source src=\"video/chrome.mp4\" type=\"video/mp4\"/>\n                    <p>This browser does not support the video element.</p>\n                </video>\n                <div class=\"outputSelector\">\n                    <label>Select audio output: </label>\n                    <select></select>\n                </div>\n            </div>\n            <div>\n                <audio title=\"local audio file\" controls loop>\n                    <source src=\"audio/audio.mp3\" type=\"audio/mp3\"/>\n                    This browser does not support the audio element.\n                </audio>\n                <div class=\"outputSelector\">\n                    <label>Select audio output: </label>\n                    <select></select>\n                </div>\n            </div>\n        </div>\n\n    </div>\n\n    <p>This demo must be run from localhost or over HTTPS Chrome 49 or later, Firefox is not supported yet.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/devices/multi\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/devices/multi/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst gumAudio = document.querySelector('audio.gum');\ngumAudio.addEventListener('play', () => {\n  gumAudio.volume = 0.1;\n  console.log('Audio lowered to reduce feedback from local gUM stream');\n});\nconst gumVideo = document.querySelector('video.gum');\ngumVideo.addEventListener('play', () => {\n  gumVideo.volume = 0.1;\n  console.log('Audio lowered to reduce feedback from local gUM stream');\n});\n\nfunction gotDevices(deviceInfos) {\n  const masterOutputSelector = document.createElement('select');\n\n  for (let i = 0; i !== deviceInfos.length; ++i) {\n    const deviceInfo = deviceInfos[i];\n    const option = document.createElement('option');\n    option.value = deviceInfo.deviceId;\n    if (deviceInfo.kind === 'audiooutput') {\n      console.info('Found audio output device: ', deviceInfo.label);\n      option.text = deviceInfo.label || `speaker ${masterOutputSelector.length + 1}`;\n      masterOutputSelector.appendChild(option);\n    } else {\n      console.log('Found non audio output device: ', deviceInfo.label);\n    }\n  }\n\n  // Clone the master outputSelector and replace outputSelector placeholders.\n  const allOutputSelectors = document.querySelectorAll('select');\n  for (let selector = 0; selector < allOutputSelectors.length; selector++) {\n    const newOutputSelector = masterOutputSelector.cloneNode(true);\n    newOutputSelector.addEventListener('change', changeAudioDestination);\n    allOutputSelectors[selector].parentNode.replaceChild(newOutputSelector,\n        allOutputSelectors[selector]);\n  }\n}\n\nnavigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);\n\n// Attach audio output device to the provided media element using the deviceId.\nfunction attachSinkId(element, sinkId, outputSelector) {\n  if (typeof element.sinkId !== 'undefined') {\n    element.setSinkId(sinkId)\n        .then(() => {\n          console.log(`Success, audio output device attached: ${sinkId} to element with ${element.title} as source.`);\n        })\n        .catch(error => {\n          let errorMessage = error;\n          if (error.name === 'SecurityError') {\n            errorMessage = `You need to use HTTPS for selecting audio output device: ${error}`;\n          }\n          console.error(errorMessage);\n          // Jump back to first output device in the list as it's the default.\n          outputSelector.selectedIndex = 0;\n        });\n  } else {\n    console.warn('Browser does not support output device selection.');\n  }\n}\n\nfunction changeAudioDestination(event) {\n  const deviceId = event.target.value;\n  const outputSelector = event.target;\n  // FIXME: Make the media element lookup dynamic.\n  const element = event.composedPath()[2].childNodes[1];\n  attachSinkId(element, deviceId, outputSelector);\n}\n\nfunction gotStream(stream) {\n  window.stream = stream; // make stream available to console\n  gumAudio.srcObject = stream;\n  gumVideo.srcObject = stream;\n}\n\nfunction start() {\n  if (window.stream) {\n    window.stream.getTracks().forEach(track => {\n      track.stop();\n    });\n  }\n  const constraints = {\n    audio: true,\n    video: true\n  };\n  navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);\n}\n\nstart();\n\nfunction handleError(error) {\n  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);\n}\n\n"
  },
  {
    "path": "src/content/extensions/multipleroutes/src/README.md",
    "content": "## Chrome WebRTC Network Limiter\nConfigures the WebRTC traffic routing options in Chrome's privacy settings.\n\n★ What it does:\nThis configures WebRTC to not use certain IP addresses or protocols:\n- private IP addresses not visible to the public internet (e.g. addresses like 192.168.1.2)\n- any public IP addresses associated with network interfaces that are not used for web traffic (e.g. an ISP-provided address, when browsing through a VPN)\n- Require WebRTC traffic to go through proxy servers as configured in Chrome. Since most of the proxy servers don't handle UDP, this effectively turns off UDP until UDP proxy support is available in Chrome and such proxies are widely deployed.\n \nWhen the extension is installed on Chrome versions prior to M48, WebRTC will only use the public IP address associated with the interface used for web traffic, typically the same addresses that are already provided to sites in browser HTTP requests. For Chrome version M48 and after, this extension provides one more configuration which allows WebRTC to use both the default public address and, for machines behind a NAT, the default private address which is associated with the public one. Said behavior will be the default after a fresh installation of the extension to Chrome M48. For upgrade scenarios, the previous selected configuration should not be changed.\n\nThe extension may also disable non-proxied UDP, but this is not on by default and must be configured using the extension's Options page.\n\n★ Notes:\nThis extension may affect the performance of applications that use WebRTC for audio/video or real-time data communication. Because it limits the potential network paths and protocols, WebRTC may pick a path which results in significantly longer delay or lower quality (e.g. through a VPN) or use TCP only through proxy servers which is not ideal for real-time communication. We are attempting to determine how common this is.\n\nBy installing this item, you agree to the Google Terms of Service and Privacy Policy at https://www.google.com/intl/en/policies/.\n\n"
  },
  {
    "path": "src/content/extensions/multipleroutes/src/_locales/en/messages.json",
    "content": "{\n   \"NETLI_DEFAULT_RADIO\": {\n      \"message\": \"<strong>Give me the best media experience:</strong> This option allows Chrome to explore all network paths to find the best way to send and receive media, which may be different from normal web traffic.\"\n   },\n   \"NETLI_DEFAULT_PUBLIC_AND_PRIVATE_INTERFACES_RADIO\": {\n      \"message\": \"<strong>Use my default public and private IP addresses:</strong> This option forces Chrome to use the same network path for media as for normal web traffic, except when a web proxy is present. For machines behind a NAT, Chrome will also use the default private address to enhance connectivity. To prevent degraded performance, Chrome will attempt to send media directly instead of using the proxy.\"\n   },\n   \"NETLI_DEFAULT_PUBLIC_INTERFACE_ONLY_RADIO\": {\n      \"message\": \"<strong>Use only my default public IP address:</strong> This option is the same as <em>Use my default public and private IP addresses</em> except that Chrome will not use the private default address.\"\n   },\n   \"NETLI_DISABLE_NON_PROXIED_UDP_RADIO\": {\n      \"message\": \"<strong>Use my proxy server (if present):</strong> This option forces Chrome to use the same network path for media as for normal web traffic, including use of a web proxy. Chrome will always attempt to send media through the proxy, which will typically hurt media performance and increase the load on the proxy; furthermore, this behavior may be incompatible with some applications.\"\n   },\n   \"NETLI_OPTION_NOT_SUPPORTED\": {\n      \"message\": \"Grayed out options require newer verion of Chrome.\"\n   },\n   \"NETLI_APPDESC\": {\n      \"message\": \"Configures how WebRTC's network traffic is routed by changing Chrome's privacy settings.\"\n   },\n   \"NETLI_APPNAME\": {\n      \"message\": \"WebRTC Network Limiter\"\n   },\n   \"NETLI_OPTIONS\": {\n      \"message\": \"WebRTC Network Limiter Options\"\n   }\n}\n"
  },
  {
    "path": "src/content/extensions/multipleroutes/src/manifest.json",
    "content": "{\n  \"default_locale\": \"en\",\n  \"description\": \"__MSG_NETLI_APPDESC__\",\n  \"icons\": {\n    \"16\": \"img/icon_16.png\",\n    \"128\": \"img/icon_128.png\"\n  },\n  \"manifest_version\": 3,\n  \"minimum_chrome_version\": \"42.0.2311.135\",\n  \"name\": \"__MSG_NETLI_APPNAME__\",\n  \"options_ui\": {\n    \"open_in_tab\": false,\n    \"page\": \"options.html\"\n  },\n  \"permissions\": [ \"privacy\" ],\n  \"version\": \"0.2.1.4\"\n}\n"
  },
  {
    "path": "src/content/extensions/multipleroutes/src/options.html",
    "content": "<!doctype html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<body>\n  <p id=\"Mode0\">\n    <input type=\"radio\" name=\"ip_policy_selection\" id=\"default\" value=\"default\">\n    <label for=\"default\">\n      <span i18n-content=\"netli_default_radio\"></span>\n    </label>\n  </p>\n  <p id=\"Mode1\">\n    <input type=\"radio\" name=\"ip_policy_selection\"\n           id=\"default_public_and_private_interfaces\"\n           value=\"default_public_and_private_interfaces\">\n    <label for=\"default_public_and_private_interfaces\">\n      <span i18n-content=\"netli_default_public_and_private_interfaces_radio\"></span>\n    </label>\n  </p>\n  <p id=\"Mode2\">\n    <input type=\"radio\" name=\"ip_policy_selection\"\n           id=\"default_public_interface_only\"\n           value=\"default_public_interface_only\">\n    <label for=\"default_public_interface_only\">\n      <span i18n-content=\"netli_default_public_interface_only_radio\"></span>\n    </label>\n  </p>\n  <p id=\"Mode3\">\n    <input type=\"radio\" name=\"ip_policy_selection\"\n           id=\"disable_non_proxied_udp\" value=\"disable_non_proxied_udp\">\n    <label for=\"disable_non_proxied_udp\" id=\"for_disable_non_proxied_udp\">\n      <span i18n-content=\"netli_disable_non_proxied_udp_radio\"></span>\n    </label>\n  </p>\n  <p>\n    <label id=\"not_supported\">\n      <span i18n-content=\"netli_option_not_supported\"></span>\n    </label>\n  </p>\n  <script src=\"utils.js\"></script>\n  <script src=\"options.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/extensions/multipleroutes/src/options.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst pn = chrome.privacy.network;\nconst pi = chrome.privacy.IPHandlingPolicy;\n\nconst mapPolicyToRadioId = {};\nmapPolicyToRadioId[pi.DEFAULT] = 0;\nmapPolicyToRadioId[pi.DEFAULT_PUBLIC_AND_PRIVATE_INTERFACES] = 1;\nmapPolicyToRadioId[pi.DEFAULT_PUBLIC_INTERFACE_ONLY] = 2;\nmapPolicyToRadioId[pi.DISABLE_NON_PROXIED_UDP] = 3;\n\nconst mapRadioIdToPolicy = {};\nmapRadioIdToPolicy[0] = pi.DEFAULT;\nmapRadioIdToPolicy[1] = pi.DEFAULT_PUBLIC_AND_PRIVATE_INTERFACES;\nmapRadioIdToPolicy[2] = pi.DEFAULT_PUBLIC_INTERFACE_ONLY;\nmapRadioIdToPolicy[3] = pi.DISABLE_NON_PROXIED_UDP;\n\n// Saves options.\nfunction saveOptions() {\n  const radios = document.getElementsByName('ip_policy_selection');\n  let i;\n  for (i = 0; i < radios.length; i++) {\n    if (radios[i].checked) {\n      break;\n    }\n  }\n\n  pn.webRTCIPHandlingPolicy.set({\n    value: mapRadioIdToPolicy[i]\n  });\n}\n\nfunction restoreRadios(policy) {\n  const radios = document.getElementsByName('ip_policy_selection');\n  radios[mapPolicyToRadioId[policy]].checked = true;\n}\n\nfunction restoreOption() {\n  pn.webRTCIPHandlingPolicy.get({}, function(details) {\n    restoreRadios(details.value);\n  });\n}\n\ndocument.addEventListener('DOMContentLoaded', restoreOption);\ndocument.getElementById('default').\n    addEventListener('click', saveOptions);\ndocument.getElementById('default_public_and_private_interfaces').\n    addEventListener('click', saveOptions);\ndocument.getElementById('default_public_interface_only').\n    addEventListener('click', saveOptions);\ndocument.getElementById('disable_non_proxied_udp').\n    addEventListener('click', saveOptions);\n\ndocument.title = chrome.i18n.getMessage('netli_options');\nconst i18nElements = document.querySelectorAll('*[i18n-content]');\nfor (let i = 0; i < i18nElements.length; i++) {\n  const elem = i18nElements[i];\n  const msg = elem.getAttribute('i18n-content');\n  elem.innerHTML = chrome.i18n.getMessage(msg);\n}\n\nfunction browserSupportsIPHandlingPolicy() {\n  return pn.webRTCIPHandlingPolicy !== undefined;\n}\n\nif (browserSupportsIPHandlingPolicy()) {\n  // Hide the 'not supported' banner.\n  document.getElementById('not_supported').innerHTML = '';\n} else {\n  // Disable all options.\n  for (let i = 0; i < 4; i++) {\n    const key = 'Mode' + i;\n    const section = document.getElementById(key);\n    section.style.color = 'gray';\n    section.querySelector('input').disabled = true;\n  }\n}\n"
  },
  {
    "path": "src/content/extensions/svc/css/main.css",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n\n\ndiv.graph-container {\n\tfloat: left;\n\tmargin: 0.5em;\n\twidth: calc(50% - 1em);\n}\n"
  },
  {
    "path": "src/content/extensions/svc/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Scalable Video Coding (SVC) Extension</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <p>This sample shows how to setup a connection between two peers using\n        <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection</a> and\n        choose the preferred video codec to use and scalability mode when the <a href=\"https://www.w3.org/TR/webrtc-svc/\">Scalable Video Coding (SVC) Extension</a> is available.\n</p>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div class=\"box\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <div class=\"box\">\n        <span>Codec preferences:</span>\n        <select id=\"codecPreferences\" disabled>\n            <option selected value=\"\">Default</option>\n        </select>\n        <br/>\n        <span>Scalability Mode:</span>\n        <select id=\"scalabilityMode\" disabled>\n            <option selected value=\"\">NONE</option>\n        </select>\n        <div id=\"actualCodec\"></div>\n    </div>\n\n    <div class=\"graph-container\" id=\"bitrateGraph\">\n        <div>Bitrate</div>\n        <canvas id=\"bitrateCanvas\"></canvas>\n    </div>\n    <div class=\"graph-container\" id=\"packetGraph\">\n        <div>Packets sent per second</div>\n        <canvas id=\"packetCanvas\"></canvas>\n    </div>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/extensions/svc\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/third_party/graph.js\"></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/extensions/svc/js/main.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* global TimelineDataSeries, TimelineGraphView */\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.addEventListener('click', start);\ncallButton.addEventListener('click', call);\nhangupButton.addEventListener('click', hangup);\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('resize', () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');\n    startTime = null;\n  }\n});\n\nconst codecPreferences = document.querySelector('#codecPreferences');\nconst scalabilityMode = document.querySelector('#scalabilityMode');\nconst supportsSetCodecPreferences = window.RTCRtpTransceiver &&\n  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;\n\nconst scalabilityModes = [\n  'L1T1',\n  'L1T2',\n  'L1T3',\n  'L2T1',\n  'L2T2',\n  'L2T3',\n  'L3T1',\n  'L3T2',\n  'L3T3',\n  'L2T1h',\n  'L2T2h',\n  'L2T3h',\n  'S2T1',\n  'S2T2',\n  'S2T3',\n  'S2T1h',\n  'S2T2h',\n  'S2T3h',\n  'S3T1',\n  'S3T2',\n  'S3T3',\n  'S3T1h',\n  'S3T2h',\n  'S3T3h',\n  'L2T2_KEY',\n  'L2T3_KEY',\n  'L3T2_KEY',\n  'L3T3_KEY'\n];\n\nlet localStream;\nlet pc1;\nlet pc2;\n\nlet bitrateGraph;\nlet bitrateSeries;\nlet headerrateSeries;\n\nlet packetGraph;\nlet packetSeries;\n\nlet lastResult;\n\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia({audio: false, video: true});\n    console.log('Received local stream');\n    localVideo.srcObject = stream;\n    localStream = stream;\n    callButton.disabled = false;\n  } catch (e) {\n    alert(`getUserMedia() error: ${e.name}`);\n  }\n  if (supportsSetCodecPreferences) {\n    const {codecs} = RTCRtpReceiver.getCapabilities('video');\n    codecs.forEach(codec => {\n      if (['video/red', 'video/ulpfec', 'video/rtx', 'video/flexfec-03'].includes(codec.mimeType)) {\n        return;\n      }\n      const option = document.createElement('option');\n      option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim();\n      option.innerText = option.value;\n      codecPreferences.appendChild(option);\n    });\n    codecPreferences.addEventListener('change', async (event) => {\n      const [mimeType] = event.target.value.split(' ');\n      while (scalabilityMode.firstChild) {\n        scalabilityMode.firstChild.remove();\n      }\n      const option = document.createElement('option');\n      option.value = '';\n      option.innerText = 'NONE';\n      scalabilityMode.appendChild(option);\n\n      const capabilityPromises = [];\n      for (const mode of scalabilityModes) {\n        capabilityPromises.push(navigator.mediaCapabilities.encodingInfo({\n          type: 'webrtc',\n          video: {\n            contentType: mimeType,\n            width: 640,\n            height: 480,\n            bitrate: 10000,\n            framerate: 29.97,\n            scalabilityMode: mode\n          }}));\n      }\n      const capabilityResults = await Promise.all(capabilityPromises);\n      for (let i = 0; i < scalabilityModes.length; ++i) {\n        if (capabilityResults[i].supported) {\n          const option = document.createElement('option');\n          option.value = scalabilityModes[i];\n          option.innerText = scalabilityModes[i];\n          scalabilityMode.appendChild(option);\n        }\n      }\n\n      if (scalabilityMode.childElementCount > 1) {\n        scalabilityMode.disabled = false;\n      } else {\n        scalabilityMode.disabled = true;\n      }\n    });\n    codecPreferences.disabled = false;\n  }\n\n  bitrateSeries = new TimelineDataSeries();\n  bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');\n  bitrateGraph.updateEndDate();\n\n  headerrateSeries = new TimelineDataSeries();\n  headerrateSeries.setColor('green');\n\n  packetSeries = new TimelineDataSeries();\n  packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');\n  packetGraph.updateEndDate();\n}\n\nasync function call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const configuration = {};\n  console.log('RTCPeerConnection configuration:', configuration);\n  pc1 = new RTCPeerConnection(configuration);\n  console.log('Created local peer connection object pc1');\n  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));\n  pc2 = new RTCPeerConnection(configuration);\n  console.log('Created remote peer connection object pc2');\n  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));\n  pc2.addEventListener('track', gotRemoteStream);\n  const mode = scalabilityMode.value;\n  localStream.getTracks().forEach((track) =>{\n    if (track.kind == 'video' && mode) {\n      pc1.addTransceiver(track, {\n        streams: [localStream],\n        sendEncodings: [\n          {scalabilityMode: mode}\n        ]\n      });\n    } else {\n      pc1.addTrack(track, localStream);\n    }\n  });\n  console.log('Added local stream to pc1');\n  codecPreferences.disabled = true;\n  scalabilityMode.disabled = true;\n\n  try {\n    console.log('pc1 createOffer start');\n    const offer = await pc1.createOffer(offerOptions);\n    await onCreateOfferSuccess(offer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nasync function onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  try {\n    await pc1.setLocalDescription(desc);\n    onSetLocalSuccess(pc1);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 setRemoteDescription start');\n  try {\n    await pc2.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  try {\n    const answer = await pc2.createAnswer();\n    await onCreateAnswerSuccess(answer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('pc2 received remote stream');\n  }\n  if (supportsSetCodecPreferences) {\n    const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex];\n    if (preferredCodec.value !== '') {\n      const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' ');\n      const {codecs} = RTCRtpReceiver.getCapabilities('video');\n      const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine);\n      const selectedCodec = codecs[selectedCodecIndex];\n      codecs.splice(selectedCodecIndex, 1);\n      codecs.unshift(selectedCodec);\n      e.transceiver.setCodecPreferences(codecs);\n      console.log('Preferred video codec', selectedCodec);\n    }\n  }\n}\n\nasync function onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  try {\n    await pc2.setLocalDescription(desc);\n    onSetLocalSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n  console.log('pc1 setRemoteDescription start');\n  try {\n    await pc1.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc1);\n\n    // Display the video codec that is actually used.\n    setTimeout(async () => {\n      const stats = await pc1.getStats();\n      stats.forEach(stat => {\n        if (!(stat.type === 'outbound-rtp' && stat.kind === 'video')) {\n          return;\n        }\n        const codec = stats.get(stat.codecId);\n        document.getElementById('actualCodec').innerText = 'Using ' + codec.mimeType +\n            ' ' + (codec.sdpFmtpLine ? codec.sdpFmtpLine + ' ' : '') +\n            ', payloadType=' + codec.payloadType + '.';\n      });\n    }, 1000);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n}\n\nasync function onIceCandidate(pc, event) {\n  try {\n    await (getOtherPc(pc).addIceCandidate(event.candidate));\n    onAddIceCandidateSuccess(pc);\n  } catch (e) {\n    onAddIceCandidateError(pc, e);\n  }\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n  codecPreferences.disabled = false;\n  scalabilityMode.disabled = false;\n}\n\n// query getStats every second\nwindow.setInterval(() => {\n  if (!pc1) {\n    return;\n  }\n  const sender = pc1.getSenders()[0];\n  if (!sender) {\n    return;\n  }\n  sender.getStats().then(res => {\n    res.forEach(report => {\n      let bytes;\n      let headerBytes;\n      let packets;\n      if (report.type === 'outbound-rtp') {\n        if (report.isRemote) {\n          return;\n        }\n        const now = report.timestamp;\n        bytes = report.bytesSent;\n        headerBytes = report.headerBytesSent;\n\n        packets = report.packetsSent;\n        if (lastResult && lastResult.has(report.id)) {\n          // calculate bitrate\n          const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /\n            (now - lastResult.get(report.id).timestamp);\n          const headerrate = 8 * (headerBytes - lastResult.get(report.id).headerBytesSent) /\n            (now - lastResult.get(report.id).timestamp);\n\n          // append to chart\n          bitrateSeries.addPoint(now, bitrate);\n          headerrateSeries.addPoint(now, headerrate);\n          bitrateGraph.setDataSeries([bitrateSeries, headerrateSeries]);\n          bitrateGraph.updateEndDate();\n\n          // calculate number of packets and append to chart\n          packetSeries.addPoint(now, packets -\n            lastResult.get(report.id).packetsSent);\n          packetGraph.setDataSeries([packetSeries]);\n          packetGraph.updateEndDate();\n        }\n      }\n    });\n    lastResult = res;\n  });\n}, 1000);\n"
  },
  {
    "path": "src/content/getusermedia/audio/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>gUM audio</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>getUserMedia, audio only</span>\n    </h1>\n\n    <audio id=\"gum-local\" controls autoplay></audio>\n\n    <p class=\"warning\">Warning: if you're not using headphones, pressing play will cause feedback.</p>\n\n    <p>Render the audio stream from an audio-only <code>getUserMedia()</code> call with an audio element.</p>\n\n    <p>The <code>MediaStream</code> object <code><em>stream</em></code> passed to the <code>getUserMedia()</code>\n        callback is in global scope, so you can inspect it from the console.</p>\n    <div>\n        <span id=\"errorMsg\"></span>\n    </div>\n    \n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/audio\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/audio/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n// Put variables in global scope to make them available to the browser console.\nconst audio = document.querySelector('audio');\n\nconst constraints = window.constraints = {\n  audio: true,\n  video: false\n};\n\nfunction handleSuccess(stream) {\n  const audioTracks = stream.getAudioTracks();\n  console.log('Got stream with constraints:', constraints);\n  console.log('Using audio device: ' + audioTracks[0].label);\n  stream.oninactive = function() {\n    console.log('Stream ended');\n  };\n  window.stream = stream; // make variable available to browser console\n  audio.srcObject = stream;\n}\n\nfunction handleError(error) {\n  const errorMessage = 'navigator.MediaDevices.getUserMedia error: ' + error.message + ' ' + error.name;\n  document.getElementById('errorMsg').innerText = errorMessage;\n  console.log(errorMessage);\n}\n\nnavigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);\n"
  },
  {
    "path": "src/content/getusermedia/canvas/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>getUserMedia to canvas</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>getUserMedia ⇒ canvas</span>\n    </h1>\n\n    <video playsinline autoplay></video>\n    <button>Take snapshot</button>\n    <canvas></canvas>\n\n    <p>Draw a frame from the video onto the canvas element using the <code>drawImage()</code> method.</p>\n\n    <p>The variables <code>canvas</code>, <code>video</code> and <code>stream</code> are in global scope, so you can\n        inspect them from the console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/canvas\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/canvas/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n// Put variables in global scope to make them available to the browser console.\nconst video = document.querySelector('video');\nconst canvas = window.canvas = document.querySelector('canvas');\ncanvas.width = 480;\ncanvas.height = 360;\n\nconst button = document.querySelector('button');\nbutton.onclick = function() {\n  canvas.width = video.videoWidth;\n  canvas.height = video.videoHeight;\n  canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);\n};\n\nconst constraints = {\n  audio: false,\n  video: true\n};\n\nfunction handleSuccess(stream) {\n  window.stream = stream; // make stream available to browser console\n  video.srcObject = stream;\n}\n\nfunction handleError(error) {\n  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);\n}\n\nnavigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);\n"
  },
  {
    "path": "src/content/getusermedia/exposure/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Control Exposure</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n    <style>\n        div.label {\n            display: inline-block;\n            font-weight: 400;\n            margin: 0 0.5em 0 0;\n            width: 10em;\n        }\n    </style>\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Control Exposure</span></h1>\n\n    <video id=\"gum-local\" autoplay playsinline></video>\n    <button id=\"showVideo\">Open camera</button>\n\n    <div>\n        <div class=\"label\">Exposure Mode:</div>\n        <select name=\"exposureMode\" id=\"exposureMode\" disabled>\n        </select>\n    </div>\n\n    <div>\n        <div class=\"label\">Exposure Time:</div>\n        <input name=\"exposureTime\" type=\"range\" disabled>\n    </div>\n\n    <div>\n        <div class=\"label\">Exposure Compensation:</div>\n        <input name=\"exposureCompensation\" type=\"range\" disabled>\n    </div>\n\n\t<div>\n        <div class=\"label\">Brightness:</div>\n        <input name=\"brightness\" type=\"range\" disabled>\n    </div>\n\n\t<div>\n        <div class=\"label\">White Balance Mode:</div>\n        <select name=\"whiteBalanceMode\" id=\"whiteBalanceMode\" disabled>\n        </select>\n    </div>\n\n\t<button id=\"refreshControls\" onClick=\"loadProperties(true)\" disabled>Refresh Controls</button>\n\n    <div id=\"errorMsg\"></div>\n\n    <p>Display the video stream from <code>getUserMedia()</code> in a video\n    element and control exposureMode, exposureTime and exposureCompensation if camera supports it.</p>\n\n    <p>The <code>MediaStreamTrack</code> object <code>track</code> is in\n        global scope, so you can inspect it from the console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/exposure\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/exposure/js/main.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n'use strict';\n\n// Put variables in global scope to make them available to the browser console.\nconst constraints = window.constraints = {\n  audio: false,\n  video: true\n};\n\nfunction handleSuccess(stream) {\n  const video = document.querySelector('video');\n  const videoTracks = stream.getVideoTracks();\n  console.log('Got stream with constraints:', constraints);\n  console.log(`Using video device: ${videoTracks[0].label}`);\n  video.srcObject = stream;\n\n  // make track variable available to browser console.\n  [window.track] = stream.getVideoTracks();\n\n  loadProperties();\n\n  document.querySelector(`button[id=refreshControls]`).disabled = false;\n}\n\nfunction loadProperties(refreshValuesOnly) {\n  const track = window.track;\n  const capabilities = track.getCapabilities();\n  const settings = track.getSettings();\n  console.log('Capabilities: ', capabilities);\n  console.log('Settings: ', settings);\n\n  for (const property of ['exposureMode', 'exposureTime', 'exposureCompensation', 'brightness', 'whiteBalanceMode']) {\n    // Check whether camera supports exposure.\n    if (!(property in settings)) {\n      errorMsg(`Camera does not support ${property}.`);\n      continue;\n    }\n\n    let element;\n\n    if (Array.isArray(capabilities[property])) {\n      // Map it to a select element.\n      const select = document.querySelector(`select[name=${property}]`);\n      element = select;\n      if (capabilities[property] && !refreshValuesOnly) {\n        for (const mode of capabilities[property]) {\n          select.insertAdjacentHTML('afterbegin', `<option value=\"${mode}\">${mode}</option>`);\n        }\n      }\n    } else {\n      // Map it to a slider element.\n      const input = document.querySelector(`input[name=${property}]`);\n      element = input;\n      input.min = capabilities[property].min;\n      input.max = capabilities[property].max;\n      input.step = capabilities[property].step;\n    }\n\n    element.value = settings[property];\n    element.disabled = false;\n    if (!refreshValuesOnly) {\n      element.oninput = async event => {\n        try {\n          const constraints = {advanced: [{[property]: element.value}]};\n          await track.applyConstraints(constraints);\n          console.log('Did successfully apply new constraints: ', constraints);\n          console.log('New camera settings: ', track.getSettings());\n        } catch (err) {\n          console.error('applyConstraints() failed: ', err);\n        }\n      };\n    }\n  }\n}\n\nfunction handleError(error) {\n  if (error.name === 'NotAllowedError') {\n    errorMsg('Permissions have not been granted to use your camera, ' +\n      'you need to allow the page access to your devices in ' +\n      'order for the demo to work.');\n  }\n  errorMsg(`getUserMedia error: ${error.name}`, error);\n}\n\nfunction errorMsg(msg, error) {\n  const errorElement = document.querySelector('#errorMsg');\n  errorElement.innerHTML += `<p>${msg}</p>`;\n  if (typeof error !== 'undefined') {\n    console.error(error);\n  }\n}\n\nasync function init(e) {\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia(constraints);\n    handleSuccess(stream);\n    e.target.disabled = true;\n  } catch (e) {\n    handleError(e);\n  }\n}\n\ndocument.querySelector('#showVideo').addEventListener('click', e => init(e));\n"
  },
  {
    "path": "src/content/getusermedia/filter/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>getUserMedia + CSS filters</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n    <style>\n        .none {\n            -webkit-filter: none;\n            filter: none;\n        }\n\n        .blur {\n            -webkit-filter: blur(3px);\n            filter: blur(3px);\n        }\n\n        .grayscale {\n            -webkit-filter: grayscale(1);\n            filter: grayscale(1);\n        }\n\n        .invert {\n            -webkit-filter: invert(1);\n            filter: invert(1);\n        }\n\n        .sepia {\n            -webkit-filter: sepia(1);\n            filter: sepia(1);\n        }\n\n        button#snapshot {\n            margin: 0 10px 25px 0;\n            width: 110px;\n        }\n\n        video {\n            object-fit: cover;\n        }\n    </style>\n\n</head>\n\n<body>\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>getUserMedia + CSS filters</span>\n    </h1>\n\n    <video playsinline autoplay></video>\n\n    <label for=\"filter\">Filter: </label>\n    <select id=\"filter\">\n        <option value=\"none\">None</option>\n        <option value=\"blur\">Blur</option>\n        <option value=\"grayscale\">Grayscale</option>\n        <option value=\"invert\">Invert</option>\n        <option value=\"sepia\">Sepia</option>\n    </select>\n\n    <button id=\"snapshot\">Take snapshot</button>\n\n    <canvas></canvas>\n\n    <p>Draw a frame from the getUserMedia video stream onto the canvas element, then apply CSS filters.</p>\n\n    <p>The variables <code>canvas</code>, <code>video</code> and <code>stream</code> are in global scope, so you can\n        inspect them from the console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/filter\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/filter/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst snapshotButton = document.querySelector('button#snapshot');\nconst filterSelect = document.querySelector('select#filter');\n\n// Put variables in global scope to make them available to the browser console.\nconst video = window.video = document.querySelector('video');\nconst canvas = window.canvas = document.querySelector('canvas');\ncanvas.width = 480;\ncanvas.height = 360;\n\nsnapshotButton.onclick = function() {\n  canvas.className = filterSelect.value;\n  canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);\n};\n\nfilterSelect.onchange = function() {\n  video.className = filterSelect.value;\n};\n\nconst constraints = {\n  audio: false,\n  video: true\n};\n\nfunction handleSuccess(stream) {\n  window.stream = stream; // make stream available to browser console\n  video.srcObject = stream;\n}\n\nfunction handleError(error) {\n  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);\n}\n\nnavigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);\n"
  },
  {
    "path": "src/content/getusermedia/getdisplaymedia/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n  <meta charset=\"utf-8\">\n  <meta name=\"description\" content=\"WebRTC code samples\">\n  <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n  <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n  <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n  <meta itemprop=\"name\" content=\"WebRTC code samples\">\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n  <base target=\"_blank\">\n\n  <title>getDisplayMedia</title>\n\n  <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n  <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n  <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n</head>\n\n<body>\n\n  <div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>getDisplayMedia</span></h1>\n\n    <video id=\"video\" autoplay playsinline muted></video>\n    <button id=\"startButton\" disabled>Start</button>\n    <fieldset id=\"options\" style=\"display:none\">\n      <legend>Advanced options</legend>\n      <select id=\"displaySurface\">\n        <option value=\"default\" selected>Show default sharing options</option>\n        <option value=\"browser\">Prefer to share a browser tab</option>\n        <option value=\"window\">Prefer to share a window</option>\n        <option value=\"monitor\">Prefer to share an entire screen</option>\n      </select>\n    </fieldset>\n    <div id=\"errorMsg\"></div>\n\n    <p>Display the screensharing stream from <code>getDisplayMedia()</code> in a video element.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/getdisplaymedia\" title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n  </div>\n\n  <script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n  <script src=\"js/main.js\"></script>\n\n  <script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/getdisplaymedia/js/main.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n'use strict';\n\nconst preferredDisplaySurface = document.getElementById('displaySurface');\nconst startButton = document.getElementById('startButton');\n\nif (adapter.browserDetails.browser === 'chrome' &&\n    adapter.browserDetails.version >= 107) {\n  // See https://developer.chrome.com/docs/web-platform/screen-sharing-controls/\n  document.getElementById('options').style.display = 'block';\n} else if (adapter.browserDetails.browser === 'firefox') {\n  // Polyfill in Firefox.\n  // See https://blog.mozilla.org/webrtc/getdisplaymedia-now-available-in-adapter-js/\n  adapter.browserShim.shimGetDisplayMedia(window, 'screen');\n}\n\nfunction handleSuccess(stream) {\n  startButton.disabled = true;\n  preferredDisplaySurface.disabled = true;\n  const video = document.querySelector('video');\n  video.srcObject = stream;\n\n  // demonstrates how to detect that the user has stopped\n  // sharing the screen via the browser UI.\n  stream.getVideoTracks()[0].addEventListener('ended', () => {\n    errorMsg('The user has ended sharing the screen');\n    startButton.disabled = false;\n    preferredDisplaySurface.disabled = false;\n  });\n}\n\nfunction handleError(error) {\n  errorMsg(`getDisplayMedia error: ${error.name}`, error);\n}\n\nfunction errorMsg(msg, error) {\n  const errorElement = document.querySelector('#errorMsg');\n  errorElement.innerHTML += `<p>${msg}</p>`;\n  if (typeof error !== 'undefined') {\n    console.error(error);\n  }\n}\n\n\nstartButton.addEventListener('click', () => {\n  const options = {audio: true, video: true};\n  const displaySurface = preferredDisplaySurface.options[preferredDisplaySurface.selectedIndex].value;\n  if (displaySurface !== 'default') {\n    options.video = {displaySurface};\n  }\n  navigator.mediaDevices.getDisplayMedia(options)\n      .then(handleSuccess, handleError);\n});\n\nif ((navigator.mediaDevices && 'getDisplayMedia' in navigator.mediaDevices)) {\n  startButton.disabled = false;\n} else {\n  errorMsg('getDisplayMedia is not supported');\n}\n"
  },
  {
    "path": "src/content/getusermedia/gum/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>getUserMedia</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>getUserMedia</span></h1>\n\n    <video id=\"gum-local\" autoplay playsinline></video>\n    <button id=\"showVideo\">Open camera</button>\n\n    <div id=\"errorMsg\"></div>\n\n    <p class=\"warning\"><strong>Warning:</strong> if you're not using headphones, pressing play will cause feedback.</p>\n\n    <p>Display the video stream from <code>getUserMedia()</code> in a video element.</p>\n\n    <p>The <code>MediaStream</code> object <code>stream</code> passed to the <code>getUserMedia()</code> callback is in\n        global scope, so you can inspect it from the console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/gum\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/gum/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n'use strict';\n\n// Put variables in global scope to make them available to the browser console.\nconst constraints = window.constraints = {\n  audio: false,\n  video: true\n};\n\nfunction handleSuccess(stream) {\n  const video = document.querySelector('video');\n  const videoTracks = stream.getVideoTracks();\n  console.log('Got stream with constraints:', constraints);\n  console.log(`Using video device: ${videoTracks[0].label}`);\n  window.stream = stream; // make variable available to browser console\n  video.srcObject = stream;\n}\n\nfunction handleError(error) {\n  if (error.name === 'OverconstrainedError') {\n    errorMsg(`OverconstrainedError: The constraints could not be satisfied by the available devices. Constraints: ${JSON.stringify(constraints)}`);\n  } else if (error.name === 'NotAllowedError') {\n    errorMsg('NotAllowedError: Permissions have not been granted to use your camera and ' +\n      'microphone, you need to allow the page access to your devices in ' +\n      'order for the demo to work.');\n  }\n  errorMsg(`getUserMedia error: ${error.name}`, error);\n}\n\nfunction errorMsg(msg, error) {\n  const errorElement = document.querySelector('#errorMsg');\n  errorElement.innerHTML += `<p>${msg}</p>`;\n  if (typeof error !== 'undefined') {\n    console.error(error);\n  }\n}\n\nasync function init(e) {\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia(constraints);\n    handleSuccess(stream);\n    e.target.disabled = true;\n  } catch (e) {\n    handleError(e);\n  }\n}\n\ndocument.querySelector('#showVideo').addEventListener('click', e => init(e));\n"
  },
  {
    "path": "src/content/getusermedia/gum/js/test.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n'use strict';\n\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/getusermedia/gum/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('getUserMedia', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('opens a camera', async () => {\n    await driver.findElement(webdriver.By.css('button')).click();\n    await driver.wait(() => driver.executeScript(() =>\n      document.querySelector('video').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA)\n    );\n    const width = await driver.findElement(webdriver.By.css('video')).getAttribute('videoWidth');\n    expect(width >>> 0).toBeGreaterThan(320);\n  });\n});\n\n"
  },
  {
    "path": "src/content/getusermedia/pan-tilt-zoom/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Control camera pan, tilt, and zoom</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n    <style>\n        div.label {\n            display: inline-block;\n            font-weight: 400;\n            margin: 0 0.5em 0 0;\n            width: 3.5em;\n        }\n    </style>\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Control Pan-Tilt-Zoom Camera</span></h1>\n\n    <video id=\"gum-local\" autoplay playsinline></video>\n    <button id=\"showVideo\">Open camera</button>\n\n    <div>\n        <div class=\"label\">Pan:</div>\n        <input name=\"pan\" type=\"range\" disabled>\n    </div>\n    <div>\n        <div class=\"label\">Tilt:</div>\n        <input name=\"tilt\" type=\"range\" disabled>\n    </div>\n    <div>\n        <div class=\"label\">Zoom:</div>\n        <input name=\"zoom\" type=\"range\" disabled>\n    </div>\n\n    <div id=\"errorMsg\"></div>\n\n    <p>Display the video stream from <code>getUserMedia()</code> in a video\n    element and control pan, tilt, and zoom if camera supports Pan-Tilt-Zoom.</p>\n\n    <p>The <code>MediaStreamTrack</code> object <code>track</code> is in\n        global scope, so you can inspect it from the console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/pan-tilt-zoom\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/pan-tilt-zoom/js/main.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n'use strict';\n\n// Put variables in global scope to make them available to the browser console.\nconst constraints = window.constraints = {\n  video: {\n    pan: true, tilt: true, zoom: true\n  }\n};\n\nfunction handleSuccess(stream) {\n  const video = document.querySelector('video');\n  const videoTracks = stream.getVideoTracks();\n  console.log('Got stream with constraints:', constraints);\n  console.log(`Using video device: ${videoTracks[0].label}`);\n  video.srcObject = stream;\n\n  // make track variable available to browser console.\n  const [track] = [window.track] = stream.getVideoTracks();\n  const capabilities = track.getCapabilities();\n  const settings = track.getSettings();\n\n  for (const ptz of ['pan', 'tilt', 'zoom']) {\n    // Check whether camera supports pan/tilt/zoom.\n    if (!(ptz in settings)) {\n      errorMsg(`Camera does not support ${ptz}.`);\n      continue;\n    }\n\n    // Map it to a slider element.\n    const input = document.querySelector(`input[name=${ptz}]`);\n    input.min = capabilities[ptz].min;\n    input.max = capabilities[ptz].max;\n    input.step = capabilities[ptz].step;\n    input.value = settings[ptz];\n    input.disabled = false;\n    input.oninput = async event => {\n      try {\n        const constraints = {advanced: [{[ptz]: input.value}]};\n        await track.applyConstraints(constraints);\n      } catch (err) {\n        console.error('applyConstraints() failed: ', err);\n      }\n    };\n  }\n}\n\nfunction handleError(error) {\n  if (error.name === 'NotAllowedError') {\n    errorMsg('Permissions have not been granted to use your camera, ' +\n      'you need to allow the page access to your devices in ' +\n      'order for the demo to work.');\n  }\n  errorMsg(`getUserMedia error: ${error.name}`, error);\n}\n\nfunction errorMsg(msg, error) {\n  const errorElement = document.querySelector('#errorMsg');\n  errorElement.innerHTML += `<p>${msg}</p>`;\n  if (typeof error !== 'undefined') {\n    console.error(error);\n  }\n}\n\nasync function init(e) {\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia(constraints);\n    handleSuccess(stream);\n    e.target.disabled = true;\n  } catch (e) {\n    handleError(e);\n  }\n}\n\ndocument.querySelector('#showVideo').addEventListener('click', e => init(e));\n"
  },
  {
    "path": "src/content/getusermedia/record/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n button {\n  margin: 0 3px 10px 0;\n  padding-left: 2px;\n  padding-right: 2px;\n  width: 120px;\n}\n\n/* copied from main button style */\n.file-upload {\n  background-color: #d84a38;\n  border: none;\n  border-radius: 2px;\n  color: white;\n  font-family: 'Roboto', sans-serif;\n  font-size: 0.8em;\n  margin: 0 0 1em 0;\n  padding: 0.5em 0.7em 0.6em 0.7em;\n}\n\n.file-upload:hover {\n  background-color: #cf402f;\n}\n\nbutton:last-of-type {\n  margin: 0;\n}\n\np.borderBelow {\n  margin: 0 0 20px 0;\n  padding: 0 0 20px 0;\n}\n\nvideo {\n  vertical-align: top;\n  --width: 25vw;\n  width: var(--width);\n  height: calc(var(--width) * 0.5625);\n}\n\nvideo:last-of-type {\n  margin: 0 0 20px 0;\n}\n\nvideo#gumVideo {\n  margin: 0 20px 20px 0;\n}\n\ninput[type=\"file\"] {\n  display: none;\n}\n"
  },
  {
    "path": "src/content/getusermedia/record/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>MediaStream Recording</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>MediaRecorder</span></h1>\n\n    <p>For more information see the MediaStream Recording API <a\n            href=\"http://w3c.github.io/mediacapture-record/MediaRecorder.html\"\n            title=\"W3C MediaStream Recording API Editor's Draft\">Editor's&nbsp;Draft</a>.</p>\n\n    <video id=\"gum\" playsinline autoplay muted></video>\n    <video id=\"recorded\" playsinline loop></video>\n\n    <div>\n        <button id=\"start-gum\">Start camera</button>\n        <button id=\"start-gdm\">Start screenshare</button>\n        <button id=\"record\" disabled>Start Recording</button>\n        <button id=\"play\" disabled>Play</button>\n        <button id=\"download\" disabled>Download</button>\n        <label for=\"upload\" class=\"file-upload\">upload locally</label><input id=\"upload\" type=\"file\"/>\n    </div>\n\n    <div>\n        Recording format: <select id=\"codecPreferences\" disabled></select>\n    </div>\n    <div>\n        <h4>Media Stream Constraints options</h4>\n        <p>Echo cancellation: <input type=\"checkbox\" id=\"echoCancellation\"></p>\n    </div>\n\n    <div>\n        <span id=\"errorMsg\"></span>\n    </div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/record\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<!-- include adapter for srcObject shim -->\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/record/js/main.js",
    "content": "/*\n*  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\n// This code is adapted from\n// https://rawgit.com/Miguelao/demos/master/mediarecorder.html\n\n'use strict';\n\n/* globals MediaRecorder */\n\nlet mediaRecorder;\nlet recordedBlobs;\n\nconst codecPreferences = document.querySelector('#codecPreferences');\nconst errorMsgElement = document.querySelector('span#errorMsg');\nconst recordedVideo = document.querySelector('video#recorded');\nconst recordButton = document.querySelector('button#record');\nrecordButton.addEventListener('click', () => {\n  if (recordButton.textContent === 'Start Recording') {\n    startRecording();\n    uploadButton.disabled = true;\n  } else {\n    stopRecording();\n    recordButton.textContent = 'Start Recording';\n    playButton.disabled = false;\n    downloadButton.disabled = false;\n    codecPreferences.disabled = false;\n  }\n});\n\nfunction doPlay(blob) {\n  recordedVideo.src = null;\n  recordedVideo.srcObject = null;\n  recordedVideo.src = window.URL.createObjectURL(blob);\n  recordedVideo.controls = true;\n  recordedVideo.play();\n};\n\nconst playButton = document.querySelector('button#play');\nplayButton.addEventListener('click', () => {\n  const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value.split(';', 1)[0];\n  const superBuffer = new Blob(recordedBlobs, {type: mimeType});\n  doPlay(superBuffer);\n});\n\nconst uploadButton = document.querySelector('#upload');\nuploadButton.addEventListener('change', e => {\n  doPlay(e.target.files[0]);\n});\n\nconst downloadButton = document.querySelector('button#download');\ndownloadButton.addEventListener('click', () => {\n  const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value.split(';', 1)[0];\n  const blob = new Blob(recordedBlobs, {type: mimeType});\n  const url = window.URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.style.display = 'none';\n  a.href = url;\n  a.download = `test.${mimeTypeToFileExtension(mimeType)}`;\n  document.body.appendChild(a);\n  a.click();\n  setTimeout(() => {\n    document.body.removeChild(a);\n    window.URL.revokeObjectURL(url);\n  }, 100);\n});\n\nfunction mimeTypeToFileExtension(mimeType) {\n  switch (mimeType) {\n    case 'video/mp4':\n      return 'mp4';\n    case 'video/webm':\n      return 'webm';\n    case 'video/x-matroska':\n      return 'mkv';\n    default:\n      throw new Error(`unsupported mimetype: ${mimeType}`);\n  }\n}\n\nfunction handleDataAvailable(event) {\n  console.log('handleDataAvailable', event);\n  if (event.data && event.data.size > 0) {\n    recordedBlobs.push(event.data);\n  }\n}\n\nfunction getSupportedMimeTypes() {\n  const possibleTypes = [\n    'video/webm;codecs=vp9,opus',\n    'video/webm;codecs=vp8,opus',\n    'video/webm;codecs=h264,opus',\n    'video/webm;codecs=av01,opus',\n    'video/x-matroska;codecs=hvc1.1.6.L186.B0,opus',\n    'video/mp4;codecs=vp9,mp4a.40.2',\n    'video/mp4;codecs=vp9,opus',\n    'video/mp4;codecs=avc1.64003E,mp4a.40.2',\n    'video/mp4;codecs=avc1.64003E,opus',\n    'video/mp4;codecs=avc3.64003E,mp4a.40.2',\n    'video/mp4;codecs=avc3.64003E,opus',\n    'video/mp4;codecs=hvc1.1.6.L186.B0,mp4a.40.2',\n    'video/mp4;codecs=hvc1.1.6.L186.B0,opus',\n    'video/mp4;codecs=hev1.1.6.L186.B0,mp4a.40.2',\n    'video/mp4;codecs=hev1.1.6.L186.B0,opus',\n    'video/mp4;codecs=av01.0.19M.08,mp4a.40.2',\n    'video/mp4;codecs=av01.0.19M.08,opus',\n    'video/mp4',\n  ];\n  return possibleTypes.filter(mimeType => {\n    return MediaRecorder.isTypeSupported(mimeType);\n  });\n}\n\nasync function startRecording() {\n  recordedBlobs = [];\n  const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value;\n  const options = {mimeType};\n  if (mimeType.split(';', 1)[0] === 'video/mp4') {\n    // Adjust sampling rate to 48khz.\n    const track = window.stream.getAudioTracks()[0];\n    if (track) {\n      const {sampleRate} = track.getSettings();\n      if (sampleRate != 48000) {\n        track.stop();\n        window.stream.removeTrack(track);\n        const newStream = await navigator.mediaDevices.getUserMedia({audio: {sampleRate: 48000}});\n        window.stream.addTrack(newStream.getTracks()[0]);\n      }\n    }\n  }\n  try {\n    mediaRecorder = new MediaRecorder(window.stream, options);\n  } catch (e) {\n    console.error('Exception while creating MediaRecorder:', e);\n    errorMsgElement.innerHTML = `Exception while creating MediaRecorder: ${JSON.stringify(e)}`;\n    return;\n  }\n\n  console.log('Created MediaRecorder', mediaRecorder, 'with options', options);\n  recordButton.textContent = 'Stop Recording';\n  playButton.disabled = true;\n  downloadButton.disabled = true;\n  codecPreferences.disabled = true;\n  mediaRecorder.onstop = (event) => {\n    console.log('Recorder stopped: ', event);\n    console.log('Recorded Blobs: ', recordedBlobs);\n  };\n  mediaRecorder.ondataavailable = handleDataAvailable;\n  mediaRecorder.start();\n  console.log('MediaRecorder started', mediaRecorder);\n}\n\nfunction stopRecording() {\n  mediaRecorder.stop();\n}\n\nfunction handleSuccess(stream) {\n  recordButton.disabled = false;\n  console.log('Got stream:', stream);\n  window.stream = stream;\n\n  const gumVideo = document.querySelector('video#gum');\n  gumVideo.srcObject = stream;\n\n  getSupportedMimeTypes().forEach(mimeType => {\n    const option = document.createElement('option');\n    option.value = mimeType;\n    option.innerText = option.value;\n    codecPreferences.appendChild(option);\n  });\n  codecPreferences.disabled = false;\n}\n\nasync function init(constraints, isGetDisplayMedia) {\n  try {\n    const stream = isGetDisplayMedia ?\n        await navigator.mediaDevices.getDisplayMedia(constraints) :\n        await navigator.mediaDevices.getUserMedia(constraints);\n    handleSuccess(stream);\n  } catch (e) {\n    console.error('Source open error:', e);\n    errorMsgElement.innerHTML = `Source error: ${e.toString()}`;\n  }\n}\n\nasync function onStart(isGetDisplayMedia) {\n  document.querySelector('button#start-gum').disabled = true;\n  document.querySelector('button#start-gdm').disabled = true;\n  const hasEchoCancellation = document.querySelector('#echoCancellation').checked;\n  const constraints = {\n    audio: {\n      echoCancellation: hasEchoCancellation\n    },\n    video: {\n      width: 1280, height: 720\n    }\n  };\n  console.log('Using media constraints:', constraints);\n  await init(constraints, isGetDisplayMedia);\n}\n\ndocument.querySelector('button#start-gum').addEventListener('click', async () => {\n  await onStart(false);\n});\ndocument.querySelector('button#start-gdm').addEventListener('click', async () => {\n  await onStart(true);\n});\n"
  },
  {
    "path": "src/content/getusermedia/resolution/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>getUserMedia: select resolution</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n    <style>\n        body, html {\n            height: 100%;\n        }\n\n        button {\n            margin: 0 10px 20px 0;\n            min-width: 90px;\n        }\n\n        div#buttons {\n            margin: 0 0 1em 0;\n        }\n\n        div#container {\n            max-width: 100%;\n        }\n\n        #errormessage {\n            display: none;\n            font-size: 300%;\n        }\n\n        #videoblock {\n            display: none;\n        }\n\n        p#dimensions {\n            height: 1em;\n            margin: 0 0 1.5em 0;\n        }\n\n        video {\n            background: none;\n            height: auto;\n            width: auto;\n        }\n    </style>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>getUserMedia: select resolution</span>\n    </h1>\n    <p></p>\n\n    <p>This example uses <a href=\"https://w3c.github.io/mediacapture-main/getusermedia.html#media-track-constraints\"\n                            title=\"W3C getusermedia specification - constraints section\">constraints</a>.</p>\n\n    <p>Click a button to call <code>getUserMedia()</code> with appropriate resolution.</p>\n    <div class=\"select\">\n        <label for=\"videoSource\">Video source: </label><select id=\"videoSource\"></select>\n    </div>\n    <div id=\"buttons\">\n        <button id=\"p180\">180p (320x180)</button>\n        <button id=\"qvga\">QVGA (320x240)</button>\n        <button id=\"p360\">360p (640x360)</button>\n        <button id=\"vga\">VGA (640x480)</button>\n        <button id=\"hd\">HD/720p (1280x720)</button>\n        <button id=\"full-hd\">Full HD/1080p (1920x1080)</button>\n        <button id=\"televisionFourK\">Television 4K/2160p (3840x2160)</button>\n        <button id=\"cinemaFourK\">Cinema 4K (4096x2160)</button>\n        <button id=\"eightK\">8K</button>\n    </div>\n\n    <div id=\"videoblock\">\n        <p id=\"dimensions\"></p>\n\n        <video id=\"gum-res-local\" playsinline autoplay></video>\n        <div id=\"width\">\n            <label>Width <span></span>px:</label>\n            <input type=\"range\" min=\"0\" max=\"7680\" value=\"0\">\n        </div>\n        <input id=\"sizelock\" type=\"checkbox\">Lock video size<br>\n        <input id=\"aspectlock\" type=\"checkbox\">Lock aspect ratio<br>\n        <input id=\"pausevideo\" type=\"checkbox\">Pause video<br>\n    </div>\n    <p id=\"errormessage\"></p>\n\n    <p>For more information, see <a href=\"http://www.html5rocks.com/en/tutorials/getusermedia/intro/\"\n                                    title=\"Media capture article by Eric Bidelman on HTML5 Rocks\">Capturing Audio &amp;\n        Video in HTML5</a> on HTML5 Rocks.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/resolution\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/resolution/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst dimensions = document.querySelector('#dimensions');\nconst video = document.querySelector('video');\nlet stream;\n\nconst qvgaButton = document.querySelector('#qvga');\nconst p180Button = document.querySelector('#p180');\nconst vgaButton = document.querySelector('#vga');\nconst p360Button = document.querySelector('#p360');\nconst hdButton = document.querySelector('#hd');\nconst fullHdButton = document.querySelector('#full-hd');\nconst cinemaFourKButton = document.querySelector('#cinemaFourK');\nconst televisionFourKButton = document.querySelector('#televisionFourK');\nconst eightKButton = document.querySelector('#eightK');\n\nconst videoblock = document.querySelector('#videoblock');\nconst messagebox = document.querySelector('#errormessage');\n\nconst widthInput = document.querySelector('div#width input');\nconst widthOutput = document.querySelector('div#width span');\nconst aspectLock = document.querySelector('#aspectlock');\nconst sizeLock = document.querySelector('#sizelock');\nconst pauseVideo = document.querySelector('#pausevideo');\n\nconst videoSelect = document.querySelector('select#videoSource');\n\nlet currentWidth = 0;\nlet currentHeight = 0;\n\np180Button.onclick = () => {\n  getMedia(p180Constraints);\n};\n\nqvgaButton.onclick = () => {\n  getMedia(qvgaConstraints);\n};\n\np360Button.onclick = () => {\n  getMedia(p360Constraints);\n};\n\nvgaButton.onclick = () => {\n  getMedia(vgaConstraints);\n};\n\nhdButton.onclick = () => {\n  getMedia(hdConstraints);\n};\n\nfullHdButton.onclick = () => {\n  getMedia(fullHdConstraints);\n};\n\ntelevisionFourKButton.onclick = () => {\n  getMedia(televisionFourKConstraints);\n};\n\ncinemaFourKButton.onclick = () => {\n  getMedia(cinemaFourKConstraints);\n};\n\neightKButton.onclick = () => {\n  getMedia(eightKConstraints);\n};\n\npauseVideo.onchange = () => {\n  if (pauseVideo.checked) {\n    video.pause();\n  } else {\n    video.play();\n  }\n};\n\nconst p180Constraints = {\n  video: {width: {exact: 320}, height: {exact: 180}}\n};\n\nconst qvgaConstraints = {\n  video: {width: {exact: 320}, height: {exact: 240}}\n};\n\nconst p360Constraints = {\n  video: {width: {exact: 640}, height: {exact: 360}}\n};\n\nconst vgaConstraints = {\n  video: {width: {exact: 640}, height: {exact: 480}}\n};\n\nconst hdConstraints = {\n  video: {width: {exact: 1280}, height: {exact: 720}}\n};\n\nconst fullHdConstraints = {\n  video: {width: {exact: 1920}, height: {exact: 1080}}\n};\n\nconst televisionFourKConstraints = {\n  video: {width: {exact: 3840}, height: {exact: 2160}}\n};\n\nconst cinemaFourKConstraints = {\n  video: {width: {exact: 4096}, height: {exact: 2160}}\n};\n\nconst eightKConstraints = {\n  video: {width: {exact: 7680}, height: {exact: 4320}}\n};\n\nfunction gotDevices(deviceInfos) {\n  // Handles being called several times to update labels. Preserve values.\n  while (videoSelect.firstChild) {\n    videoSelect.removeChild(videoSelect.firstChild);\n  }\n  for (let i = 0; i !== deviceInfos.length; ++i) {\n    const deviceInfo = deviceInfos[i];\n    const option = document.createElement('option');\n    option.value = deviceInfo.deviceId;\n    if (deviceInfo.kind === 'videoinput') {\n      option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;\n      videoSelect.appendChild(option);\n    }\n  }\n}\n\nfunction handleError(error) {\n  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);\n}\n\nnavigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);\n\nfunction gotStream(mediaStream) {\n  stream = window.stream = mediaStream; // stream available to console\n  video.srcObject = mediaStream;\n  messagebox.style.display = 'none';\n  videoblock.style.display = 'block';\n  const track = mediaStream.getVideoTracks()[0];\n  const constraints = track.getConstraints();\n  console.log('Result constraints: ' + JSON.stringify(constraints));\n  if (constraints && constraints.width && constraints.width.exact) {\n    widthInput.value = constraints.width.exact;\n    widthOutput.textContent = constraints.width.exact;\n  } else if (constraints && constraints.width && constraints.width.min) {\n    widthInput.value = constraints.width.min;\n    widthOutput.textContent = constraints.width.min;\n  }\n}\n\nfunction errorMessage(who, what) {\n  const message = who + ': ' + what;\n  messagebox.innerText = message;\n  messagebox.style.display = 'block';\n  console.log(message);\n}\n\nfunction clearErrorMessage() {\n  messagebox.style.display = 'none';\n}\n\nfunction displayVideoDimensions(whereSeen) {\n  if (video.videoWidth) {\n    dimensions.innerText = 'Actual video dimensions: ' + video.videoWidth +\n      'x' + video.videoHeight + 'px.';\n    if (currentWidth !== video.videoWidth ||\n      currentHeight !== video.videoHeight) {\n      console.log(whereSeen + ': ' + dimensions.innerText);\n      currentWidth = video.videoWidth;\n      currentHeight = video.videoHeight;\n    }\n  } else {\n    dimensions.innerText = 'Video not ready';\n  }\n}\n\nvideo.onloadedmetadata = () => {\n  displayVideoDimensions('loadedmetadata');\n};\n\nvideo.onresize = () => {\n  displayVideoDimensions('resize');\n};\n\nfunction constraintChange(e) {\n  widthOutput.textContent = e.target.value;\n  const track = window.stream.getVideoTracks()[0];\n  let constraints;\n  if (aspectLock.checked) {\n    constraints = {\n      width: {exact: e.target.value},\n      aspectRatio: {\n        exact: video.videoWidth / video.videoHeight\n      }\n    };\n  } else {\n    constraints = {width: {exact: e.target.value}};\n  }\n  clearErrorMessage();\n  console.log('applying ' + JSON.stringify(constraints));\n  track.applyConstraints(constraints)\n      .then(() => {\n        console.log('applyConstraint success');\n        displayVideoDimensions('applyConstraints');\n      })\n      .catch(err => {\n        errorMessage('applyConstraints', err.name);\n      });\n}\n\nwidthInput.onchange = constraintChange;\n\nsizeLock.onchange = () => {\n  if (sizeLock.checked) {\n    console.log('Setting fixed size');\n    video.style.width = '100%';\n  } else {\n    console.log('Setting auto size');\n    video.style.width = 'auto';\n  }\n};\n\nfunction getMedia(constraints) {\n  if (stream) {\n    stream.getTracks().forEach(track => {\n      track.stop();\n    });\n  }\n\n  clearErrorMessage();\n  videoblock.style.display = 'none';\n  constraints.video.deviceId = {exact: videoSelect.value};\n  console.log('getUserMedia constraints: ' + JSON.stringify(constraints));\n  navigator.mediaDevices.getUserMedia(constraints)\n      .then(gotStream)\n      .catch(e => {\n        errorMessage('getUserMedia', e.message, e.name);\n      });\n}\n"
  },
  {
    "path": "src/content/getusermedia/resolution/js/test.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/*\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/getusermedia/resolution/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('getUserMedia resolutions', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  const buttonToResolution = {\n    'p180': 320,\n    'p360': 640,\n    'qvga': 320,\n    'vga': 640,\n    'hd': 1280,\n    'full-hd': 1920,\n    'televisionFourK': 3840,\n    /* TODO: unsupported by fake device? Or is fake device limited to physical device resolution?\n    'cinemaFourK': 4096,\n    'eightK': 8192,\n    */\n  };\n  Object.keys(buttonToResolution).forEach(buttonId => {\n    const resolution = buttonToResolution[buttonId];\n\n    it(`opens a camera with width ${resolution}px`, async () => {\n      await driver.findElement(webdriver.By.id(buttonId)).click();\n      await driver.wait(() => driver.executeScript(() =>\n        document.querySelector('video').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA)\n      );\n      const width = await driver.findElement(webdriver.By.css('video')).getAttribute('videoWidth');\n      expect(width >>> 0).toBe(resolution);\n    });\n  });\n});\n\n"
  },
  {
    "path": "src/content/getusermedia/source/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n   <meta http-equiv=\"refresh\"\n   content=\"0; url=//webrtc.github.io/samples/src/content/devices/input-output/\">\n   <title>Page move</title>\n</head>\n<body>\n   <p>The page has moved to:\n   <a href=\"//webrtc.github.io/samples/src/content/devices/input-output/\">this page</a></p>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/volume/css/main.css",
    "content": "div#meters > div {\n  margin: 0 0 1em 0;\n}\n\ndiv#meters div.label {\n  display: inline-block;\n  font-weight: 400;\n  margin: 0 0.5em 0 0;\n  width: 3.5em;\n}\n\ndiv#meters div.value {\n  display: inline-block;\n}\n\nmeter {\n  width: 50%;\n}\n\nmeter#clip {\n  color: #db4437;\n}\n\nmeter#slow {\ncolor: #f4b400;\n}\n\nmeter#instant {\ncolor: #0f9d58;\n}\n"
  },
  {
    "path": "src/content/getusermedia/volume/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Audio stream volume</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Audio stream volume</span>\n    </h1>\n\n    <p>Measure the volume of a local media stream using WebAudio.</p>\n\n    <div id=\"meters\">\n        <div id=\"instant\">\n            <div class=\"label\">Instant:</div>\n            <meter high=\"0.25\" max=\"1\" value=\"0\"></meter>\n            <div class=\"value\"></div>\n        </div>\n        <div id=\"slow\">\n            <div class=\"label\">Slow:</div>\n            <meter high=\"0.25\" max=\"1\" value=\"0\"></meter>\n            <div class=\"value\"></div>\n        </div>\n        <div id=\"clip\">\n            <div class=\"label\">Clip:</div>\n            <meter max=\"1\" value=\"0\"></meter>\n            <div class=\"value\"></div>\n        </div>\n    </div>\n\n    <div>\n        <button type=\"button\" id=\"startButton\">Start</button> \n        <button type=\"button\" id=\"stopButton\" disabled>Stop</button>\n    <div>\n\n    <p>The 'instant' volume changes approximately every 50ms; the 'slow' volume approximates the average volume over\n        about a second.</p>\n    <p>Note that you will not hear your own voice; use the <a href=\"../audio\">local audio rendering demo</a> for that.\n    </p>\n    <p>The <code>audioContext</code>, <code>stream</code> and <code>soundMeter</code> variables are in global scope, so\n        you can inspect them from the console.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/volume\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/soundmeter.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/getusermedia/volume/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n/* global AudioContext, SoundMeter */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst stopButton = document.getElementById('stopButton');\nstartButton.onclick = start;\nstopButton.onclick = stop;\n\nconst instantMeter = document.querySelector('#instant meter');\nconst slowMeter = document.querySelector('#slow meter');\nconst clipMeter = document.querySelector('#clip meter');\n\nconst instantValueDisplay = document.querySelector('#instant .value');\nconst slowValueDisplay = document.querySelector('#slow .value');\nconst clipValueDisplay = document.querySelector('#clip .value');\n\n// Put variables in global scope to make them available to the browser console.\nconst constraints = window.constraints = {\n  audio: true,\n  video: false\n};\n\nlet meterRefresh = null;\n\nfunction handleSuccess(stream) {\n  // Put variables in global scope to make them available to the\n  // browser console.\n  window.stream = stream;\n  const soundMeter = window.soundMeter = new SoundMeter(window.audioContext);\n  soundMeter.connectToSource(stream, function(e) {\n    if (e) {\n      alert(e);\n      return;\n    }\n    meterRefresh = setInterval(() => {\n      instantMeter.value = instantValueDisplay.innerText =\n        soundMeter.instant.toFixed(2);\n      slowMeter.value = slowValueDisplay.innerText =\n        soundMeter.slow.toFixed(2);\n      clipMeter.value = clipValueDisplay.innerText =\n        soundMeter.clip;\n    }, 200);\n  });\n}\n\nfunction handleError(error) {\n  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);\n}\n\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  stopButton.disabled = false;\n\n  try {\n    window.AudioContext = window.AudioContext || window.webkitAudioContext;\n    window.audioContext = new AudioContext();\n  } catch (e) {\n    alert('Web Audio API not supported.');\n  }\n\n  navigator.mediaDevices\n      .getUserMedia(constraints)\n      .then(handleSuccess)\n      .catch(handleError);\n}\n\nfunction stop() {\n  console.log('Stopping local stream');\n  startButton.disabled = false;\n  stopButton.disabled = true;\n\n  window.stream.getTracks().forEach(track => track.stop());\n  window.soundMeter.stop();\n  window.audioContext.close();\n  clearInterval(meterRefresh);\n  instantMeter.value = instantValueDisplay.innerText = '';\n  slowMeter.value = slowValueDisplay.innerText = '';\n  clipMeter.value = clipValueDisplay.innerText = '';\n}\n"
  },
  {
    "path": "src/content/getusermedia/volume/js/soundmeter.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n// Meter class that generates a number correlated to audio volume.\n// The meter class itself displays nothing, but it makes the\n// instantaneous and time-decaying volumes available for inspection.\n// It also reports on the fraction of samples that were at or near\n// the top of the measurement range.\nfunction SoundMeter(context) {\n  this.context = context;\n  this.instant = 0.0;\n  this.slow = 0.0;\n  this.clip = 0.0;\n  this.node = null;\n}\n\nSoundMeter.prototype.connectToSource = async function(stream, callback) {\n  console.log('SoundMeter connecting');\n  try {\n    await this.context.audioWorklet.addModule('js/volume-meter-processor.js');\n    this.mic = this.context.createMediaStreamSource(stream);\n    this.node = new AudioWorkletNode(this.context, 'volume-meter-processor');\n    this.node.port.onmessage = (event) => {\n      const {instant, clip} = event.data;\n      this.instant = instant;\n      this.clip = clip;\n      this.slow = 0.95 * this.slow + 0.05 * this.instant;\n    };\n    this.mic.connect(this.node);\n    if (typeof callback !== 'undefined') {\n      callback(null);\n    }\n  } catch (e) {\n    console.error(e);\n    if (typeof callback !== 'undefined') {\n      callback(e);\n    }\n  }\n};\n\nSoundMeter.prototype.stop = function() {\n  console.log('SoundMeter stopping');\n  this.mic.disconnect();\n  this.node.disconnect();\n};\n"
  },
  {
    "path": "src/content/getusermedia/volume/js/volume-meter-processor.js",
    "content": "/*\n *  Copyright (c) 2025 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n// This class is used to compute the volume of the input audio stream.\nclass VolumeMeterProcessor extends AudioWorkletProcessor {\n  constructor() {\n    super();\n    this._lastUpdate = Date.now();\n  }\n  process(inputs) {\n    // This example only supports mono channel.\n    const input = inputs[0][0];\n    if (!input) {\n      return true;\n    }\n    let sum = 0.0;\n    let clipcount = 0;\n    for (let i = 0; i < input.length; ++i) {\n      sum += input[i] * input[i];\n      if (Math.abs(input[i]) > 0.99) {\n        clipcount += 1;\n      }\n    }\n    const instant = Math.sqrt(sum / input.length);\n    this.port.postMessage({\n      instant: instant,\n      clip: clipcount / input.length,\n    });\n    return true;\n  }\n}\n\nregisterProcessor('volume-meter-processor', VolumeMeterProcessor);\n"
  },
  {
    "path": "src/content/insertable-streams/audio-processing/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Insertable Streams - Audio</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Audio processing with insertable streams</span>\n    </h1>\n\n    <p>This sample shows how to perform processing on an audio stream using the experimental\n        <a href=\"https://github.com/w3c/mediacapture-insertable-streams\">insertable streams</a> API.\n        It applies a low-pass filter in realtime to audio recorded from a microphone and plays it\n        back.\n    </p>\n    <audio id=\"audioOutput\" controls></audio>\n\n    <div>\n        <button type=\"button\" id=\"startButton\">Start</button>\n        <button type=\"button\" id=\"stopButton\" disabled>Stop</button>\n    <div>\n    <p class=\"warning\">Warning: if you're not using headphones, pressing Start will cause feedback.</p>\n    <div id=\"errorMsg\" class=\"warning\"></div>\n\n    <p>View the console to see logging. The <code>audio</code>, <code>processor</code>,\n        <code>generator</code>, <code>transformer</code>, <code>stream</code> and\n        <code>processedStream</code> variables are in global scope, so you can inspect them from the\n        console. You may also adjust the level of filtering by assigning to <code>cutoff</code>.</p>\n\n    <p>\n        <b>Note</b>: This sample is using an experimental API that has not yet been standardized. As\n        of 2021-09-29, this API is available in Chrome M94.\n    </p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/audio-processing\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/audio-processing/js/main.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator, AudioData */\nif (typeof MediaStreamTrackProcessor === 'undefined' ||\n    typeof MediaStreamTrackGenerator === 'undefined') {\n  alert(\n      'Your browser does not support the experimental MediaStreamTrack API ' +\n      'for Insertable Streams of Media. See the note at the bottom of the ' +\n      'page.');\n}\n\ntry {\n  new MediaStreamTrackGenerator('audio');\n  console.log('Audio insertable streams supported');\n} catch (e) {\n  alert(\n      'Your browser does not support insertable audio streams. See the note ' +\n        'at the bottom of the page.');\n}\n\nif (typeof AudioData === 'undefined') {\n  alert(\n      'Your browser does not support WebCodecs. See the note at the bottom ' +\n      'of the page.');\n}\n\n// Put variables in global scope to make them available to the browser console.\n\n// Audio element\nlet audio;\n\n// Buttons\nlet startButton;\nlet stopButton;\n\n// Transformation chain elements\nlet processor;\nlet generator;\n\n// Stream from getUserMedia\nlet stream;\n// Output from the transform\nlet processedStream;\n\n// Worker for processing\nlet worker;\n\n// Initialize on page load.\nasync function init() {\n  audio = document.getElementById('audioOutput');\n  startButton = document.getElementById('startButton');\n  stopButton = document.getElementById('stopButton');\n\n  startButton.onclick = start;\n  stopButton.onclick = stop;\n}\n\nconst constraints = window.constraints = {\n  audio: true,\n  video: false\n};\n\nasync function start() {\n  startButton.disabled = true;\n  try {\n    stream = await navigator.mediaDevices.getUserMedia(constraints);\n    const audioTracks = stream.getAudioTracks();\n    console.log('Using audio device: ' + audioTracks[0].label);\n    stream.oninactive = () => {\n      console.log('Stream ended');\n    };\n\n    processor = new MediaStreamTrackProcessor(audioTracks[0]);\n    generator = new MediaStreamTrackGenerator('audio');\n    const source = processor.readable;\n    const sink = generator.writable;\n    worker = new Worker('js/worker.js');\n    worker.postMessage({source: source, sink: sink}, [source, sink]);\n\n    processedStream = new MediaStream();\n    processedStream.addTrack(generator);\n    audio.srcObject = processedStream;\n    stopButton.disabled = false;\n    await audio.play();\n  } catch (error) {\n    const errorMessage =\n          'navigator.MediaDevices.getUserMedia error: ' + error.message + ' ' +\n          error.name;\n    document.getElementById('errorMsg').innerText = errorMessage;\n    console.log(errorMessage);\n  }\n}\n\nasync function stop() {\n  stopButton.disabled = true;\n  audio.pause();\n  audio.srcObject = null;\n  stream.getTracks().forEach(track => {\n    track.stop();\n  });\n  worker.postMessage({command: 'abort'});\n  startButton.disabled = false;\n}\n\nwindow.onload = init;\n"
  },
  {
    "path": "src/content/insertable-streams/audio-processing/js/worker.js",
    "content": "/*\n *  Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n// Adjust this value to increase/decrease the amount of filtering.\n// eslint-disable-next-line prefer-const\nlet cutoff = 100;\n\n// Returns a low-pass transform function for use with TransformStream.\nfunction lowPassFilter() {\n  const format = 'f32-planar';\n  let lastValuePerChannel = undefined;\n  return (data, controller) => {\n    const rc = 1.0 / (cutoff * 2 * Math.PI);\n    const dt = 1.0 / data.sampleRate;\n    const alpha = dt / (rc + dt);\n    const nChannels = data.numberOfChannels;\n    if (!lastValuePerChannel) {\n      console.log(`Audio stream has ${nChannels} channels.`);\n      lastValuePerChannel = Array(nChannels).fill(0);\n    }\n    const buffer = new Float32Array(data.numberOfFrames * nChannels);\n    for (let c = 0; c < nChannels; c++) {\n      const offset = data.numberOfFrames * c;\n      const samples = buffer.subarray(offset, offset + data.numberOfFrames);\n      data.copyTo(samples, {planeIndex: c, format});\n      let lastValue = lastValuePerChannel[c];\n\n      // Apply low-pass filter to samples.\n      for (let i = 0; i < samples.length; ++i) {\n        lastValue = lastValue + alpha * (samples[i] - lastValue);\n        samples[i] = lastValue;\n      }\n\n      lastValuePerChannel[c] = lastValue;\n    }\n    controller.enqueue(new AudioData({\n      format,\n      sampleRate: data.sampleRate,\n      numberOfFrames: data.numberOfFrames,\n      numberOfChannels: nChannels,\n      timestamp: data.timestamp,\n      data: buffer\n    }));\n  };\n}\n\nlet abortController;\n\nonmessage = async (event) => {\n  if (event.data.command == 'abort') {\n    abortController.abort();\n    abortController = null;\n  } else {\n    const source = event.data.source;\n    const sink = event.data.sink;\n    const transformer = new TransformStream({transform: lowPassFilter()});\n    abortController = new AbortController();\n    const signal = abortController.signal;\n    const promise = source.pipeThrough(transformer, {signal}).pipeTo(sink);\n    promise.catch((e) => {\n      if (signal.aborted) {\n        console.log('Shutting down streams after abort.');\n      } else {\n        console.error('Error from stream transform:', e);\n      }\n      source.cancel(e);\n      sink.abort(e);\n    });\n  }\n};\n"
  },
  {
    "path": "src/content/insertable-streams/endtoend-encryption/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\nbutton {\n  margin: 20px 10px 0 0;\n  width: 100px;\n}\n\ndiv#buttons {\n  margin: 0 0 20px 0;\n}\n\ndiv#status {\n  height: 2em;\n  margin: 1em 0 0 0;\n}\n\ninput#audio {\n  margin: 0 0.5em 0 0;\n  position: relative;\n  top: -1px;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n"
  },
  {
    "path": "src/content/insertable-streams/endtoend-encryption/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection end to end encryption</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection end to end encryption</span>\n    </h1>\n    <span id=\"banner\"></span>\n    <p>\n      <h2>Sender and receiver</h2>\n      <div id=\"videos\">\n        Sender and receiver<br>\n        <video id=\"video1\" playsinline autoplay muted></video>\n        <video id=\"video2\" playsinline autoplay></video>\n      </div>\n      <br>\n      Crypto key: <input type=\"text\" id=\"crypto-key\">\n      Encrypt first bytes: <input type=\"checkbox\" id=\"crypto-offset\">\n    </p>\n    <p>\n      <h2>Middlebox</h2>\n      <div id=\"monitor\">\n        <video id=\"video-monitor\" playsinline autoplay muted></video>\n        Switch audio to middlebox: <input type=\"checkbox\" id=\"mute-middlebox\">\n      </div>\n    </p>\n\n    <div id=\"buttons\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\" disabled>Call</button>\n        <button id=\"hangupButton\" disabled>Hang Up</button>\n    </div>\n    <div id=\"status\"></div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/endtoend-encryption\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/videopipe.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/endtoend-encryption/js/main.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global RTCRtpScriptTransform */\n/* global VideoPipe */\n\nconst video1 = document.querySelector('video#video1');\nconst video2 = document.querySelector('video#video2');\nconst videoMonitor = document.querySelector('#video-monitor');\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\n\nconst cryptoKey = document.querySelector('#crypto-key');\nconst cryptoOffsetBox = document.querySelector('#crypto-offset');\nconst banner = document.querySelector('#banner');\nconst muteMiddleBox = document.querySelector('#mute-middlebox');\n\nstartButton.onclick = start;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\ncryptoKey.addEventListener('change', setCryptoKey);\ncryptoOffsetBox.addEventListener('change', setCryptoKey);\nmuteMiddleBox.addEventListener('change', toggleMute);\n\nlet startToMiddle;\nlet startToEnd;\n\nlet localStream;\n// eslint-disable-next-line no-unused-vars\nlet remoteStream;\n\n// Preferring a certain codec is an expert option without GUI.\n// Use opus by default.\n// eslint-disable-next-line prefer-const\nlet preferredAudioCodecMimeType = 'audio/opus';\n// Use VP8 by default to limit depacketization issues.\n// eslint-disable-next-line prefer-const\nlet preferredVideoCodecMimeType = 'video/VP8';\n\nconst supportsSetCodecPreferences = window.RTCRtpTransceiver &&\n  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;\n\nlet hasEnoughAPIs = !!window.RTCRtpScriptTransform;\n\nif (!hasEnoughAPIs) {\n  const supportsInsertableStreams =\n      !!RTCRtpSender.prototype.createEncodedStreams;\n\n  let supportsTransferableStreams = false;\n  try {\n    const stream = new ReadableStream();\n    window.postMessage(stream, '*', [stream]);\n    supportsTransferableStreams = true;\n  } catch (e) {\n    console.error('Transferable streams are not supported.');\n  }\n  hasEnoughAPIs = supportsInsertableStreams && supportsTransferableStreams;\n}\n\nif (!hasEnoughAPIs) {\n  banner.innerText = 'Your browser does not support WebRTC Encoded Transforms. ' +\n  'This sample will not work.';\n  if (adapter.browserDetails.browser === 'chrome') {\n    banner.innerText += ' Try with Enable experimental Web Platform features enabled from chrome://flags.';\n  }\n  startButton.disabled = true;\n  cryptoKey.disabled = true;\n  cryptoOffsetBox.disabled = true;\n}\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  video1.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nfunction gotRemoteStream(stream) {\n  console.log('Received remote stream');\n  remoteStream = stream;\n  video2.srcObject = stream;\n}\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  const options = {audio: true, video: true};\n  navigator.mediaDevices\n      .getUserMedia(options)\n      .then(gotStream)\n      .catch(function(e) {\n        alert('getUserMedia() failed');\n        console.log('getUserMedia() error: ', e);\n      });\n}\n\n// We use a Worker to do the encryption and decryption.\n// See\n//   https://developer.mozilla.org/en-US/docs/Web/API/Worker\n// for basic concepts.\nconst worker = new Worker('./js/worker.js', {name: 'E2EE worker'});\nfunction setupSenderTransform(sender) {\n  if (window.RTCRtpScriptTransform) {\n    sender.transform = new RTCRtpScriptTransform(worker, {operation: 'encode'});\n    return;\n  }\n\n  const senderStreams = sender.createEncodedStreams();\n  // Instead of creating the transform stream here, we do a postMessage to the worker. The first\n  // argument is an object defined by us, the second is a list of variables that will be transferred to\n  // the worker. See\n  //   https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage\n  // If you want to do the operations on the main thread instead, comment out the code below.\n  /*\n  const transformStream = new TransformStream({\n    transform: encodeFunction,\n  });\n  senderStreams.readable\n      .pipeThrough(transformStream)\n      .pipeTo(senderStreams.writable);\n  */\n  const {readable, writable} = senderStreams;\n  worker.postMessage({\n    operation: 'encode',\n    readable,\n    writable,\n  }, [readable, writable]);\n}\n\nfunction setupReceiverTransform(receiver) {\n  if (window.RTCRtpScriptTransform) {\n    receiver.transform = new RTCRtpScriptTransform(worker, {operation: 'decode'});\n    return;\n  }\n\n  const receiverStreams = receiver.createEncodedStreams();\n  const {readable, writable} = receiverStreams;\n  worker.postMessage({\n    operation: 'decode',\n    readable,\n    writable,\n  }, [readable, writable]);\n}\n\nfunction maybeSetCodecPreferences(trackEvent) {\n  if (!supportsSetCodecPreferences) return;\n  if (trackEvent.track.kind === 'audio' && preferredAudioCodecMimeType ) {\n    const {codecs} = RTCRtpReceiver.getCapabilities('audio');\n    const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredAudioCodecMimeType);\n    const selectedCodec = codecs[selectedCodecIndex];\n    codecs.splice(selectedCodecIndex, 1);\n    codecs.unshift(selectedCodec);\n    trackEvent.transceiver.setCodecPreferences(codecs);\n  } else if (trackEvent.track.kind === 'video' && preferredVideoCodecMimeType) {\n    const {codecs} = RTCRtpReceiver.getCapabilities('video');\n    const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredVideoCodecMimeType);\n    const selectedCodec = codecs[selectedCodecIndex];\n    codecs.splice(selectedCodecIndex, 1);\n    codecs.unshift(selectedCodec);\n    trackEvent.transceiver.setCodecPreferences(codecs);\n  }\n}\n\nfunction call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  // The real use case is where the middle box relays the\n  // packets and listens in, but since we don't have\n  // access to raw packets, we just send the same video\n  // to both places.\n  startToMiddle = new VideoPipe(localStream, true, false, e => {\n    // Do not setup the receiver transform.\n    maybeSetCodecPreferences(e);\n    videoMonitor.srcObject = e.streams[0];\n  });\n  startToMiddle.pc1.getSenders().forEach(setupSenderTransform);\n  startToMiddle.negotiate();\n\n  startToEnd = new VideoPipe(localStream, true, true, e => {\n    setupReceiverTransform(e.receiver);\n    maybeSetCodecPreferences(e);\n    gotRemoteStream(e.streams[0]);\n  });\n  startToEnd.pc1.getSenders().forEach(setupSenderTransform);\n  startToEnd.negotiate();\n\n  console.log('Video pipes created');\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  startToMiddle.close();\n  startToEnd.close();\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n\nfunction setCryptoKey(event) {\n  console.log('Setting crypto key to ' + cryptoKey.value);\n  const currentCryptoKey = cryptoKey.value;\n  const useCryptoOffset = !cryptoOffsetBox.checked;\n  if (currentCryptoKey) {\n    banner.innerText = 'Encryption is ON';\n  } else {\n    banner.innerText = 'Encryption is OFF';\n  }\n  worker.postMessage({\n    operation: 'setCryptoKey',\n    currentCryptoKey,\n    useCryptoOffset,\n  });\n}\n\nfunction toggleMute(event) {\n  video2.muted = muteMiddleBox.checked;\n  videoMonitor.muted = !muteMiddleBox.checked;\n}\n"
  },
  {
    "path": "src/content/insertable-streams/endtoend-encryption/js/test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/insertable-streams/endtoend-encryption/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('insertable streams e2ee', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection and hangs up', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return startToEnd && startToEnd.pc1 && startToEnd.pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return startToEnd && startToEnd.pc2 && startToEnd.pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('video2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n\n    await driver.findElement(webdriver.By.id('hangupButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return startToEnd && startToEnd.pc1 && startToEnd.pc1.connectionState === 'closed'; // eslint-disable-line no-undef\n    }));\n  });\n\n  it('establisheѕ a encrypted connection with a key set prior to connecting', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    await driver.findElement(webdriver.By.id('crypto-key'))\n        .sendKeys('secret\\n');\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('banner').innerText === 'Encryption is ON';\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('video2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/insertable-streams/endtoend-encryption/js/videopipe.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n//\n// A \"videopipe\" abstraction on top of WebRTC.\n//\n// The usage of this abstraction:\n// var pipe = new VideoPipe(mediastream, handlerFunction);\n// handlerFunction = function(MediaStreamTrackEvent) {\n//   do_something\n// }\n// pipe.close();\n//\n// The VideoPipe will set up 2 PeerConnections, connect them to each\n// other, and call HandlerFunction when the stream's track is available\n// in the second PeerConnection.\n//\n'use strict';\n\nfunction VideoPipe(stream, forceSend, forceReceive, handler) {\n  this.pc1 = new RTCPeerConnection();\n  this.pc2 = new RTCPeerConnection();\n  this.pc2.ontrack = handler;\n  stream.getTracks().forEach((track) => this.pc1.addTrack(track, stream));\n}\n\nVideoPipe.prototype.negotiate = async function() {\n  this.pc1.onicecandidate = e => this.pc2.addIceCandidate(e.candidate);\n  this.pc2.onicecandidate = e => this.pc1.addIceCandidate(e.candidate);\n\n  await this.pc1.setLocalDescription();\n  await this.pc2.setRemoteDescription(this.pc1.localDescription);\n  await this.pc2.setLocalDescription();\n  await this.pc1.setRemoteDescription(this.pc2.localDescription);\n};\n\nVideoPipe.prototype.close = function() {\n  this.pc1.close();\n  this.pc2.close();\n};\n"
  },
  {
    "path": "src/content/insertable-streams/endtoend-encryption/js/worker.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n/*\n * This is a worker doing the encode/decode transformations to add end-to-end\n * encryption to a WebRTC PeerConnection using the Insertable Streams API.\n */\n\n'use strict';\nlet currentCryptoKey;\nlet useCryptoOffset = true;\nlet currentKeyIdentifier = 0;\n\n// If using crypto offset (controlled by a checkbox):\n// Do not encrypt the first couple of bytes of the payload. This allows\n// a middle to determine video keyframes or the opus mode being used.\n// For VP8 this is the content described in\n//   https://tools.ietf.org/html/rfc6386#section-9.1\n// which is 10 bytes for key frames and 3 bytes for delta frames.\n// For opus (where encodedFrame.type is not set) this is the TOC byte from\n//   https://tools.ietf.org/html/rfc6716#section-3.1\n// TODO: make this work for other codecs.\n//\n// It makes the (encrypted) video and audio much more fun to watch and listen to\n// as the decoder does not immediately throw a fatal error.\nconst frameTypeToCryptoOffset = {\n  key: 10,\n  delta: 3,\n  undefined: 1,\n};\n\nfunction dump(encodedFrame, direction, max = 16) {\n  const data = new Uint8Array(encodedFrame.data);\n  let bytes = '';\n  for (let j = 0; j < data.length && j < max; j++) {\n    bytes += (data[j] < 16 ? '0' : '') + data[j].toString(16) + ' ';\n  }\n  const metadata = encodedFrame.getMetadata();\n  console.log(performance.now().toFixed(2), direction, bytes.trim(),\n      'len=' + encodedFrame.data.byteLength,\n      'type=' + (encodedFrame.type || 'audio'),\n      'ts=' + (metadata.rtpTimestamp || encodedFrame.timestamp),\n      'ssrc=' + metadata.synchronizationSource,\n      'pt=' + (metadata.payloadType || '(unknown)'),\n      'mimeType=' + (metadata.mimeType || '(unknown)'),\n  );\n}\n\nlet scount = 0;\nfunction encodeFunction(encodedFrame, controller) {\n  if (scount++ < 30) { // dump the first 30 packets.\n    dump(encodedFrame, 'send');\n  }\n  if (currentCryptoKey) {\n    const view = new DataView(encodedFrame.data);\n    // Any length that is needed can be used for the new buffer.\n    const newData = new ArrayBuffer(encodedFrame.data.byteLength + 5);\n    const newView = new DataView(newData);\n\n    const cryptoOffset = useCryptoOffset? frameTypeToCryptoOffset[encodedFrame.type] : 0;\n    for (let i = 0; i < cryptoOffset && i < encodedFrame.data.byteLength; ++i) {\n      newView.setInt8(i, view.getInt8(i));\n    }\n    // This is a bitwise xor of the key with the payload. This is not strong encryption, just a demo.\n    for (let i = cryptoOffset; i < encodedFrame.data.byteLength; ++i) {\n      const keyByte = currentCryptoKey.charCodeAt(i % currentCryptoKey.length);\n      newView.setInt8(i, view.getInt8(i) ^ keyByte);\n    }\n    // Append keyIdentifier.\n    newView.setUint8(encodedFrame.data.byteLength, currentKeyIdentifier % 0xff);\n    // Append checksum\n    newView.setUint32(encodedFrame.data.byteLength + 1, 0xDEADBEEF);\n\n    encodedFrame.data = newData;\n  }\n  controller.enqueue(encodedFrame);\n}\n\nlet rcount = 0;\nfunction decodeFunction(encodedFrame, controller) {\n  if (rcount++ < 30) { // dump the first 30 packets\n    dump(encodedFrame, 'recv');\n  }\n  const view = new DataView(encodedFrame.data);\n  const checksum = encodedFrame.data.byteLength > 4 ? view.getUint32(encodedFrame.data.byteLength - 4) : false;\n  if (currentCryptoKey) {\n    if (checksum !== 0xDEADBEEF) {\n      console.log('Corrupted frame received, checksum ' +\n                  checksum.toString(16));\n      return; // This can happen when the key is set and there is an unencrypted frame in-flight.\n    }\n    const keyIdentifier = view.getUint8(encodedFrame.data.byteLength - 5);\n    if (keyIdentifier !== currentKeyIdentifier) {\n      console.log(`Key identifier mismatch, got ${keyIdentifier} expected ${currentKeyIdentifier}.`);\n      return;\n    }\n\n    const newData = new ArrayBuffer(encodedFrame.data.byteLength - 5);\n    const newView = new DataView(newData);\n    const cryptoOffset = useCryptoOffset? frameTypeToCryptoOffset[encodedFrame.type] : 0;\n\n    for (let i = 0; i < cryptoOffset; ++i) {\n      newView.setInt8(i, view.getInt8(i));\n    }\n    for (let i = cryptoOffset; i < encodedFrame.data.byteLength - 5; ++i) {\n      const keyByte = currentCryptoKey.charCodeAt(i % currentCryptoKey.length);\n      newView.setInt8(i, view.getInt8(i) ^ keyByte);\n    }\n    encodedFrame.data = newData;\n  } else if (checksum === 0xDEADBEEF) {\n    return; // encrypted in-flight frame but we already forgot about the key.\n  }\n  controller.enqueue(encodedFrame);\n}\n\nfunction handleTransform(operation, readable, writable) {\n  if (operation === 'encode') {\n    const transformStream = new TransformStream({\n      transform: encodeFunction,\n    });\n    readable\n        .pipeThrough(transformStream)\n        .pipeTo(writable);\n  } else if (operation === 'decode') {\n    const transformStream = new TransformStream({\n      transform: decodeFunction,\n    });\n    readable\n        .pipeThrough(transformStream)\n        .pipeTo(writable);\n  }\n}\n\n// Handler for messages, including transferable streams.\nonmessage = (event) => {\n  if (event.data.operation === 'encode' || event.data.operation === 'decode') {\n    return handleTransform(event.data.operation, event.data.readable, event.data.writable);\n  }\n  if (event.data.operation === 'setCryptoKey') {\n    if (event.data.currentCryptoKey !== currentCryptoKey) {\n      currentKeyIdentifier++;\n    }\n    currentCryptoKey = event.data.currentCryptoKey;\n    useCryptoOffset = event.data.useCryptoOffset;\n  }\n};\n\n// Handler for RTCRtpScriptTransforms.\nif (self.RTCTransformEvent) {\n  self.onrtctransform = (event) => {\n    const transformer = event.transformer;\n    handleTransform(transformer.options.operation, transformer.readable, transformer.writable);\n  };\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n   <meta http-equiv=\"refresh\"\n   content=\"0; url=//webrtc.github.io/samples/src/content/insertable-streams/video-processing/\">\n   <title>Page move</title>\n</head>\n<body>\n   <p>The page has moved to:\n   <a href=\"//webrtc.github.io/samples/src/content/insertable-streams/video-processing/\">this page</a></p>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/video-analyzer/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-analyzer/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Insertable Streams Video Analyzer</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Insertable Streams Video Analyzer</span></h1>\n\n    <h3><span id=\"banner\"></span></h3>\n    <p>This sample shows how Insertable Streams can be used to analyze\n      the encoded form of a video track.\n    </p>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div class=\"box\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n        <br><br>\n        <button id=\"size-small\" disabled>Small</button>\n        <button id=\"size-vga\" disabled>VGA</button>\n        <button id=\"size-hd\" disabled>HD</button>\n    </div>\n\n    <p>View the console to see logging.\n    </p>\n    <div class=\"analyzer\">\n      Video size: <span id=\"video-size\"></span><br>\n      Keyframe count: <span id=\"keyframe-count\"></span><br>\n      Interframe count: <span id=\"interframe-count\"></span><br>\n      Last keyframe size: <span id=\"keyframe-size\"></span><br>\n      Last interframe size: <span id=\"interframe-size\"></span><br>\n      Duplicate count: <span id=\"duplicate-count\"></span><br>\n    </div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/video-analyzer\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/video-analyzer/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.addEventListener('click', start);\ncallButton.addEventListener('click', call);\nhangupButton.addEventListener('click', hangup);\n\nconst smallButton = document.getElementById('size-small');\nsmallButton.addEventListener('click', () => {\n  localStream.getVideoTracks()[0].applyConstraints({width: {exact: 180}});\n});\nconst vgaButton = document.getElementById('size-vga');\nvgaButton.addEventListener('click', () => {\n  localStream.getVideoTracks()[0].applyConstraints({width: {exact: 640}});\n});\nconst hdButton = document.getElementById('size-hd');\nhdButton.addEventListener('click', () => {\n  localStream.getVideoTracks()[0].applyConstraints({width: {exact: 1024}});\n});\n\n\nconst banner = document.querySelector('#banner');\n\n\nconst supportsInsertableStreams =\n      !!RTCRtpSender.prototype.createEncodedStreams;\n\nif (!supportsInsertableStreams) {\n  banner.innerText = 'Your browser does not support Insertable Streams. ' +\n  'This sample will not work.';\n  startButton.disabled = true;\n}\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');\n    startTime = null;\n  }\n});\n\nlet localStream;\nlet pc1;\nlet pc2;\nconst offerOptions = {\n  offerToReceiveAudio: 0,\n  offerToReceiveVideo: 1\n};\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia({video: true});\n    console.log('Received local stream');\n    localVideo.srcObject = stream;\n    localStream = stream;\n    callButton.disabled = false;\n    smallButton.disabled = false;\n    vgaButton.disabled = false;\n    hdButton.disabled = false;\n  } catch (e) {\n    alert(`getUserMedia() error: ${e.name}`);\n  }\n}\n\nasync function call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = localStream.getVideoTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  pc1 = new RTCPeerConnection();\n  console.log('Created local peer connection object pc1');\n  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));\n  pc2 = new RTCPeerConnection();\n  console.log('Created remote peer connection object pc2');\n  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));\n  pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));\n  pc2.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2, e));\n  pc2.addEventListener('track', gotRemoteTrack);\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Added local stream to pc1');\n\n  try {\n    console.log('pc1 createOffer start');\n    const offer = await pc1.createOffer(offerOptions);\n    await onCreateOfferSuccess(offer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nasync function onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  try {\n    await pc1.setLocalDescription(desc);\n    onSetLocalSuccess(pc1);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 setRemoteDescription start');\n  try {\n    await pc2.setRemoteDescription({type: 'offer', sdp: desc.sdp.replace('red/90000', 'green/90000')});\n    onSetRemoteSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 createAnswer start');\n  try {\n    const answer = await pc2.createAnswer();\n    await onCreateAnswerSuccess(answer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteTrack(e) {\n  console.log('pc2 received remote stream');\n  const frameStreams = e.receiver.createEncodedStreams();\n  frameStreams.readable.pipeThrough(new TransformStream({\n    transform: videoAnalyzer\n  }))\n      .pipeTo(frameStreams.writable);\n  remoteVideo.srcObject = e.streams[0];\n}\n\nasync function onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  try {\n    await pc2.setLocalDescription(desc);\n    onSetLocalSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n  console.log('pc1 setRemoteDescription start');\n  try {\n    await pc1.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc1);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n}\n\nasync function onIceCandidate(pc, event) {\n  try {\n    await (getOtherPc(pc).addIceCandidate(event.candidate));\n    onAddIceCandidateSuccess(pc);\n  } catch (e) {\n    onAddIceCandidateError(pc, e);\n  }\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n  }\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n\nconst keyFrameCountDisplay = document.querySelector('#keyframe-count');\nconst keyFrameSizeDisplay = document.querySelector('#keyframe-size');\nconst interFrameCountDisplay = document.querySelector('#interframe-count');\nconst interFrameSizeDisplay = document.querySelector('#interframe-size');\nconst videoSizeDisplay = document.querySelector('#video-size');\nconst duplicateCountDisplay = document.querySelector('#duplicate-count');\nlet keyFrameCount = 0;\nlet interFrameCount = 0;\nlet keyFrameLastSize = 0;\nlet interFrameLastSize = 0;\nlet duplicateCount = 0;\nlet prevFrameType;\nlet prevFrameTimestamp;\nlet prevFrameSynchronizationSource;\n\nfunction videoAnalyzer(encodedFrame, controller) {\n  const view = new DataView(encodedFrame.data);\n  // We assume that the video is VP8.\n  // TODO: Check the codec to see that it is.\n  // The lowest value bit in the first byte is the keyframe indicator.\n  // https://tools.ietf.org/html/rfc6386#section-9.1\n  const keyframeBit = view.getUint8(0) & 0x01;\n  // console.log(view.getUint8(0).toString(16));\n  if (keyframeBit === 0) {\n    keyFrameCount++;\n    keyFrameLastSize = encodedFrame.data.byteLength;\n  } else {\n    interFrameCount++;\n    interFrameLastSize = encodedFrame.data.byteLength;\n  }\n  if (encodedFrame.type === prevFrameType &&\n      encodedFrame.timestamp === prevFrameTimestamp &&\n      encodedFrame.synchronizationSource === prevFrameSynchronizationSource) {\n    duplicateCount++;\n  }\n  prevFrameType = encodedFrame.type;\n  prevFrameTimestamp = encodedFrame.timestamp;\n  prevFrameSynchronizationSource = encodedFrame.synchronizationSource;\n  controller.enqueue(encodedFrame);\n}\n\n// Update the display of the counters once a second.\nsetInterval(() => {\n  keyFrameCountDisplay.innerText = keyFrameCount;\n  keyFrameSizeDisplay.innerText = keyFrameLastSize;\n  interFrameCountDisplay.innerText = interFrameCount;\n  interFrameSizeDisplay.innerText = interFrameLastSize;\n  duplicateCountDisplay.innerText = duplicateCount;\n}, 500);\n\nremoteVideo.addEventListener('resize', () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  videoSizeDisplay.innerText = `${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`;\n});\n"
  },
  {
    "path": "src/content/insertable-streams/video-crop/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\nbutton {\n  margin: 20px 10px 0 0;\n  width: 100px;\n}\n\ndiv#buttons {\n  margin: 0 0 20px 0;\n}\n\ndiv#status {\n  height: 2em;\n  margin: 1em 0 0 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-crop/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Insertable Streams - Crop in a worker</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Breakout Box crop</span></h1>\n\n    <p>This sample shows how to perform cropping on a video stream using the experimental\n        <a href=\"https://github.com/w3c/mediacapture-transform\">mediacapture-transform</a> API\n        in a Worker.\n    </p>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"croppedVideo\" playsinline autoplay muted></video>\n\n    <div class=\"box\">\n        <button id=\"startButton\">Start</button>\n    </div>\n\n    <p>\n        <b>Note</b>: This sample is using an experimental API that has not yet been standardized. As\n        of 2022-11-21, this API is available in the latest version of Chrome based browsers.\n    </p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/video-crop\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/video-crop/js/main.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */\nif (typeof MediaStreamTrackProcessor === 'undefined' ||\n    typeof MediaStreamTrackGenerator === 'undefined') {\n  alert(\n      'Your browser does not support the experimental MediaStreamTrack API ' +\n      'for Insertable Streams of Media. See the note at the bottom of the ' +\n      'page.');\n}\n\nconst startButton = document.getElementById('startButton');\nconst localVideo = document.getElementById('localVideo');\nconst croppedVideo = document.getElementById('croppedVideo');\n\nconst worker = new Worker('./js/worker.js', {name: 'Crop worker'});\nstartButton.addEventListener('click', async () => {\n  const stream = await navigator.mediaDevices.getUserMedia({video: {width: 1280, height: 720}});\n  localVideo.srcObject = stream;\n\n  const [track] = stream.getTracks();\n  const processor = new MediaStreamTrackProcessor({track});\n  const {readable} = processor;\n\n  const generator = new MediaStreamTrackGenerator({kind: 'video'});\n  const {writable} = generator;\n  croppedVideo.srcObject = new MediaStream([generator]);\n\n  worker.postMessage({\n    operation: 'crop',\n    readable,\n    writable,\n  }, [readable, writable]);\n});\n"
  },
  {
    "path": "src/content/insertable-streams/video-crop/js/worker.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nfunction transform(frame, controller) {\n  // Cropping from an existing video frame is supported by the API in Chrome 94+.\n  const newFrame = new VideoFrame(frame, {\n    visibleRect: {\n      x: 320,\n      width: 640,\n      y: 180,\n      height: 360,\n    }\n  });\n  controller.enqueue(newFrame);\n  frame.close();\n}\n\nonmessage = async (event) => {\n  const {operation} = event.data;\n  if (operation === 'crop') {\n    const {readable, writable} = event.data;\n    readable\n        .pipeThrough(new TransformStream({transform}))\n        .pipeTo(writable);\n  } else {\n    console.error('Unknown operation', operation);\n  }\n};\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/css/main.css",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n.video {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  vertical-align: top;\n}\n\n.sourceVideo {\n  margin: 0 20px 20px 0;\n}\n\n.sinkVideo {\n  margin: 0 0 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  .video {\n    height: 90px;\n    width: calc(50% - 7px);\n  }\n  .sourceVideo {\n    margin: 0 10px 20px 0;\n  }\n  .sinkVideo {\n    margin: 0 0 10px 0;\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Insertable Streams - Video</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Video processing with insertable streams</span></h1>\n\n    <p>This sample shows how to perform processing on a video stream using the experimental\n        <a href=\"https://github.com/w3c/mediacapture-insertable-streams\">insertable streams</a> API.\n        There are options for the source of the input stream, the destination of the output stream,\n        and the API used to transform the stream. There is also the option to duplicate the source\n        stream to a video element on the page, which may affect the source FPS.\n    </p>\n\n    <span id=\"outputVideoContainer\"></span>\n\n    <div class=\"box\">\n        <span>Source:</span>\n        <select id=\"sourceSelector\" disabled>\n            <option selected value=\"\">(stopped)</option>\n            <option value=\"camera\">Camera</option>\n            <option value=\"video\">Video</option>\n            <option value=\"canvas\">Canvas</option>\n            <option value=\"pc\">Peer connection (from camera)</option>\n        </select>\n        <span>Add to page: <input type=\"checkbox\" id=\"sourceVisible\" disabled></span>\n    </div>\n    <div class=\"box\">\n        <span>Transform:</span>\n        <select id=\"transformSelector\" disabled>\n            <option selected value=\"webgl\">WebGL</option>\n            <option value=\"canvas2d\">Canvas2D</option>\n            <option value=\"noop\">Do nothing</option>\n            <option value=\"drop\">Drop frames at random</option>\n            <option value=\"delay\">Delay all frames by 100ms</option>\n            <option value=\"webcodec\">Run frames through WebCodec</option>\n        </select>\n    </div>\n    <div class=\"box\">\n        <span>Destination:</span>\n        <select id=\"sinkSelector\" disabled>\n            <option selected value=\"video\">Video</option>\n            <option value=\"pc\">Peer connection</option>\n        </select>\n    </div>\n\n    <p>View the console to see logging.</p>\n\n    <p>\n        <b>Note</b>: This sample is using an experimental API that has not yet been standardized.\n        This API is available in Chrome 94 or later.\n    </p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/video-processing\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/camera-source.js\" async></script>\n<script src=\"js/canvas-source.js\" async></script>\n<script src=\"js/canvas-transform.js\" async></script>\n<script src=\"js/peer-connection-pipe.js\" async></script>\n<script src=\"js/peer-connection-sink.js\" async></script>\n<script src=\"js/peer-connection-source.js\" async></script>\n<script src=\"js/pipeline.js\" async></script>\n<script src=\"js/simple-transforms.js\" async></script>\n<script src=\"js/video-mirror-helper.js\" async></script>\n<script src=\"js/video-sink.js\" async></script>\n<script src=\"js/video-source.js\" async></script>\n<script src=\"js/webcodec-transform.js\" async></script>\n<script src=\"js/webgl-transform.js\" async></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/camera-source.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global VideoMirrorHelper */ // defined in video-mirror-helper.js\n\n/**\n * Opens the device's camera with getUserMedia.\n * @implements {MediaStreamSource} in pipeline.js\n */\nclass CameraSource { // eslint-disable-line no-unused-vars\n  constructor() {\n    /**\n     * @private @const {!VideoMirrorHelper} manages displaying the video stream\n     *     in the page\n     */\n    this.videoMirrorHelper_ = new VideoMirrorHelper();\n    /** @private {?MediaStream} camera stream, initialized in getMediaStream */\n    this.stream_ = null;\n    /** @private {string} */\n    this.debugPath_ = '<unknown>';\n  }\n  /** @override */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n    this.videoMirrorHelper_.setDebugPath(`${path}.videoMirrorHelper_`);\n  }\n  /** @override */\n  setVisibility(visible) {\n    this.videoMirrorHelper_.setVisibility(visible);\n  }\n  /** @override */\n  async getMediaStream() {\n    if (this.stream_) return this.stream_;\n    console.log('[CameraSource] Requesting camera.');\n    this.stream_ =\n        await navigator.mediaDevices.getUserMedia({audio: false, video: true});\n    console.log(\n        '[CameraSource] Received camera stream.',\n        `${this.debugPath_}.stream_ =`, this.stream_);\n    this.videoMirrorHelper_.setStream(this.stream_);\n    return this.stream_;\n  }\n  /** @override */\n  destroy() {\n    console.log('[CameraSource] Stopping camera');\n    this.videoMirrorHelper_.destroy();\n    if (this.stream_) {\n      this.stream_.getTracks().forEach(t => t.stop());\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/canvas-source.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst TEXT_SOURCE =\n    'https://raw.githubusercontent.com/w3c/mediacapture-insertable-streams/main/explainer.md';\nconst CANVAS_ASPECT_RATIO = 16 / 9;\n\n/**\n * @param {number} x\n * @return {number} x rounded to the nearest even integer\n */\nfunction roundToEven(x) {\n  return 2 * Math.round(x / 2);\n}\n\n/**\n * Draws text on a Canvas.\n * @implements {MediaStreamSource} in pipeline.js\n */\nclass CanvasSource { // eslint-disable-line no-unused-vars\n  constructor() {\n    /** @private {boolean} */\n    this.visibility_ = false;\n    /**\n     * @private {?HTMLCanvasElement} canvas element providing the MediaStream.\n     */\n    this.canvas_ = null;\n    /**\n     * @private {?CanvasRenderingContext2D} the 2D context used to draw the\n     *     animation.\n     */\n    this.ctx_ = null;\n    /**\n     * @private {?MediaStream} the MediaStream from captureStream.\n     */\n    this.stream_ = null;\n    /**\n     * @private {?CanvasCaptureMediaStreamTrack} the capture track from\n     *     canvas_, obtained from stream_. We manually request new animation\n     *     frames on this track.\n     */\n    this.captureTrack_ = null;\n    /** @private {number} requestAnimationFrame handle */\n    this.requestAnimationFrameHandle_ = 0;\n    /** @private {!Array<string>} text to render */\n    this.text_ = ['WebRTC samples'];\n    /** @private {string} */\n    this.debugPath_ = '<unknown>';\n    fetch(TEXT_SOURCE)\n        .then(response => {\n          if (response.ok) {\n            return response.text();\n          }\n          throw new Error(`Request completed with status ${response.status}.`);\n        })\n        .then(text => {\n          this.text_ = text.trim().split('\\n');\n        })\n        .catch((e) => {\n          console.log(`[CanvasSource] The request to retrieve ${\n            TEXT_SOURCE} encountered an error: ${e}.`);\n        });\n  }\n  /** @override */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n  }\n  /** @override */\n  setVisibility(visible) {\n    this.visibility_ = visible;\n    if (this.canvas_) {\n      this.updateCanvasVisibility();\n    }\n  }\n  /** @private */\n  updateCanvasVisibility() {\n    if (this.canvas_.parentNode && !this.visibility_) {\n      this.canvas_.parentNode.removeChild(this.canvas_);\n    } else if (!this.canvas_.parentNode && this.visibility_) {\n      console.log('[CanvasSource] Adding source canvas to page.');\n      const outputVideoContainer =\n          document.getElementById('outputVideoContainer');\n      outputVideoContainer.parentNode.insertBefore(\n          this.canvas_, outputVideoContainer);\n    }\n  }\n  /** @private */\n  requestAnimationFrame() {\n    this.requestAnimationFrameHandle_ =\n        requestAnimationFrame(now => this.animate(now));\n  }\n  /**\n   * @private\n   * @param {number} now current animation timestamp\n   */\n  animate(now) {\n    this.requestAnimationFrame();\n    const ctx = this.ctx_;\n    if (!this.canvas_ || !ctx || !this.captureTrack_) {\n      return;\n    }\n\n    // Resize canvas based on displayed size; or if not visible, based on the\n    // output video size.\n    // VideoFrame prefers to have dimensions that are even numbers.\n    if (this.visibility_) {\n      this.canvas_.width = roundToEven(this.canvas_.clientWidth);\n    } else {\n      const outputVideoContainer =\n          document.getElementById('outputVideoContainer');\n      const outputVideo = outputVideoContainer.firstElementChild;\n      if (outputVideo) {\n        this.canvas_.width = roundToEven(outputVideo.clientWidth);\n      }\n    }\n    this.canvas_.height = roundToEven(this.canvas_.width / CANVAS_ASPECT_RATIO);\n\n    ctx.fillStyle = '#fff';\n    ctx.fillRect(0, 0, this.canvas_.width, this.canvas_.height);\n\n    const linesShown = 20;\n    const millisecondsPerLine = 1000;\n    const linesIncludingExtraBlank = this.text_.length + linesShown;\n    const totalAnimationLength = linesIncludingExtraBlank * millisecondsPerLine;\n    const currentFrame = now % totalAnimationLength;\n    const firstLineIdx = Math.floor(\n        linesIncludingExtraBlank * (currentFrame / totalAnimationLength) -\n        linesShown);\n    const lineFraction = (now % millisecondsPerLine) / millisecondsPerLine;\n\n    const border = 20;\n    const fontSize = (this.canvas_.height - 2 * border) / (linesShown + 1);\n    ctx.font = `${fontSize}px sansserif`;\n\n    const textWidth = this.canvas_.width - 2 * border;\n\n    // first line\n    if (firstLineIdx >= 0) {\n      const fade = Math.floor(256 * lineFraction);\n      ctx.fillStyle = `rgb(${fade},${fade},${fade})`;\n      const position = (2 - lineFraction) * fontSize;\n      ctx.fillText(this.text_[firstLineIdx], border, position, textWidth);\n    }\n\n    // middle lines\n    for (let line = 2; line <= linesShown - 1; line++) {\n      const lineIdx = firstLineIdx + line - 1;\n      if (lineIdx >= 0 && lineIdx < this.text_.length) {\n        ctx.fillStyle = 'black';\n        const position = (line + 1 - lineFraction) * fontSize;\n        ctx.fillText(this.text_[lineIdx], border, position, textWidth);\n      }\n    }\n\n    // last line\n    const lastLineIdx = firstLineIdx + linesShown - 1;\n    if (lastLineIdx >= 0 && lastLineIdx < this.text_.length) {\n      const fade = Math.floor(256 * (1 - lineFraction));\n      ctx.fillStyle = `rgb(${fade},${fade},${fade})`;\n      const position = (linesShown + 1 - lineFraction) * fontSize;\n      ctx.fillText(this.text_[lastLineIdx], border, position, textWidth);\n    }\n\n    this.captureTrack_.requestFrame();\n  }\n  /** @override */\n  async getMediaStream() {\n    if (this.stream_) return this.stream_;\n\n    console.log('[CanvasSource] Initializing 2D context for source animation.');\n    this.canvas_ =\n      /** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));\n    this.canvas_.classList.add('video', 'sourceVideo');\n    // Generally video frames do not have an alpha channel. Even if the browser\n    // supports it, there may be a performance cost, so we disable alpha.\n    this.ctx_ = /** @type {?CanvasRenderingContext2D} */ (\n      this.canvas_.getContext('2d', {alpha: false}));\n    if (!this.ctx_) {\n      throw new Error('Unable to create CanvasRenderingContext2D');\n    }\n    this.updateCanvasVisibility();\n    this.stream_ = this.canvas_.captureStream(0);\n    this.captureTrack_ = /** @type {!CanvasCaptureMediaStreamTrack} */ (\n      this.stream_.getTracks()[0]);\n    this.requestAnimationFrame();\n    console.log(\n        '[CanvasSource] Initialized canvas, context, and capture stream.',\n        `${this.debugPath_}.canvas_ =`, this.canvas_,\n        `${this.debugPath_}.ctx_ =`, this.ctx_, `${this.debugPath_}.stream_ =`,\n        this.stream_, `${this.debugPath_}.captureTrack_ =`, this.captureTrack_);\n\n    return this.stream_;\n  }\n  /** @override */\n  destroy() {\n    console.log('[CanvasSource] Stopping source animation');\n    if (this.requestAnimationFrameHandle_) {\n      cancelAnimationFrame(this.requestAnimationFrameHandle_);\n    }\n    if (this.canvas_) {\n      if (this.canvas_.parentNode) {\n        this.canvas_.parentNode.removeChild(this.canvas_);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/canvas-transform.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Applies a picture-frame effect using CanvasRenderingContext2D.\n * @implements {FrameTransform} in pipeline.js\n */\nclass CanvasTransform { // eslint-disable-line no-unused-vars\n  constructor() {\n    /**\n     * @private {?OffscreenCanvas} canvas used to create the 2D context.\n     *     Initialized in init.\n     */\n    this.canvas_ = null;\n    /**\n     * @private {?CanvasRenderingContext2D} the 2D context used to draw the\n     *     effect. Initialized in init.\n     */\n    this.ctx_ = null;\n    /** @private {string} */\n    this.debugPath_ = 'debug.pipeline.frameTransform_';\n  }\n  /** @override */\n  async init() {\n    console.log('[CanvasTransform] Initializing 2D context for transform');\n    this.canvas_ = new OffscreenCanvas(1, 1);\n    this.ctx_ = /** @type {?CanvasRenderingContext2D} */ (\n      this.canvas_.getContext('2d', {alpha: false, desynchronized: true}));\n    if (!this.ctx_) {\n      throw new Error('Unable to create CanvasRenderingContext2D');\n    }\n    console.log(\n        '[CanvasTransform] CanvasRenderingContext2D initialized.',\n        `${this.debugPath_}.canvas_ =`, this.canvas_,\n        `${this.debugPath_}.ctx_ =`, this.ctx_);\n  }\n\n  /** @override */\n  async transform(frame, controller) {\n    const ctx = this.ctx_;\n    if (!this.canvas_ || !ctx) {\n      frame.close();\n      return;\n    }\n    const width = frame.displayWidth;\n    const height = frame.displayHeight;\n    this.canvas_.width = width;\n    this.canvas_.height = height;\n    const timestamp = frame.timestamp;\n\n    ctx.drawImage(frame, 0, 0);\n    frame.close();\n\n    ctx.shadowColor = '#000';\n    ctx.shadowBlur = 20;\n    ctx.lineWidth = 50;\n    ctx.strokeStyle = '#000';\n    ctx.strokeRect(0, 0, width, height);\n\n    // alpha: 'discard' is needed in order to send frames to a PeerConnection.\n    controller.enqueue(new VideoFrame(this.canvas_, {timestamp, alpha: 'discard'}));\n  }\n\n  /** @override */\n  destroy() {}\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/main.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */\nif (typeof MediaStreamTrackProcessor === 'undefined' ||\n    typeof MediaStreamTrackGenerator === 'undefined') {\n  alert(\n      'Your browser does not support the experimental MediaStreamTrack API ' +\n      'for Insertable Streams of Media. See the note at the bottom of the ' +\n      'page.');\n}\n\n/* global CameraSource */ // defined in camera-source.js\n/* global CanvasSource */ // defined in canvas-source.js\n/* global CanvasTransform */ // defined in canvas-transform.js\n/* global PeerConnectionSink */ // defined in peer-connection-sink.js\n/* global PeerConnectionSource */ // defined in peer-connection-source.js\n/* global Pipeline */ // defined in pipeline.js\n/* global NullTransform, DropTransform, DelayTransform */ // defined in simple-transforms.js\n/* global VideoSink */ // defined in video-sink.js\n/* global VideoSource */ // defined in video-source.js\n/* global WebGLTransform */ // defined in webgl-transform.js\n/* global WebCodecTransform */ // defined in webcodec-transform.js\n\n/**\n * Allows inspecting objects in the console. See console log messages for\n * attributes added to this debug object.\n * @type {!Object<string,*>}\n */\nlet debug = {};\n\n/**\n * FrameTransformFn applies a transform to a frame and queues the output frame\n * (if any) using the controller. The first argument is the input frame and the\n * second argument is the stream controller.\n * The VideoFrame should be closed as soon as it is no longer needed to free\n * resources and maintain good performance.\n * @typedef {function(\n *     !VideoFrame,\n *     !TransformStreamDefaultController<!VideoFrame>): !Promise<undefined>}\n */\nlet FrameTransformFn; // eslint-disable-line no-unused-vars\n\n/**\n * Creates a pair of MediaStreamTrackProcessor and MediaStreamTrackGenerator\n * that applies transform to sourceTrack. This function is the core part of the\n * sample, demonstrating how to use the new API.\n * @param {!MediaStreamTrack} sourceTrack the video track to be transformed. The\n *     track can be from any source, e.g. getUserMedia, RTCTrackEvent, or\n *     captureStream on HTMLMediaElement or HTMLCanvasElement.\n * @param {!FrameTransformFn} transform the transform to apply to sourceTrack;\n *     the transformed frames are available on the returned track. See the\n *     implementations of FrameTransform.transform later in this file for\n *     examples.\n * @param {!AbortSignal} signal can be used to stop processing\n * @return {!MediaStreamTrack} the result of sourceTrack transformed using\n *     transform.\n */\n// eslint-disable-next-line no-unused-vars\nfunction createProcessedMediaStreamTrack(sourceTrack, transform, signal) {\n  // Create the MediaStreamTrackProcessor.\n  /** @type {?MediaStreamTrackProcessor<!VideoFrame>} */\n  let processor;\n  try {\n    processor = new MediaStreamTrackProcessor(sourceTrack);\n  } catch (e) {\n    alert(`MediaStreamTrackProcessor failed: ${e}`);\n    throw e;\n  }\n\n  // Create the MediaStreamTrackGenerator.\n  /** @type {?MediaStreamTrackGenerator<!VideoFrame>} */\n  let generator;\n  try {\n    generator = new MediaStreamTrackGenerator('video');\n  } catch (e) {\n    alert(`MediaStreamTrackGenerator failed: ${e}`);\n    throw e;\n  }\n\n  const source = processor.readable;\n  const sink = generator.writable;\n\n  // Create a TransformStream using our FrameTransformFn. (Note that the\n  // \"Stream\" in TransformStream refers to the Streams API, specified by\n  // https://streams.spec.whatwg.org/, not the Media Capture and Streams API,\n  // specified by https://w3c.github.io/mediacapture-main/.)\n  /** @type {!TransformStream<!VideoFrame, !VideoFrame>} */\n  const transformer = new TransformStream({transform});\n\n  // Apply the transform to the processor's stream and send it to the\n  // generator's stream.\n  const promise = source.pipeThrough(transformer, {signal}).pipeTo(sink);\n\n  promise.catch((e) => {\n    if (signal.aborted) {\n      console.log(\n          '[createProcessedMediaStreamTrack] Shutting down streams after abort.');\n    } else {\n      console.error(\n          '[createProcessedMediaStreamTrack] Error from stream transform:', e);\n    }\n    source.cancel(e);\n    sink.abort(e);\n  });\n\n  debug['processor'] = processor;\n  debug['generator'] = generator;\n  debug['transformStream'] = transformer;\n  console.log(\n      '[createProcessedMediaStreamTrack] Created MediaStreamTrackProcessor, ' +\n          'MediaStreamTrackGenerator, and TransformStream.',\n      'debug.processor =', processor, 'debug.generator =', generator,\n      'debug.transformStream =', transformer);\n\n  return generator;\n}\n\n/**\n * The current video pipeline. Initialized by initPipeline().\n * @type {?Pipeline}\n */\nlet pipeline;\n\n/**\n * Sets up handlers for interacting with the UI elements on the page.\n */\nfunction initUI() {\n  const sourceSelector = /** @type {!HTMLSelectElement} */ (\n    document.getElementById('sourceSelector'));\n  const sourceVisibleCheckbox = (/** @type {!HTMLInputElement} */ (\n    document.getElementById('sourceVisible')));\n  /**\n   * Updates the pipeline based on the current settings of the sourceSelector\n   * and sourceVisible UI elements. Unlike updatePipelineSource(), never\n   * re-initializes the pipeline.\n   */\n  function updatePipelineSourceIfSet() {\n    const sourceType =\n        sourceSelector.options[sourceSelector.selectedIndex].value;\n    if (!sourceType) return;\n    console.log(`[UI] Selected source: ${sourceType}`);\n    let source;\n    switch (sourceType) {\n      case 'camera':\n        source = new CameraSource();\n        break;\n      case 'video':\n        source = new VideoSource();\n        break;\n      case 'canvas':\n        source = new CanvasSource();\n        break;\n      case 'pc':\n        source = new PeerConnectionSource(new CameraSource());\n        break;\n      default:\n        alert(`unknown source ${sourceType}`);\n        return;\n    }\n    source.setVisibility(sourceVisibleCheckbox.checked);\n    pipeline.updateSource(source);\n  }\n  /**\n   * Updates the pipeline based on the current settings of the sourceSelector\n   * and sourceVisible UI elements. If the \"stopped\" option is selected,\n   * reinitializes the pipeline instead.\n   */\n  function updatePipelineSource() {\n    const sourceType =\n        sourceSelector.options[sourceSelector.selectedIndex].value;\n    if (!sourceType || !pipeline) {\n      initPipeline();\n    } else {\n      updatePipelineSourceIfSet();\n    }\n  }\n  sourceSelector.oninput = updatePipelineSource;\n  sourceSelector.disabled = false;\n\n  /**\n   * Updates the source visibility, if the source is already started.\n   */\n  function updatePipelineSourceVisibility() {\n    console.log(`[UI] Changed source visibility: ${\n        sourceVisibleCheckbox.checked ? 'added' : 'removed'}`);\n    if (pipeline) {\n      const source = pipeline.getSource();\n      if (source) {\n        source.setVisibility(sourceVisibleCheckbox.checked);\n      }\n    }\n  }\n  sourceVisibleCheckbox.oninput = updatePipelineSourceVisibility;\n  sourceVisibleCheckbox.disabled = false;\n\n  const transformSelector = /** @type {!HTMLSelectElement} */ (\n    document.getElementById('transformSelector'));\n  /**\n   * Updates the pipeline based on the current settings of the transformSelector\n   * UI element.\n   */\n  function updatePipelineTransform() {\n    if (!pipeline) {\n      return;\n    }\n    const transformType =\n        transformSelector.options[transformSelector.selectedIndex].value;\n    console.log(`[UI] Selected transform: ${transformType}`);\n    switch (transformType) {\n      case 'webgl':\n        pipeline.updateTransform(new WebGLTransform());\n        break;\n      case 'canvas2d':\n        pipeline.updateTransform(new CanvasTransform());\n        break;\n      case 'drop':\n        // Defined in simple-transforms.js.\n        pipeline.updateTransform(new DropTransform());\n        break;\n      case 'noop':\n        // Defined in simple-transforms.js.\n        pipeline.updateTransform(new NullTransform());\n        break;\n      case 'delay':\n        // Defined in simple-transforms.js.\n        pipeline.updateTransform(new DelayTransform());\n        break;\n      case 'webcodec':\n        // Defined in webcodec-transform.js\n        pipeline.updateTransform(new WebCodecTransform());\n        break;\n      default:\n        alert(`unknown transform ${transformType}`);\n        break;\n    }\n  }\n  transformSelector.oninput = updatePipelineTransform;\n  transformSelector.disabled = false;\n\n  const sinkSelector = (/** @type {!HTMLSelectElement} */ (\n    document.getElementById('sinkSelector')));\n  /**\n   * Updates the pipeline based on the current settings of the sinkSelector UI\n   * element.\n   */\n  function updatePipelineSink() {\n    const sinkType = sinkSelector.options[sinkSelector.selectedIndex].value;\n    console.log(`[UI] Selected sink: ${sinkType}`);\n    switch (sinkType) {\n      case 'video':\n        pipeline.updateSink(new VideoSink());\n        break;\n      case 'pc':\n        pipeline.updateSink(new PeerConnectionSink());\n        break;\n      default:\n        alert(`unknown sink ${sinkType}`);\n        break;\n    }\n  }\n  sinkSelector.oninput = updatePipelineSink;\n  sinkSelector.disabled = false;\n\n  /**\n   * Initializes/reinitializes the pipeline. Called on page load and after the\n   * user chooses to stop the video source.\n   */\n  function initPipeline() {\n    if (pipeline) pipeline.destroy();\n    pipeline = new Pipeline();\n    debug = {pipeline};\n    updatePipelineSourceIfSet();\n    updatePipelineTransform();\n    updatePipelineSink();\n    console.log(\n        '[initPipeline] Created new Pipeline.', 'debug.pipeline =', pipeline);\n  }\n}\n\nwindow.onload = initUI;\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/peer-connection-pipe.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Sends a MediaStream to one end of an RTCPeerConnection and provides the\n * remote end as the resulting MediaStream.\n * In an actual video calling app, the two RTCPeerConnection objects would be\n * instantiated on different devices. However, in this sample, both sides of the\n * peer connection are local to allow the sample to be self-contained.\n * For more detailed samples using RTCPeerConnection, take a look at\n * https://webrtc.github.io/samples/.\n */\nclass PeerConnectionPipe { // eslint-disable-line no-unused-vars\n  /**\n   * @param {!MediaStream} inputStream stream to pipe over the peer connection\n   * @param {string} debugPath the path to this object from the debug global var\n   */\n  constructor(inputStream, debugPath) {\n    /**\n     * @private @const {!RTCPeerConnection} the calling side of the peer\n     *     connection, connected to inputStream_.\n     */\n    this.caller_ = new RTCPeerConnection(null);\n    /**\n     * @private @const {!RTCPeerConnection} the answering side of the peer\n     *     connection, providing the stream returned by getMediaStream.\n     */\n    this.callee_ = new RTCPeerConnection(null);\n    /** @private {string} */\n    this.debugPath_ = debugPath;\n    /**\n     * @private @const {!Promise<!MediaStream>} the stream containing tracks\n     *     from callee_, returned by getMediaStream.\n     */\n    this.outputStreamPromise_ = this.init_(inputStream);\n  }\n  /**\n   * Sets the path to this object from the debug global var.\n   * @param {string} path\n   */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n  }\n  /**\n   * @param {!MediaStream} inputStream stream to pipe over the peer connection\n   * @return {!Promise<!MediaStream>}\n   * @private\n   */\n  async init_(inputStream) {\n    console.log(\n        '[PeerConnectionPipe] Initiating peer connection.',\n        `${this.debugPath_} =`, this);\n    this.caller_.onicecandidate = (/** !RTCPeerConnectionIceEvent*/ event) => {\n      if (event.candidate) this.callee_.addIceCandidate(event.candidate);\n    };\n    this.callee_.onicecandidate = (/** !RTCPeerConnectionIceEvent */ event) => {\n      if (event.candidate) this.caller_.addIceCandidate(event.candidate);\n    };\n    const outputStream = new MediaStream();\n    const receiverStreamPromise = new Promise(resolve => {\n      this.callee_.ontrack = (/** !RTCTrackEvent */ event) => {\n        outputStream.addTrack(event.track);\n        if (outputStream.getTracks().length == inputStream.getTracks().length) {\n          resolve(outputStream);\n        }\n      };\n    });\n    inputStream.getTracks().forEach(track => {\n      this.caller_.addTransceiver(track, {direction: 'sendonly'});\n    });\n    await this.caller_.setLocalDescription();\n    await this.callee_.setRemoteDescription(\n        /** @type {!RTCSessionDescription} */ (this.caller_.localDescription));\n    await this.callee_.setLocalDescription();\n    await this.caller_.setRemoteDescription(\n        /** @type {!RTCSessionDescription} */ (this.callee_.localDescription));\n    await receiverStreamPromise;\n    console.log(\n        '[PeerConnectionPipe] Peer connection established.',\n        `${this.debugPath_}.caller_ =`, this.caller_,\n        `${this.debugPath_}.callee_ =`, this.callee_);\n    return receiverStreamPromise;\n  }\n\n  /**\n   * Provides the MediaStream that has been piped through a peer connection.\n   * @return {!Promise<!MediaStream>}\n   */\n  getOutputStream() {\n    return this.outputStreamPromise_;\n  }\n\n  /** Frees any resources used by this object. */\n  destroy() {\n    console.log('[PeerConnectionPipe] Closing peer connection.');\n    this.caller_.close();\n    this.callee_.close();\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/peer-connection-sink.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global PeerConnectionPipe */ // defined in peer-connection-pipe.js\n/* global VideoSink */ // defined in video-sink.js\n\n/**\n * Sends the transformed video to one end of an RTCPeerConnection and displays\n * the remote end in a video element. In this sample, a PeerConnectionSink\n * represents processing the local user's camera input using a\n * MediaStreamTrackProcessor before sending it to a remote video call\n * participant. Contrast with a PeerConnectionSource.\n * @implements {MediaStreamSink} in pipeline.js\n */\nclass PeerConnectionSink { // eslint-disable-line no-unused-vars\n  constructor() {\n    /**\n     * @private @const {!VideoSink} manages displaying the video stream in the\n     *     page\n     */\n    this.videoSink_ = new VideoSink();\n    /**\n     * @private {?PeerConnectionPipe} handles piping the MediaStream through an\n     *     RTCPeerConnection\n     */\n    this.pipe_ = null;\n    /** @private {string} */\n    this.debugPath_ = 'debug.pipeline.sink_';\n    this.videoSink_.setDebugPath(`${this.debugPath_}.videoSink_`);\n  }\n\n  /** @override */\n  async setMediaStream(stream) {\n    console.log(\n        '[PeerConnectionSink] Setting peer connection sink stream.', stream);\n    if (this.pipe_) this.pipe_.destroy();\n    this.pipe_ = new PeerConnectionPipe(stream, `${this.debugPath_}.pipe_`);\n    const pipedStream = await this.pipe_.getOutputStream();\n    console.log(\n        '[PeerConnectionSink] Received callee peer connection stream.',\n        pipedStream);\n    await this.videoSink_.setMediaStream(pipedStream);\n  }\n\n  /** @override */\n  destroy() {\n    this.videoSink_.destroy();\n    if (this.pipe_) this.pipe_.destroy();\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/peer-connection-source.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global PeerConnectionPipe */ // defined in peer-connection-pipe.js\n/* global VideoMirrorHelper */ // defined in video-mirror-helper.js\n\n/**\n * Sends the original source video to one end of an RTCPeerConnection and\n * provides the remote end as the final source.\n * In this sample, a PeerConnectionSource represents receiving video from a\n * remote participant and locally processing it using a\n * MediaStreamTrackProcessor before displaying it on the screen. Contrast with a\n * PeerConnectionSink.\n * @implements {MediaStreamSource} in pipeline.js\n */\nclass PeerConnectionSource { // eslint-disable-line no-unused-vars\n  /**\n   * @param {!MediaStreamSource} originalSource original stream source, whose\n   *     output is sent over the peer connection\n   */\n  constructor(originalSource) {\n    /**\n     * @private @const {!VideoMirrorHelper} manages displaying the video stream\n     *     in the page\n     */\n    this.videoMirrorHelper_ = new VideoMirrorHelper();\n    /**\n     * @private @const {!MediaStreamSource} original stream source, whose output\n     *     is sent on the sender peer connection. In an actual video calling\n     *     app, this stream would be generated from the remote participant's\n     *     camera. However, in this sample, both sides of the peer connection\n     *     are local to allow the sample to be self-contained.\n     */\n    this.originalStreamSource_ = originalSource;\n    /**\n     * @private {?PeerConnectionPipe} handles piping the MediaStream through an\n     *     RTCPeerConnection\n     */\n    this.pipe_ = null;\n    /** @private {string} */\n    this.debugPath_ = '<unknown>';\n  }\n  /** @override */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n    this.videoMirrorHelper_.setDebugPath(`${path}.videoMirrorHelper_`);\n    this.originalStreamSource_.setDebugPath(`${path}.originalStreamSource_`);\n    if (this.pipe_) this.pipe_.setDebugPath(`${path}.pipe_`);\n  }\n  /** @override */\n  setVisibility(visible) {\n    this.videoMirrorHelper_.setVisibility(visible);\n  }\n\n  /** @override */\n  async getMediaStream() {\n    if (this.pipe_) return this.pipe_.getOutputStream();\n\n    console.log(\n        '[PeerConnectionSource] Obtaining original source media stream.',\n        `${this.debugPath_}.originalStreamSource_ =`,\n        this.originalStreamSource_);\n    const originalStream = await this.originalStreamSource_.getMediaStream();\n    this.pipe_ =\n        new PeerConnectionPipe(originalStream, `${this.debugPath_}.pipe_`);\n    const outputStream = await this.pipe_.getOutputStream();\n    console.log(\n        '[PeerConnectionSource] Received callee peer connection stream.',\n        outputStream);\n    this.videoMirrorHelper_.setStream(outputStream);\n    return outputStream;\n  }\n\n  /** @override */\n  destroy() {\n    this.videoMirrorHelper_.destroy();\n    if (this.pipe_) this.pipe_.destroy();\n    this.originalStreamSource_.destroy();\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/pipeline.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global createProcessedMediaStreamTrack */ // defined in main.js\n\n/**\n * Wrapper around createProcessedMediaStreamTrack to apply transform to a\n * MediaStream.\n * @param {!MediaStream} sourceStream the video stream to be transformed. The\n *     first video track will be used.\n * @param {!FrameTransformFn} transform the transform to apply to the\n *     sourceStream.\n * @param {!AbortSignal} signal can be used to stop processing\n * @return {!MediaStream} holds a single video track of the transformed video\n *     frames\n */\nfunction createProcessedMediaStream(sourceStream, transform, signal) {\n  // For this sample, we're only dealing with video tracks.\n  /** @type {!MediaStreamTrack} */\n  const sourceTrack = sourceStream.getVideoTracks()[0];\n\n  const processedTrack =\n      createProcessedMediaStreamTrack(sourceTrack, transform, signal);\n\n  // Create a new MediaStream to hold our processed track.\n  const processedStream = new MediaStream();\n  processedStream.addTrack(processedTrack);\n\n  return processedStream;\n}\n\n/**\n * Interface implemented by all video sources the user can select. A common\n * interface allows the user to choose a source independently of the transform\n * and sink.\n * @interface\n */\nclass MediaStreamSource { // eslint-disable-line no-unused-vars\n  /**\n   * Sets the path to this object from the debug global var.\n   * @param {string} path\n   */\n  setDebugPath(path) {}\n  /**\n   * Indicates if the source video should be mirrored/displayed on the page. If\n   * false (the default), any element producing frames will not be a child of\n   * the document.\n   * @param {boolean} visible whether to add the raw source video to the page\n   */\n  setVisibility(visible) {}\n  /**\n   * Initializes and returns the MediaStream for this source.\n   * @return {!Promise<!MediaStream>}\n   */\n  async getMediaStream() {}\n  /** Frees any resources used by this object. */\n  destroy() {}\n}\n\n/**\n * Interface implemented by all video transforms that the user can select. A\n * common interface allows the user to choose a transform independently of the\n * source and sink.\n * @interface\n */\nclass FrameTransform { // eslint-disable-line no-unused-vars\n  /** Initializes state that is reused across frames. */\n  async init() {}\n  /**\n   * Applies the transform to frame. Queues the output frame (if any) using the\n   * controller.\n   * @param {!VideoFrame} frame the input frame\n   * @param {!TransformStreamDefaultController<!VideoFrame>} controller\n   */\n  async transform(frame, controller) {}\n  /** Frees any resources used by this object. */\n  destroy() {}\n}\n\n/**\n * Interface implemented by all video sinks that the user can select. A common\n * interface allows the user to choose a sink independently of the source and\n * transform.\n * @interface\n */\nclass MediaStreamSink { // eslint-disable-line no-unused-vars\n  /**\n   * @param {!MediaStream} stream\n   */\n  async setMediaStream(stream) {}\n  /** Frees any resources used by this object. */\n  destroy() {}\n}\n\n/**\n * Assembles a MediaStreamSource, FrameTransform, and MediaStreamSink together.\n */\nclass Pipeline { // eslint-disable-line no-unused-vars\n  constructor() {\n    /** @private {?MediaStreamSource} set by updateSource*/\n    this.source_ = null;\n    /** @private {?FrameTransform} set by updateTransform */\n    this.frameTransform_ = null;\n    /** @private {?MediaStreamSink} set by updateSink */\n    this.sink_ = null;\n    /** @private {!AbortController} may used to stop all processing */\n    this.abortController_ = new AbortController();\n    /**\n     * @private {?MediaStream} set in maybeStartPipeline_ after all of source_,\n     *     frameTransform_, and sink_ are set\n     */\n    this.processedStream_ = null;\n  }\n\n  /** @return {?MediaStreamSource} */\n  getSource() {\n    return this.source_;\n  }\n\n  /**\n   * Sets a new source for the pipeline.\n   * @param {!MediaStreamSource} mediaStreamSource\n   */\n  async updateSource(mediaStreamSource) {\n    if (this.source_) {\n      this.abortController_.abort();\n      this.abortController_ = new AbortController();\n      this.source_.destroy();\n      this.processedStream_ = null;\n    }\n    this.source_ = mediaStreamSource;\n    this.source_.setDebugPath('debug.pipeline.source_');\n    console.log(\n        '[Pipeline] Updated source.',\n        'debug.pipeline.source_ = ', this.source_);\n    await this.maybeStartPipeline_();\n  }\n\n  /** @private */\n  async maybeStartPipeline_() {\n    if (this.processedStream_ || !this.source_ || !this.frameTransform_ ||\n        !this.sink_) {\n      return;\n    }\n    const sourceStream = await this.source_.getMediaStream();\n    await this.frameTransform_.init();\n    try {\n      this.processedStream_ = createProcessedMediaStream(\n          sourceStream, async (frame, controller) => {\n            if (this.frameTransform_) {\n              await this.frameTransform_.transform(frame, controller);\n            }\n          }, this.abortController_.signal);\n    } catch (e) {\n      this.destroy();\n      return;\n    }\n    await this.sink_.setMediaStream(this.processedStream_);\n    console.log(\n        '[Pipeline] Pipeline started.',\n        'debug.pipeline.abortController_ =', this.abortController_);\n  }\n\n  /**\n   * Sets a new transform for the pipeline.\n   * @param {!FrameTransform} frameTransform\n   */\n  async updateTransform(frameTransform) {\n    if (this.frameTransform_) this.frameTransform_.destroy();\n    this.frameTransform_ = frameTransform;\n    console.log(\n        '[Pipeline] Updated frame transform.',\n        'debug.pipeline.frameTransform_ = ', this.frameTransform_);\n    if (this.processedStream_) {\n      await this.frameTransform_.init();\n    } else {\n      await this.maybeStartPipeline_();\n    }\n  }\n\n  /**\n   * Sets a new sink for the pipeline.\n   * @param {!MediaStreamSink} mediaStreamSink\n   */\n  async updateSink(mediaStreamSink) {\n    if (this.sink_) this.sink_.destroy();\n    this.sink_ = mediaStreamSink;\n    console.log(\n        '[Pipeline] Updated sink.', 'debug.pipeline.sink_ = ', this.sink_);\n    if (this.processedStream_) {\n      await this.sink_.setMediaStream(this.processedStream_);\n    } else {\n      await this.maybeStartPipeline_();\n    }\n  }\n\n  /** Frees any resources used by this object. */\n  destroy() {\n    console.log('[Pipeline] Destroying Pipeline');\n    this.abortController_.abort();\n    if (this.source_) this.source_.destroy();\n    if (this.frameTransform_) this.frameTransform_.destroy();\n    if (this.sink_) this.sink_.destroy();\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/simple-transforms.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Does nothing.\n * @implements {FrameTransform} in pipeline.js\n */\nclass NullTransform { // eslint-disable-line no-unused-vars\n  /** @override */\n  async init() {}\n  /** @override */\n  async transform(frame, controller) {\n    controller.enqueue(frame);\n  }\n  /** @override */\n  destroy() {}\n}\n\n/**\n * Drops frames at random.\n * @implements {FrameTransform} in pipeline.js\n */\nclass DropTransform { // eslint-disable-line no-unused-vars\n  /** @override */\n  async init() {}\n  /** @override */\n  async transform(frame, controller) {\n    if (Math.random() < 0.5) {\n      controller.enqueue(frame);\n    } else {\n      frame.close();\n    }\n  }\n  /** @override */\n  destroy() {}\n}\n\n/**\n * Delays all frames by 100ms.\n * @implements {FrameTransform} in pipeline.js\n */\nclass DelayTransform { // eslint-disable-line no-unused-vars\n  /** @override */\n  async init() {}\n  /** @override */\n  async transform(frame, controller) {\n    await new Promise(resolve => setTimeout(resolve, 100));\n    controller.enqueue(frame);\n  }\n  /** @override */\n  destroy() {}\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/video-mirror-helper.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Helper to display a MediaStream in an HTMLVideoElement, based on the\n * visibility setting.\n */\nclass VideoMirrorHelper { // eslint-disable-line no-unused-vars\n  constructor() {\n    /** @private {boolean} */\n    this.visibility_ = false;\n    /** @private {?MediaStream} the stream to display */\n    this.stream_ = null;\n    /**\n     * @private {?HTMLVideoElement} video element mirroring the camera stream.\n     *    Set if visibility_ is true and stream_ is set.\n     */\n    this.video_ = null;\n    /** @private {string} */\n    this.debugPath_ = '<unknown>';\n  }\n  /**\n   * Sets the path to this object from the debug global var.\n   * @param {string} path\n   */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n  }\n  /**\n   * Indicates if the video should be mirrored/displayed on the page.\n   * @param {boolean} visible whether to add the video from the source stream to\n   *     the page\n   */\n  setVisibility(visible) {\n    this.visibility_ = visible;\n    if (this.video_ && !this.visibility_) {\n      this.video_.parentNode.removeChild(this.video_);\n      this.video_ = null;\n    }\n    this.maybeAddVideoElement_();\n  }\n\n  /**\n   * @param {!MediaStream} stream\n   */\n  setStream(stream) {\n    this.stream_ = stream;\n    this.maybeAddVideoElement_();\n  }\n\n  /** @private */\n  maybeAddVideoElement_() {\n    if (!this.video_ && this.visibility_ && this.stream_) {\n      this.video_ =\n        /** @type {!HTMLVideoElement} */ (document.createElement('video'));\n      console.log(\n          '[VideoMirrorHelper] Adding source video mirror.',\n          `${this.debugPath_}.video_ =`, this.video_);\n      this.video_.classList.add('video', 'sourceVideo');\n      this.video_.srcObject = this.stream_;\n      const outputVideoContainer =\n          document.getElementById('outputVideoContainer');\n      outputVideoContainer.parentNode.insertBefore(\n          this.video_, outputVideoContainer);\n      this.video_.play();\n    }\n  }\n\n  /** Frees any resources used by this object. */\n  destroy() {\n    if (this.video_) {\n      this.video_.pause();\n      this.video_.srcObject = null;\n      this.video_.parentNode.removeChild(this.video_);\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/video-sink.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Displays the output stream in a video element.\n * @implements {MediaStreamSink} in pipeline.js\n */\nclass VideoSink { // eslint-disable-line no-unused-vars\n  constructor() {\n    /**\n     * @private {?HTMLVideoElement} output video element\n     */\n    this.video_ = null;\n    /** @private {string} */\n    this.debugPath_ = 'debug.pipeline.sink_';\n  }\n  /**\n   * Sets the path to this object from the debug global var.\n   * @param {string} path\n   */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n  }\n  /** @override */\n  async setMediaStream(stream) {\n    console.log('[VideoSink] Setting sink stream.', stream);\n    if (!this.video_) {\n      this.video_ =\n        /** @type {!HTMLVideoElement} */ (document.createElement('video'));\n      this.video_.classList.add('video', 'sinkVideo');\n      document.getElementById('outputVideoContainer').appendChild(this.video_);\n      console.log(\n          '[VideoSink] Added video element to page.',\n          `${this.debugPath_}.video_ =`, this.video_);\n    }\n    this.video_.srcObject = stream;\n    this.video_.play();\n  }\n  /** @override */\n  destroy() {\n    if (this.video_) {\n      console.log('[VideoSink] Stopping sink video');\n      this.video_.pause();\n      this.video_.srcObject = null;\n      this.video_.parentNode.removeChild(this.video_);\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/video-source.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Decodes and plays a video.\n * @implements {MediaStreamSource} in pipeline.js\n */\nclass VideoSource { // eslint-disable-line no-unused-vars\n  constructor() {\n    /** @private {boolean} */\n    this.visibility_ = false;\n    /** @private {?HTMLVideoElement} video element providing the MediaStream */\n    this.video_ = null;\n    /**\n     * @private {?Promise<!MediaStream>} a Promise that resolves to the\n     *     MediaStream from captureStream. Set iff video_ is set.\n     */\n    this.stream_ = null;\n    /** @private {string} */\n    this.debugPath_ = '<unknown>';\n  }\n  /** @override */\n  setDebugPath(path) {\n    this.debugPath_ = path;\n  }\n  /** @override */\n  setVisibility(visible) {\n    this.visibility_ = visible;\n    if (this.video_) {\n      this.updateVideoVisibility();\n    }\n  }\n  /** @private */\n  updateVideoVisibility() {\n    if (this.video_.parentNode && !this.visibility_) {\n      if (!this.video_.paused) {\n        // Video playback is automatically paused when the element is removed\n        // from the DOM. That is not the behavior we want.\n        this.video_.onpause = async () => {\n          this.video_.onpause = null;\n          await this.video_.play();\n        };\n      }\n      this.video_.parentNode.removeChild(this.video_);\n    } else if (!this.video_.parentNode && this.visibility_) {\n      console.log(\n          '[VideoSource] Adding source video element to page.',\n          `${this.debugPath_}.video_ =`, this.video_);\n      const outputVideoContainer =\n          document.getElementById('outputVideoContainer');\n      outputVideoContainer.parentNode.insertBefore(\n          this.video_, outputVideoContainer);\n    }\n  }\n  /** @override */\n  async getMediaStream() {\n    if (this.stream_) return this.stream_;\n\n    console.log('[VideoSource] Loading video');\n\n    this.video_ =\n      /** @type {!HTMLVideoElement} */ (document.createElement('video'));\n    this.video_.classList.add('video', 'sourceVideo');\n    this.video_.controls = true;\n    this.video_.loop = true;\n    this.video_.muted = true;\n    // All browsers that support insertable streams also support WebM/VP8.\n    this.video_.src = '../../../video/chrome.webm';\n    this.video_.load();\n    this.video_.play();\n    this.updateVideoVisibility();\n    this.stream_ = new Promise((resolve, reject) => {\n      this.video_.oncanplay = () => {\n        if (!resolve || !reject) return;\n        console.log('[VideoSource] Obtaining video capture stream');\n        if (this.video_.captureStream) {\n          resolve(this.video_.captureStream());\n        } else if (this.video_.mozCaptureStream) {\n          resolve(this.video_.mozCaptureStream());\n        } else {\n          const e = new Error('Stream capture is not supported');\n          console.error(e);\n          reject(e);\n        }\n        resolve = null;\n        reject = null;\n      };\n    });\n    await this.stream_;\n    console.log(\n        '[VideoSource] Received source video stream.',\n        `${this.debugPath_}.stream_ =`, this.stream_);\n    return this.stream_;\n  }\n  /** @override */\n  destroy() {\n    if (this.video_) {\n      console.log('[VideoSource] Stopping source video');\n      this.video_.pause();\n      if (this.video_.parentNode) {\n        this.video_.parentNode.removeChild(this.video_);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/webcodec-transform.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Encodes and decodes frames using the WebCodec API.\n * @implements {FrameTransform} in pipeline.js\n */\nclass WebCodecTransform { // eslint-disable-line no-unused-vars\n  constructor() {\n    // Encoder and decoder are initialized in init()\n    this.decoder_ = null;\n    this.encoder_ = null;\n    this.controller_ = null;\n  }\n  /** @override */\n  async init() {\n    console.log('[WebCodecTransform] Initializing encoder and decoder');\n    this.decoder_ = new VideoDecoder({\n      output: frame => this.handleDecodedFrame(frame),\n      error: this.error\n    });\n    this.encoder_ = new VideoEncoder({\n      output: frame => this.handleEncodedFrame(frame),\n      error: this.error\n    });\n    this.encoder_.configure({codec: 'vp8', width: 640, height: 480});\n    this.decoder_.configure({codec: 'vp8', width: 640, height: 480});\n  }\n\n  /** @override */\n  async transform(frame, controller) {\n    if (!this.encoder_) {\n      frame.close();\n      return;\n    }\n    try {\n      this.controller_ = controller;\n      this.encoder_.encode(frame);\n    } finally {\n      frame.close();\n    }\n  }\n\n  /** @override */\n  destroy() {}\n\n  /* Helper functions */\n  handleEncodedFrame(encodedFrame) {\n    this.decoder_.decode(encodedFrame);\n  }\n\n  handleDecodedFrame(videoFrame) {\n    if (!this.controller_) {\n      videoFrame.close();\n      return;\n    }\n    this.controller_.enqueue(videoFrame);\n  }\n\n  error(e) {\n    console.log('[WebCodecTransform] Bad stuff happened: ' + e);\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/video-processing/js/webgl-transform.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Applies a warp effect using WebGL.\n * @implements {FrameTransform} in pipeline.js\n */\nclass WebGLTransform { // eslint-disable-line no-unused-vars\n  constructor() {\n    // All fields are initialized in init()\n    /** @private {?OffscreenCanvas} canvas used to create the WebGL context */\n    this.canvas_ = null;\n    /** @private {?WebGLRenderingContext} */\n    this.gl_ = null;\n    /** @private {?WebGLUniformLocation} location of inSampler */\n    this.sampler_ = null;\n    /** @private {?WebGLProgram} */\n    this.program_ = null;\n    /** @private {?WebGLTexture} input texture */\n    this.texture_ = null;\n    /** @private {string} */\n    this.debugPath_ = 'debug.pipeline.frameTransform_';\n  }\n  /** @override */\n  async init() {\n    console.log('[WebGLTransform] Initializing WebGL.');\n    this.canvas_ = new OffscreenCanvas(1, 1);\n    const gl = /** @type {?WebGLRenderingContext} */ (\n      this.canvas_.getContext('webgl'));\n    if (!gl) {\n      alert(\n          'Failed to create WebGL context. Check that WebGL is supported ' +\n          'by your browser and hardware.');\n      return;\n    }\n    this.gl_ = gl;\n    const vertexShader = this.loadShader_(gl.VERTEX_SHADER, `\n      precision mediump float;\n      attribute vec3 g_Position;\n      attribute vec2 g_TexCoord;\n      varying vec2 texCoord;\n      void main() {\n        gl_Position = vec4(g_Position, 1.0);\n        texCoord = g_TexCoord;\n      }`);\n    const fragmentShader = this.loadShader_(gl.FRAGMENT_SHADER, `\n      precision mediump float;\n      varying vec2 texCoord;\n      uniform sampler2D inSampler;\n      void main(void) {\n        float boundary = distance(texCoord, vec2(0.5)) - 0.2;\n        if (boundary < 0.0) {\n          gl_FragColor = texture2D(inSampler, texCoord);\n        } else {\n          // Rotate the position\n          float angle = 2.0 * boundary;\n          vec2 rotation = vec2(sin(angle), cos(angle));\n          vec2 fromCenter = texCoord - vec2(0.5);\n          vec2 rotatedPosition = vec2(\n            fromCenter.x * rotation.y + fromCenter.y * rotation.x,\n            fromCenter.y * rotation.y - fromCenter.x * rotation.x) + vec2(0.5);\n          gl_FragColor = texture2D(inSampler, rotatedPosition);\n        }\n      }`);\n    if (!vertexShader || !fragmentShader) return;\n    // Create the program object\n    const programObject = gl.createProgram();\n    gl.attachShader(programObject, vertexShader);\n    gl.attachShader(programObject, fragmentShader);\n    // Link the program\n    gl.linkProgram(programObject);\n    // Check the link status\n    const linked = gl.getProgramParameter(programObject, gl.LINK_STATUS);\n    if (!linked) {\n      const infoLog = gl.getProgramInfoLog(programObject);\n      gl.deleteProgram(programObject);\n      throw new Error(`Error linking program:\\n${infoLog}`);\n    }\n    gl.deleteShader(vertexShader);\n    gl.deleteShader(fragmentShader);\n    this.sampler_ = gl.getUniformLocation(programObject, 'inSampler');\n    this.program_ = programObject;\n    // Bind attributes\n    const vertices = [1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0];\n    // Pass-through.\n    const txtcoords = [1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0];\n    // Mirror horizonally.\n    // const txtcoords = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];\n    this.attributeSetFloats_('g_Position', 2, vertices);\n    this.attributeSetFloats_('g_TexCoord', 2, txtcoords);\n    // Initialize input texture\n    this.texture_ = gl.createTexture();\n    gl.bindTexture(gl.TEXTURE_2D, this.texture_);\n    const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue\n    gl.texImage2D(\n        gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n    console.log(\n        '[WebGLTransform] WebGL initialized.', `${this.debugPath_}.canvas_ =`,\n        this.canvas_, `${this.debugPath_}.gl_ =`, this.gl_);\n  }\n\n  /**\n   * Creates and compiles a WebGLShader from the provided source code.\n   * @param {number} type either VERTEX_SHADER or FRAGMENT_SHADER\n   * @param {string} shaderSrc\n   * @return {!WebGLShader}\n   * @private\n   */\n  loadShader_(type, shaderSrc) {\n    const gl = this.gl_;\n    const shader = gl.createShader(type);\n    // Load the shader source\n    gl.shaderSource(shader, shaderSrc);\n    // Compile the shader\n    gl.compileShader(shader);\n    // Check the compile status\n    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n      const infoLog = gl.getShaderInfoLog(shader);\n      gl.deleteShader(shader);\n      throw new Error(`Error compiling shader:\\n${infoLog}`);\n    }\n    return shader;\n  }\n\n  /**\n   * Sets a floating point shader attribute to the values in arr.\n   * @param {string} attrName the name of the shader attribute to set\n   * @param {number} vsize the number of components of the shader attribute's\n   *   type\n   * @param {!Array<number>} arr the values to set\n   * @private\n   */\n  attributeSetFloats_(attrName, vsize, arr) {\n    const gl = this.gl_;\n    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());\n    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(arr), gl.STATIC_DRAW);\n    const attr = gl.getAttribLocation(this.program_, attrName);\n    gl.enableVertexAttribArray(attr);\n    gl.vertexAttribPointer(attr, vsize, gl.FLOAT, false, 0, 0);\n  }\n\n  /** @override */\n  async transform(frame, controller) {\n    const gl = this.gl_;\n    if (!gl || !this.canvas_) {\n      frame.close();\n      return;\n    }\n    const width = frame.displayWidth;\n    const height = frame.displayHeight;\n    if (this.canvas_.width !== width || this.canvas_.height !== height) {\n      this.canvas_.width = width;\n      this.canvas_.height = height;\n      gl.viewport(0, 0, width, height);\n    }\n    const timestamp = frame.timestamp;\n    gl.activeTexture(gl.TEXTURE0);\n    gl.bindTexture(gl.TEXTURE_2D, this.texture_);\n    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n    gl.texImage2D(\n        gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);\n    frame.close();\n    gl.useProgram(this.program_);\n    gl.uniform1i(this.sampler_, 0);\n    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n    gl.bindTexture(gl.TEXTURE_2D, null);\n    // alpha: 'discard' is needed in order to send frames to a PeerConnection.\n    controller.enqueue(new VideoFrame(this.canvas_, {timestamp, alpha: 'discard'}));\n  }\n\n  /** @override */\n  destroy() {\n    if (this.gl_) {\n      console.log('[WebGLTransform] Forcing WebGL context to be lost.');\n      /** @type {!WEBGL_lose_context} */ (\n        this.gl_.getExtension('WEBGL_lose_context'))\n          .loseContext();\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/webgpu/css/main.css",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\nvideo {\n  width: 480px;\n  height: 270px;\n}\n\n.output {\n  width: 960px;\n  height: 540px;\n  margin: 0px 0px 0px 0px;\n}\n\n.error {\n  font-size: 20px;\n  color:red;\n}\n"
  },
  {
    "path": "src/content/insertable-streams/webgpu/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Integrations with WebGPU for custom video rendering</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\" />\n    <link rel=\"stylesheet\" href=\"css/main.css\" />\n\n</head>\n\n<body>\n\n    <div id=\"container\">\n        <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n            <span>Integrations with WebGPU for custom video rendering</span>\n        </h1>\n\n        <p>This sample shows how to render multiple video streams to canvas using the <a\n                href=\"https://github.com/w3c/mediacapture-transform\">insertable streams</a> and <a\n                href=\"https://gpuweb.github.io/gpuweb/\">WebGPU</a> APIs. There are options to either process the\n            rendering on the main thread or on a worker thread.\n        </p>\n        <div id=\"errorMsg\" class=\"error\"></div>\n        <br>\n        <div class=\"box\">\n            <span>Choose type of rendering:</span>\n            <select id=\"sourceSelector\">\n                <option selected value=\"stopped\">(stopped)</option>\n                <option value=\"main\">Main thread</option>\n                <option value=\"worker\">Worker thread</option>\n            </select>\n        </div>\n\n        <div>Input:</div>\n        <video id=\"inputVideo\" class=\"input\" width=\"480\" height=\"270\"></video>\n        <video id=\"gumInputVideo\" width=\"480\"  height=\"270\"></video>\n        <div>Output:</div>\n        <div id=\"outputVideo\" class=\"output\"></div>\n        <p>\n            <b>Note</b>: This sample is using WebGPU API that is in Origin Trial as\n            of 2021-09-21 and is available in Chrome M94 if the experimental code is enabled on\n            the command line with\n            <code>--enable-unsafe-webgpu</code>.\n        </p>\n        <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/webgpu\"\n            title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n    </div>\n\n    <script src=\"js/main.js\" async></script>\n    <script src=\"js/multi_video_main.js\" async></script>\n    <script src=\"js/multi_video_worker_manager.js\" async></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "src/content/insertable-streams/webgpu/js/main.js",
    "content": "'use strict';\n\n/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */\nif (typeof MediaStreamTrackProcessor === 'undefined' ||\n    typeof MediaStreamTrackGenerator === 'undefined') {\n  const errorMessage = 'Your browser does not support the MediaStreamTrack ' +\n        'API for Insertable Streams of Media which was shipped in M94.';\n  document.getElementById('errorMsg').innerText = errorMessage;\n  console.log(errorMessage);\n}\n\n/* global WebGPUTransform */ // defined in multi_video_main.js\n/* global WebGPUWorker */ // defined in multi_video_worker_manager.js\n\nlet videoElement;\n\nasync function getMediaStream(src) {\n  videoElement = document.getElementById('inputVideo');\n  videoElement.controls = true;\n  videoElement.loop = true;\n  videoElement.muted = true;\n  videoElement.src = src;\n  videoElement.load();\n  videoElement.play();\n\n  let sourceStream;\n  const mediaPromise = new Promise((resolve, reject) => {\n    videoElement.oncanplay = () => {\n      if (!resolve || !reject) return;\n      console.log('Obtaining video capture stream');\n      if (videoElement.captureStream) {\n        sourceStream = videoElement.captureStream();\n        resolve();\n      } else if (videoElement.mozCaptureStream) {\n        sourceStream = videoElement.mozCaptureStream();\n        resolve();\n      } else {\n        reject(new Error('Stream capture is not supported'));\n      }\n      resolve = null;\n      reject = null;\n    };\n  });\n  await mediaPromise;\n  console.log(\n      'Received source video stream.', sourceStream);\n  return sourceStream;\n}\n\nfunction getUserMediaStream() {\n  return navigator.mediaDevices.getUserMedia({\n    audio: false,\n    video: {width: 480, height: 270}\n  }).catch(err => {\n    throw new Error('Unable to fetch getUserMedia stream ' + err);\n  });\n}\n\nlet gpuTransform;\nlet gumTrack;\nlet gumVideo;\n\nasync function main(sourceType) {\n  const gumStream = await getUserMediaStream();\n  gumTrack = gumStream.getVideoTracks()[0];\n  const gumProcessor = new MediaStreamTrackProcessor({track: gumTrack});\n\n  gumVideo = document.getElementById('gumInputVideo');\n  gumVideo.srcObject = gumStream;\n  gumVideo.play();\n\n  const videoStream = await getMediaStream('../../../video/chrome.webm');\n  const videoTrack = videoStream.getVideoTracks()[0];\n  const videoProcessor = new MediaStreamTrackProcessor({track: videoTrack});\n\n  if (sourceType === 'main') {\n    gpuTransform = new WebGPUTransform();\n  }\n  if (sourceType === 'worker') {\n    gpuTransform = new WebGPUWorker();\n  }\n  await gpuTransform.init();\n  await gpuTransform.transform(videoProcessor.readable, gumProcessor.readable);\n}\n\nfunction destroy_source() {\n  if (videoElement) {\n    console.log('Stopping source video');\n    videoElement.pause();\n  }\n  if (gumVideo) {\n    console.log('Stopping gUM stream');\n    gumVideo.pause();\n    gumVideo.srcObject = null;\n  }\n  if (gumTrack) gumTrack.stop();\n}\n\nconst sourceSelector = document.getElementById('sourceSelector');\n\nfunction updateSource() {\n  if (gpuTransform) {\n    gpuTransform.destroy();\n  }\n  gpuTransform = null;\n  destroy_source();\n  const sourceType = sourceSelector.options[sourceSelector.selectedIndex].value;\n\n  console.log('New source is', sourceType);\n  if (sourceType !== 'stopped') {\n    main(sourceType);\n  }\n}\n\nsourceSelector.oninput = updateSource;\n"
  },
  {
    "path": "src/content/insertable-streams/webgpu/js/multi_video_main.js",
    "content": "\n'use strict';\n\nconst wgslShaders = {\n  vertex: `\nstruct VertexInput {\n  [[location(0)]] position : vec3<f32>;\n  [[location(1)]] uv : vec2<f32>;\n};\n\nstruct VertexOutput {\n  [[builtin(position)]] Position : vec4<f32>;\n  [[location(0)]] fragUV : vec2<f32>;\n};\n\n[[stage(vertex)]]\nfn main(input : VertexInput) -> VertexOutput {\n    var output : VertexOutput;\n    output.Position = vec4<f32>(input.position, 1.0);\n    output.fragUV = vec2<f32>(-0.5,-0.0) + input.uv;\n    return output;\n}\n`,\n\n  fragment: `\n[[binding(0), group(0)]] var mySampler: sampler;\n[[binding(1), group(0)]] var myTexture: texture_2d<f32>;\n\n[[stage(fragment)]]\nfn main([[location(0)]] fragUV : vec2<f32>) -> [[location(0)]] vec4<f32> {\n  return textureSample(myTexture, mySampler, fragUV);\n}\n`,\n};\n\nclass WebGPUTransform { // eslint-disable-line no-unused-vars\n  constructor() {\n    this.canvas_ = null;\n    this.context_ = null;\n    this.device_ = null;\n    this.renderPipeline_ = null;\n    this.sampler_ = null;\n    this.videoTexture_ = null;\n    this.vertexBuffer_ = null;\n  }\n\n  async init(inputCanvas) {\n    console.log('[WebGPUTransform] Initializing WebGPU.');\n    this.canvas_ = inputCanvas;\n    let errorElement;\n    if (!this.canvas_) {\n      this.canvas_ = document.createElement('canvas');\n      document.getElementById('outputVideo').append(this.canvas_);\n      this.canvas_.width = 960;\n      this.canvas_.height = 540;\n      errorElement = document.getElementById('errorMsg');\n    }\n\n    const canvas = this.canvas_;\n    const context = canvas.getContext('webgpu');\n    if (!context) {\n      const errorMessage = 'Your browser does not support the WebGPU API.' +\n                ' Please see the note at the bottom of the page.';\n      if (errorElement) errorElement.innerText = errorMessage;\n      return errorMessage;\n    }\n    this.context_ = context;\n    const adapter = await navigator.gpu.requestAdapter();\n    const device = await adapter.requestDevice();\n    this.device_ = device;\n    if (!this.device_) {\n      console.log('[WebGPUTransform] requestDevice failed.');\n      return;\n    }\n    const swapChainFormat = 'bgra8unorm';\n\n    const rectVerts = new Float32Array([\n      1.0, 1.0, 0.0, 1.0, 0.0,\n      1.0, -1.0, 0.0, 1.0, 1.0,\n      -1.0, -1.0, 0.0, 0.0, 1.0,\n      1.0, 1.0, 0.0, 1.0, 0.0,\n      -1.0, -1.0, 0.0, 0.0, 1.0,\n      -1.0, 1.0, 0.0, 0.0, 0.0,\n    ]);\n    // Creates a GPU buffer.\n    const vertexBuffer = device.createBuffer({\n      size: rectVerts.byteLength,\n      usage: GPUBufferUsage.VERTEX,\n      mappedAtCreation: true,\n    });\n    // Copies rectVerts to vertexBuffer\n    new Float32Array(vertexBuffer.getMappedRange()).set(rectVerts);\n    vertexBuffer.unmap();\n    this.vertexBuffer_ = vertexBuffer;\n\n    context.configure({\n      device,\n      format: swapChainFormat\n    });\n\n    this.renderPipeline_ = device.createRenderPipeline({\n      vertex: {\n        module: device.createShaderModule({\n          code: wgslShaders.vertex,\n        }),\n        entryPoint: 'main',\n        buffers: [\n          {\n            arrayStride: 20,\n            attributes: [\n              {\n                // position\n                shaderLocation: 0,\n                offset: 0,\n                format: 'float32x3',\n              },\n              {\n                // uv\n                shaderLocation: 1,\n                offset: 12,\n                format: 'float32x2',\n              },\n            ],\n          },\n        ],\n      },\n      fragment: {\n        module: device.createShaderModule({\n          code: wgslShaders.fragment,\n        }),\n        entryPoint: 'main',\n        targets: [\n          {\n            format: swapChainFormat,\n          },\n        ],\n      },\n      primitive: {\n        topology: 'triangle-list',\n      },\n    });\n\n    this.videoTexture_ = device.createTexture({\n      size: [480 * 2, 270 * 2],\n      format: 'rgba8unorm',\n      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING |\n                GPUTextureUsage.RENDER_ATTACHMENT,\n    });\n\n    this.sampler_ = device.createSampler({\n      addressModeU: 'repeat',\n      addressModeV: 'repeat',\n      addressModeW: 'repeat',\n      magFilter: 'linear',\n      minFilter: 'linear',\n    });\n  }\n\n  async copyOnTexture(device, videoTexture, frame, xcorr, ycorr) {\n    if (!frame) {\n      return;\n    }\n    // Using GPUExternalTexture(when it's implemented for Breakout Box frames) will\n    // avoid making extra copies through ImageBitmap.\n    const videoBitmap = await createImageBitmap(frame, {resizeWidth: 480, resizeHeight: 270});\n    device.queue.copyExternalImageToTexture(\n        {source: videoBitmap, origin: {x: 0, y: 0}},\n        {texture: videoTexture, origin: {x: xcorr, y: ycorr}},\n        {\n          // the width of the image being copied\n          width: videoBitmap.width,\n          height: videoBitmap.height,\n        }\n    );\n    videoBitmap.close();\n    frame.close();\n  }\n\n  async renderOnScreen(videoSource, gumSource) {\n    const device = this.device_;\n    const videoTexture = this.videoTexture_;\n    if (!device) {\n      console.log('[WebGPUTransform] device is undefined or null.');\n      return false;\n    }\n\n    const videoPromise = videoSource.read().then(({value}) => {\n      this.copyOnTexture(device, videoTexture, value, 0, 270);\n    });\n    const gumPromise = gumSource.read().then(({value}) => {\n      this.copyOnTexture(device, videoTexture, value, 480, 0);\n    });\n    await Promise.all([videoPromise, gumPromise]);\n\n    if (!this.device_) {\n      console.log('Check if destroy has been called asynchronously.');\n      return false;\n    }\n\n    const uniformBindGroup = device.createBindGroup({\n      layout: this.renderPipeline_.getBindGroupLayout(0),\n      entries: [\n        {\n          binding: 0,\n          resource: this.sampler_,\n        },\n        {\n          binding: 1,\n          resource: videoTexture.createView(),\n        },\n      ],\n    });\n\n    const commandEncoder = device.createCommandEncoder();\n    const textureView = this.context_.getCurrentTexture().createView();\n\n    const renderPassDescriptor = {\n      colorAttachments: [\n        {\n          view: textureView,\n          loadValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},\n          storeOp: 'store',\n        },\n      ],\n    };\n    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);\n    passEncoder.setPipeline(this.renderPipeline_);\n    passEncoder.setVertexBuffer(0, this.vertexBuffer_);\n    passEncoder.setBindGroup(0, uniformBindGroup);\n    passEncoder.draw(6, 1, 0, 0);\n    passEncoder.endPass();\n    device.queue.submit([commandEncoder.finish()]);\n    return true;\n  }\n\n\n  async transform(videoStream, gumStream) {\n    const videoSource = videoStream.getReader();\n    const gumSource = gumStream.getReader();\n    while (true) {\n      const rendered = await this.renderOnScreen(videoSource, gumSource);\n      if (!rendered) {\n        break;\n      }\n    }\n    videoSource.cancel();\n    gumSource.cancel();\n  }\n\n  destroy() {\n    if (this.device_) {\n      // Currently being implemented.\n      // await this.device_.destroy();\n      this.device_ = null;\n      this.vertexBuffer_.destroy();\n      this.videoTexture_.destroy();\n      if (this.canvas_.parentNode) {\n        this.canvas_.parentNode.removeChild(this.canvas_);\n      }\n      console.log('[WebGPUTransform] Context destroyed.',);\n    }\n  }\n}\n"
  },
  {
    "path": "src/content/insertable-streams/webgpu/js/multi_video_worker.js",
    "content": "importScripts('./multi_video_main.js');\n'use strict';\n\nlet mainTransform = null;\n\n/* global WebGPUTransform */ // defined in multi_video_main.js\n\nonmessage = async (event) => {\n  const {operation} = event.data;\n  if (operation === 'init') {\n    mainTransform = new WebGPUTransform();\n    const {canvas} = event.data;\n    const msg = await mainTransform.init(canvas);\n    if (msg) {\n      postMessage({error: msg});\n    } else {\n      postMessage({result: 'Done'});\n    }\n  } else if (operation === 'transform') {\n    const {videoStream, gumStream} = event.data;\n    mainTransform.transform(videoStream, gumStream);\n  }\n};\n"
  },
  {
    "path": "src/content/insertable-streams/webgpu/js/multi_video_worker_manager.js",
    "content": "\n'use strict';\n\nlet worker;\nlet screenCanvas;\n\n// eslint-disable-next-line no-unused-vars\nclass WebGPUWorker {\n  async init() {\n    screenCanvas = document.createElement('canvas');\n    document.getElementById('outputVideo').append(screenCanvas);\n    screenCanvas.width = 960;\n    screenCanvas.height = 540;\n\n    worker = new Worker('./js/multi_video_worker.js');\n    console.log('Created a worker thread.');\n    const offScreen = screenCanvas.transferControlToOffscreen();\n\n    const onMessage = new Promise((resolve, reject) => {\n      worker.addEventListener('message', function handleMsgFromWorker(msg) {\n        if (msg.data.error) {\n          document.getElementById('errorMsg').innerText = msg.data.error;\n          reject(msg.data.error);\n        }\n        if (msg.data.result === 'Done') {\n          resolve();\n        }\n      });\n    });\n    worker.postMessage(\n        {\n          operation: 'init',\n          canvas: offScreen,\n        }, [offScreen]);\n\n    await onMessage;\n  }\n\n  transform(videoStream, gumStream) {\n    if (videoStream && gumStream) {\n      worker.postMessage(\n          {\n            operation: 'transform',\n            videoStream: videoStream,\n            gumStream: gumStream,\n          }, [videoStream, gumStream]);\n    }\n  }\n\n  destroy() {\n    if (screenCanvas.parentNode) {\n      screenCanvas.parentNode.removeChild(screenCanvas);\n    }\n    worker.terminate();\n    console.log('Worker thread destroyed.');\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/always-negotiate-datachannels/css/main.css",
    "content": "/*\n *  Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 90px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/always-negotiate-datachannels/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection - Always negotiate datachannels</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div>\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\" disabled>Call</button>\n        <button id=\"renegotiateButton\" disabled>Renegotiate</button>\n        <button id=\"hangupButton\" disabled>Hang Up</button>\n        <input type=\"checkbox\" id=\"negotiateDataChannel\"disabled><label for=\"negotiateDataChannel\">Always negotiate datachannel</label>\n    </div>\n\n    <p id=\"log\">\n    </p>\n    <p>\n      Data channels are not used in this example but negotiating them in the SDP (using the checkbox in supported browsers)\n      avoids video freezes (on the right video) if the <a href=\"https://w3c.github.io/webrtc-extensions/#always-negotiating-datachannels\" target=_blank>alwaysNegotiateDataChannels configuration option</a> is used.\n    </p>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as\n        well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/always-negotiate-datachannels\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/always-negotiate-datachannels/js/main.js",
    "content": "/*\n *  Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst renegotiateButton = document.getElementById('renegotiateButton');\nconst hangupButton = document.getElementById('hangupButton');\nconst negotiateCheckbox = document.getElementById('negotiateDataChannel');\nstartButton.onclick = start;\ncallButton.onclick = call;\nrenegotiateButton.onclick = renegotiate;\nhangupButton.onclick = hangup;\n\nlet localStream;\nlet pc1;\nlet pc2;\n\nfunction log(text) {\n  document.getElementById('log').innerText += text + '\\n';\n}\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function start() {\n  startButton.disabled = true;\n  negotiateCheckbox.disabled = true;\n  const stream = await navigator.mediaDevices.getUserMedia({\n    audio: false,\n    video: true,\n  });\n  localVideo.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n  negotiateCheckbox.disabled = false;\n}\n\nasync function call() {\n  callButton.disabled = true;\n  negotiateCheckbox.disabled = true;\n  renegotiateButton.disabled = false;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  const audioTracks = localStream.getAudioTracks();\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const config = {\n    alwaysNegotiateDataChannels: negotiateCheckbox.checked,\n  };\n  pc1 = new RTCPeerConnection(config);\n  // Use pc.getConfiguration to detect whether the feature is supported.\n  if (pc1.getConfiguration().alwaysNegotiateDataChannels === undefined) {\n    log('RTCConfiguration.alwaysNegotiateDataChannel is not supported in this browser (' + adapter.browserDetails.browser + '/' + adapter.browserDetails.version + ')');\n  } else {\n    log('RTCConfiguration.alwaysNegotiateDataChannel is supported in this browser (' + adapter.browserDetails.browser + '/' + adapter.browserDetails.version + ')');\n  }\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection();\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  // Create an audio transceiver which serves as transport.\n  pc1.addTransceiver('audio');\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n\n  await pc1.setLocalDescription();\n  await pc2.setRemoteDescription(pc1.localDescription);\n  await pc2.setLocalDescription();\n  await pc1.setRemoteDescription(pc2.localDescription);\n  renegotiateButton.disabled = false;\n}\n\nfunction gotRemoteStream(e) {\n  remoteVideo.srcObject = e.streams[0];\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .catch(err => onAddIceCandidateError(pc, err));\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nasync function renegotiate() {\n  renegotiateButton.disabled = true;\n  hangupButton.disabled = true;\n  pc1.getTransceivers()[0].stop(); // stops the first transceiver.\n  if (negotiateCheckbox.checked) {\n    log('Stopped transceiver, the right video should not freeze (if always negotiating data channels is supported)');\n  } else {\n    log('Stopped transceiver, the right video will freeze for ~4 seconds');\n  }\n  await pc1.setLocalDescription();\n  await pc2.setRemoteDescription(pc1.localDescription);\n  await new Promise(r => setTimeout(r, 4000)); // wait 4 seconds.\n  console.log('renegotiated');\n  await pc2.setLocalDescription();\n  await pc1.setRemoteDescription(pc2.localDescription);\n  await new Promise(r => setTimeout(r, 1000)); // wait 1 seconds.\n  const stats = await pc2.getStats();\n  const freezes = [...stats.values()].filter(s => s.type === 'inbound-rtp' && s.kind === 'video').map(s => s.totalFreezesDuration);\n  log('Renegotiation done, the getStats() API detected a freeze lasting ' + freezes + ' seconds');\n  log('Flip the \"always negotiate data channels\" box and try again');\n  hangupButton.disabled = false;\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n  negotiateCheckbox.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/audio/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\naudio {\n  display: inline-block;\n  position: relative;\n  top: 9px;\n  width: calc(100% - 120px);\n}\n\nbutton {\n  margin: 0 20px 0 0;\n  width: 96px;\n}\n\ntable {\n  border-collapse: collapse;\n}\n\nth, td {\n  border: 1px solid black;\n}\n\ntr:hover {\n  background-color: #f5f5f5;\n}\n\ndiv#audio {\n  margin: 0 0 29px 0;\n}\n\ndiv#audio > div {\n  margin: 0 0 20px 0;\n}\n\ndiv.label {\n  display: inline-block;\n  font-weight: 400;\n  width: 120px;\n}\n\ndiv.graph-container {\n  float: left;\n  margin: 0.5em;\n  width: calc(50% - 1em);\n}\n\na#viewSource {\n  clear: both;\n}\n"
  },
  {
    "path": "src/content/peerconnection/audio/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection: audio only</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link href=\"https://fonts.googleapis.com/css?family=Inconsolata\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection: audio only</span>\n    </h1>\n\n    <div id=\"audio\">\n        <div>\n            <div class=\"label\">Local audio:</div>\n            <audio id=\"audio1\" autoplay controls muted></audio>\n        </div>\n        <div>\n            <div class=\"label\">Remote audio:</div>\n            <audio id=\"audio2\" autoplay controls></audio>\n        </div>\n    </div>\n\n    <div id=\"buttons\">\n        <select id=\"codec\">\n            <!-- Codec values are matched with how they appear in the SDP.\n            For instance, opus matches opus/48000/2 in Chrome, and ISAC/16000\n            matches 16K iSAC (but not 32K iSAC). -->\n            <option value=\"opus\">Opus</option>\n            <option value=\"ISAC\">iSAC 16K</option>\n            <option value=\"G722\">G722</option>\n            <option value=\"PCMU\">PCMU</option>\n            <option value=\"red\">RED</option>\n        </select>\n        <select id=\"codecPreferences\" disabled>\n            <option selected value=\"\">Default</option>\n        </select>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n    <div class=\"graph-container\" id=\"bitrateGraph\">\n        <div>Bitrate</div>\n        <canvas id=\"bitrateCanvas\"></canvas>\n    </div>\n    <div class=\"graph-container\" id=\"packetGraph\">\n        <div>Packets sent per second</div>\n        <canvas id=\"packetCanvas\"></canvas>\n    </div>\n    <div class=\"graph-container\" id=\"audioLevelGraph\">\n        <div>average audio level ([0..1])</div>\n        <canvas id=\"audioLevelCanvas\"></canvas>\n    </div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/audio\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n<table>\n    <caption>Bitrate and Packes sent per second - approximate results in browsers</caption>\n    <tr>\n        <th>Opus</th>\n        <th>iSAC 16K</th>\n        <th>G722</th>\n        <th>PCMU</th>\n        <th>Browsers Tested</th>\n    </tr>\n    <tr>\n        <td>~40 kbps / Muted : Same, ~50 Packets, Muted : Same or slight drop</td>\n        <td>~30 kbps / Muted : Same, ~33 Packets, Muted : Same or slight drop</td>\n        <td>~70 kbps / Muted : Same, ~50 Packets, Muted : Same</td>\n        <td>~70 kbps / Muted : Same, ~55 Packets, Muted : Same</td>\n        <td>Tested in Chrome, Not tested in Opera, Firefox, Safari, Edge</td>\n    </tr>\n</table>\n<hr>\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n<script src=\"../../../js/third_party/graph.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/audio/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* global TimelineDataSeries, TimelineGraphView */\n\n'use strict';\n\nconst audio2 = document.querySelector('audio#audio2');\nconst callButton = document.querySelector('button#callButton');\nconst hangupButton = document.querySelector('button#hangupButton');\nconst codecSelector = document.querySelector('select#codec');\nhangupButton.disabled = true;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\nlet pc1;\nlet pc2;\nlet localStream;\n\nlet bitrateGraph;\nlet bitrateSeries;\nlet targetBitrateSeries;\nlet headerrateSeries;\n\nlet packetGraph;\nlet packetSeries;\n\nlet lastResult;\n\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 0,\n  voiceActivityDetection: false\n};\n\nconst audioLevels = [];\nlet audioLevelGraph;\nlet audioLevelSeries;\n\n// Enabling opus DTX is an expert option without GUI.\n// eslint-disable-next-line prefer-const\nlet useDtx = false;\n\n// Disabling Opus FEC is an expert option without GUI.\n// eslint-disable-next-line prefer-const\nlet useFec = true;\n\n// We only show one way of doing this.\nconst codecPreferences = document.querySelector('#codecPreferences');\nconst supportsSetCodecPreferences = window.RTCRtpTransceiver &&\n  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;\nif (supportsSetCodecPreferences) {\n  codecSelector.style.display = 'none';\n\n  const {codecs} = RTCRtpReceiver.getCapabilities('audio');\n  codecs.forEach(codec => {\n    if (['audio/CN', 'audio/telephone-event'].includes(codec.mimeType)) {\n      return;\n    }\n    const option = document.createElement('option');\n    option.value = (codec.mimeType + ' ' + codec.clockRate + ' ' +\n      (codec.sdpFmtpLine || '')).trim();\n    option.innerText = option.value;\n    codecPreferences.appendChild(option);\n  });\n  codecPreferences.disabled = false;\n} else {\n  codecPreferences.style.display = 'none';\n}\n\n// Change the ptime. For opus supported values are [10, 20, 40, 60].\n// Expert option without GUI.\n// eslint-disable-next-line no-unused-vars\nasync function setPtime(ptime) {\n  const offer = await pc1.createOffer();\n  await pc1.setLocalDescription(offer);\n  const desc = pc1.remoteDescription;\n  if (desc.sdp.indexOf('a=ptime:') !== -1) {\n    desc.sdp = desc.sdp.replace(/a=ptime:.*/, 'a=ptime:' + ptime);\n  } else {\n    desc.sdp += 'a=ptime:' + ptime + '\\r\\n';\n  }\n  await pc1.setRemoteDescription(desc);\n}\n\nfunction gotStream(stream) {\n  hangupButton.disabled = false;\n  console.log('Received local stream');\n  localStream = stream;\n  const audioTracks = localStream.getAudioTracks();\n  if (audioTracks.length > 0) {\n    console.log(`Using Audio device: ${audioTracks[0].label}`);\n  }\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n\n  pc1.createOffer(offerOptions)\n      .then(gotDescription1, onCreateSessionDescriptionError);\n\n  bitrateSeries = new TimelineDataSeries();\n  bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');\n  bitrateGraph.updateEndDate();\n\n  targetBitrateSeries = new TimelineDataSeries();\n  targetBitrateSeries.setColor('blue');\n\n  headerrateSeries = new TimelineDataSeries();\n  headerrateSeries.setColor('green');\n\n  packetSeries = new TimelineDataSeries();\n  packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');\n  packetGraph.updateEndDate();\n\n  audioLevelSeries = new TimelineDataSeries();\n  audioLevelGraph = new TimelineGraphView('audioLevelGraph', 'audioLevelCanvas');\n  audioLevelGraph.updateEndDate();\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction call() {\n  callButton.disabled = true;\n  codecSelector.disabled = true;\n  console.log('Starting call');\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n  console.log('Requesting local stream');\n  navigator.mediaDevices\n      .getUserMedia({\n        audio: true,\n        video: false\n      })\n      .then(gotStream)\n      .catch(e => {\n        alert(`getUserMedia() error: ${e.name}`);\n      });\n}\n\nfunction gotDescription1(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  pc1.setLocalDescription(desc)\n      .then(() => {\n        if (!supportsSetCodecPreferences) {\n          desc.sdp = forceChosenAudioCodec(desc.sdp);\n        }\n        pc2.setRemoteDescription(desc).then(() => {\n          return pc2.createAnswer().then(gotDescription2, onCreateSessionDescriptionError);\n        }, onSetSessionDescriptionError);\n      }, onSetSessionDescriptionError);\n}\n\nfunction gotDescription2(desc) {\n  console.log(`Answer from pc2\\n${desc.sdp}`);\n  pc2.setLocalDescription(desc).then(() => {\n    if (!supportsSetCodecPreferences) {\n      desc.sdp = forceChosenAudioCodec(desc.sdp);\n    }\n    if (useDtx) {\n      desc.sdp = desc.sdp.replace('useinbandfec=1', 'useinbandfec=1;usedtx=1');\n    }\n    if (!useFec) {\n      desc.sdp = desc.sdp.replace('useinbandfec=1', 'useinbandfec=0');\n    }\n    pc1.setRemoteDescription(desc).then(() => {}, onSetSessionDescriptionError);\n  }, onSetSessionDescriptionError);\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  localStream.getTracks().forEach(track => track.stop());\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n  codecSelector.disabled = false;\n}\n\nfunction gotRemoteStream(e) {\n  if (supportsSetCodecPreferences) {\n    const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex];\n    if (preferredCodec.value !== '') {\n      const [mimeType, clockRate, sdpFmtpLine] = preferredCodec.value.split(' ');\n      const {codecs} = RTCRtpReceiver.getCapabilities('audio');\n      console.log(mimeType, clockRate, sdpFmtpLine);\n      console.log(JSON.stringify(codecs, null, ' '));\n      const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.clockRate === parseInt(clockRate, 10) && c.sdpFmtpLine === sdpFmtpLine);\n      const selectedCodec = codecs[selectedCodecIndex];\n      codecs.splice(selectedCodecIndex, 1);\n      codecs.unshift(selectedCodec);\n      e.transceiver.setCodecPreferences(codecs);\n      console.log('Preferred video codec', selectedCodec);\n    }\n  }\n\n  if (audio2.srcObject !== e.streams[0]) {\n    audio2.srcObject = e.streams[0];\n    console.log('Received remote stream');\n  }\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc).addIceCandidate(event.candidate)\n      .then(\n          () => onAddIceCandidateSuccess(pc),\n          err => onAddIceCandidateError(pc, err)\n      );\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log(`Failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction forceChosenAudioCodec(sdp) {\n  return maybePreferCodec(sdp, 'audio', 'send', codecSelector.value);\n}\n\n// Copied from AppRTC's sdputils.js:\n\n// Sets |codec| as the default |type| codec if it's present.\n// The format of |codec| is 'NAME/RATE', e.g. 'opus/48000'.\nfunction maybePreferCodec(sdp, type, dir, codec) {\n  const str = `${type} ${dir} codec`;\n  if (codec === '') {\n    console.log(`No preference on ${str}.`);\n    return sdp;\n  }\n\n  console.log(`Prefer ${str}: ${codec}`);\n\n  const sdpLines = sdp.split('\\r\\n');\n\n  // Search for m line.\n  const mLineIndex = findLine(sdpLines, 'm=', type);\n  if (mLineIndex === null) {\n    return sdp;\n  }\n\n  // If the codec is available, set it as the default in m line.\n  const codecIndex = findLine(sdpLines, 'a=rtpmap', codec);\n  console.log('codecIndex', codecIndex);\n  if (codecIndex) {\n    const payload = getCodecPayloadType(sdpLines[codecIndex]);\n    if (payload) {\n      sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], payload);\n    }\n  }\n\n  sdp = sdpLines.join('\\r\\n');\n  return sdp;\n}\n\n// Find the line in sdpLines that starts with |prefix|, and, if specified,\n// contains |substr| (case-insensitive search).\nfunction findLine(sdpLines, prefix, substr) {\n  return findLineInRange(sdpLines, 0, -1, prefix, substr);\n}\n\n// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|\n// and, if specified, contains |substr| (case-insensitive search).\nfunction findLineInRange(sdpLines, startLine, endLine, prefix, substr) {\n  const realEndLine = endLine !== -1 ? endLine : sdpLines.length;\n  for (let i = startLine; i < realEndLine; ++i) {\n    if (sdpLines[i].indexOf(prefix) === 0) {\n      if (!substr ||\n        sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {\n        return i;\n      }\n    }\n  }\n  return null;\n}\n\n// Gets the codec payload type from an a=rtpmap:X line.\nfunction getCodecPayloadType(sdpLine) {\n  const pattern = new RegExp('a=rtpmap:(\\\\d+) \\\\w+\\\\/\\\\d+');\n  const result = sdpLine.match(pattern);\n  return (result && result.length === 2) ? result[1] : null;\n}\n\n// Returns a new m= line with the specified codec as the first one.\nfunction setDefaultCodec(mLine, payload) {\n  const elements = mLine.split(' ');\n\n  // Just copy the first three parameters; codec order starts on fourth.\n  const newLine = elements.slice(0, 3);\n\n  // Put target payload first and copy in the rest.\n  newLine.push(payload);\n  for (let i = 3; i < elements.length; i++) {\n    if (elements[i] !== payload) {\n      newLine.push(elements[i]);\n    }\n  }\n  return newLine.join(' ');\n}\n\n// query getStats every second\nwindow.setInterval(() => {\n  if (!pc1) {\n    return;\n  }\n  const sender = pc1.getSenders()[0];\n  if (!sender) {\n    return;\n  }\n  sender.getStats().then(res => {\n    res.forEach(report => {\n      let bytes;\n      let headerBytes;\n      let packets;\n      if (report.type === 'outbound-rtp') {\n        if (report.isRemote) {\n          return;\n        }\n        const now = report.timestamp;\n        bytes = report.bytesSent;\n        headerBytes = report.headerBytesSent;\n\n        packets = report.packetsSent;\n        if (lastResult && lastResult.has(report.id)) {\n          const deltaT = (now - lastResult.get(report.id).timestamp) / 1000;\n          // calculate bitrate\n          const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /\n            deltaT;\n          const headerrate = 8 * (headerBytes - lastResult.get(report.id).headerBytesSent) /\n            deltaT;\n\n          // append to chart\n          bitrateSeries.addPoint(now, bitrate);\n          headerrateSeries.addPoint(now, headerrate);\n          targetBitrateSeries.addPoint(now, report.targetBitrate);\n          bitrateGraph.setDataSeries([bitrateSeries, headerrateSeries, targetBitrateSeries]);\n          bitrateGraph.updateEndDate();\n\n          // calculate number of packets and append to chart\n          packetSeries.addPoint(now, (packets -\n            lastResult.get(report.id).packetsSent) / deltaT);\n          packetGraph.setDataSeries([packetSeries]);\n          packetGraph.updateEndDate();\n        }\n      }\n    });\n    lastResult = res;\n  });\n}, 1000);\n\nif (window.RTCRtpReceiver && ('getSynchronizationSources' in window.RTCRtpReceiver.prototype)) {\n  let lastTime;\n  const getAudioLevel = (timestamp) => {\n    window.requestAnimationFrame(getAudioLevel);\n    if (!pc2) {\n      return;\n    }\n    const receiver = pc2.getReceivers().find(r => r.track.kind === 'audio');\n    if (!receiver) {\n      return;\n    }\n    const sources = receiver.getSynchronizationSources();\n    sources.forEach(source => {\n      audioLevels.push(source.audioLevel);\n    });\n    if (!lastTime) {\n      lastTime = timestamp;\n    } else if (timestamp - lastTime > 500 && audioLevels.length > 0) {\n      // Update graph every 500ms.\n      const maxAudioLevel = Math.max.apply(null, audioLevels);\n      audioLevelSeries.addPoint(Date.now(), maxAudioLevel);\n      audioLevelGraph.setDataSeries([audioLevelSeries]);\n      audioLevelGraph.updateEndDate();\n      audioLevels.length = 0;\n      lastTime = timestamp;\n    }\n  };\n  window.requestAnimationFrame(getAudioLevel);\n}\n"
  },
  {
    "path": "src/content/peerconnection/audio/js/test.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/audio/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('audio-only peerconnection', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection', async () => {\n    await driver.findElement(webdriver.By.id('callButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.executeScript(() => {\n      return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('audio2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n  });\n\n  // TODO: assert usage of different codecs via getStats.\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/bandwidth/css/main.css",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n\nbutton {\n  margin: 0 20px 0 0;\n  width: 96px;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.label {\n  display: inline-block;\n  font-weight: 400;\n  width: 120px;\n}\n\ndiv.graph-container {\n  float: left;\n  margin: 0.5em;\n  width: calc(50% - 1em);\n}\n\na#viewSource {\n  clear: both;\n}\n"
  },
  {
    "path": "src/content/peerconnection/bandwidth/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection: adjust bandwidth</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link href=\"https://fonts.googleapis.com/css?family=Inconsolata\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection: adjust bandwidth</span>\n    </h1>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div id=\"buttons\">\n        <label>Bandwidth:</label>\n        <select id=\"bandwidth\" disabled>\n            <option value=\"unlimited\" selected>unlimited</option>\n            <option value=\"2000\">2000</option>\n            <option value=\"1000\">1000</option>\n            <option value=\"500\">500</option>\n            <option value=\"250\">250</option>\n            <option value=\"125\">125</option>\n            <option value=\"75\">75</option>\n        </select>\n        kbps\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n        Use synthetic video: <input type=\"checkbox\" id=\"synthetic\">\n    </div>\n    <div class=\"graph-container\" id=\"bitrateGraph\">\n        <div>Bitrate</div>\n        <canvas id=\"bitrateCanvas\"></canvas>\n    </div>\n    <div class=\"graph-container\" id=\"packetGraph\">\n        <div>Packets sent per second</div>\n        <canvas id=\"packetCanvas\"></canvas>\n    </div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/bandwidth\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/third_party/graph.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/bandwidth/js/main.js",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* global TimelineDataSeries, TimelineGraphView */\n\n'use strict';\n\nconst remoteVideo = document.querySelector('video#remoteVideo');\nconst localVideo = document.querySelector('video#localVideo');\nconst callButton = document.querySelector('button#callButton');\nconst hangupButton = document.querySelector('button#hangupButton');\nconst bandwidthSelector = document.querySelector('select#bandwidth');\nconst synthetic = document.querySelector('input#synthetic');\nhangupButton.disabled = true;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\nlet pc1;\nlet pc2;\nlet localStream;\n\n// Can be set in the console before making a call to test this keeps\n// within the envelope set by the SDP. In kbps.\n// eslint-disable-next-line prefer-const\nlet maxBandwidth = 0;\n\nlet bitrateGraph;\nlet bitrateSeries;\nlet headerrateSeries;\n\nlet packetGraph;\nlet packetSeries;\n\nlet lastResult;\n\nlet lastRemoteStart = 0;\n\n// lastRemoteFullSizeDelay is designed to be picked up by a test script.\n// eslint-disable-next-line no-unused-vars\nlet lastRemoteFullSizeDelay = 0;\n\nconst offerOptions = {\n  offerToReceiveAudio: 0,\n  offerToReceiveVideo: 1\n};\n\nremoteVideo.addEventListener('resize', ev => {\n  const elapsed = performance.now() - lastRemoteStart;\n  console.log(elapsed, ': Resize event, size ',\n      remoteVideo.videoWidth, 'x', remoteVideo.videoHeight);\n  if (localVideo.videoWidth == remoteVideo.videoWidth &&\n      localVideo.videoHeight == remoteVideo.videoHeight) {\n    lastRemoteFullSizeDelay = elapsed;\n    console.log('Full size achieved');\n  }\n});\n\n\nfunction gotStream(stream) {\n  hangupButton.disabled = false;\n  console.log('Received local stream');\n  localStream = stream;\n  localVideo.srcObject = stream;\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n\n  pc1.createOffer(\n      offerOptions\n  ).then(\n      gotDescription1,\n      onCreateSessionDescriptionError\n  );\n\n  bitrateSeries = new TimelineDataSeries();\n  bitrateGraph = new TimelineGraphView('bitrateGraph', 'bitrateCanvas');\n  bitrateGraph.updateEndDate();\n\n  headerrateSeries = new TimelineDataSeries();\n  headerrateSeries.setColor('green');\n\n  packetSeries = new TimelineDataSeries();\n  packetGraph = new TimelineGraphView('packetGraph', 'packetCanvas');\n  packetGraph.updateEndDate();\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log('Failed to create session description: ' + error.toString());\n}\n\nfunction call() {\n  callButton.disabled = true;\n  bandwidthSelector.disabled = false;\n  console.log('Starting call');\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = onIceCandidate.bind(pc1);\n\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = onIceCandidate.bind(pc2);\n  pc2.ontrack = gotRemoteStream;\n\n  if (synthetic.checked) {\n    console.log('Requesting synthetic local stream');\n    gotStream(syntheticVideoStream());\n  } else {\n    console.log('Requesting live local stream');\n    navigator.mediaDevices.getUserMedia({video: true})\n        .then(gotStream)\n        .catch(e => alert('getUserMedia() error: ' + e.name));\n  }\n}\n\nfunction gotDescription1(desc) {\n  console.log('Offer from pc1 \\n' + desc.sdp);\n  pc1.setLocalDescription(desc).then(\n      () => {\n        pc2.setRemoteDescription(desc)\n            .then(() => pc2.createAnswer().then(gotDescription2, onCreateSessionDescriptionError),\n                onSetSessionDescriptionError);\n      }, onSetSessionDescriptionError\n  );\n}\n\nfunction gotDescription2(desc) {\n  pc2.setLocalDescription(desc).then(\n      () => {\n        console.log('Answer from pc2 \\n' + desc.sdp);\n        let p;\n        if (maxBandwidth) {\n          p = pc1.setRemoteDescription({\n            type: desc.type,\n            sdp: updateBandwidthRestriction(desc.sdp, maxBandwidth)\n          });\n        } else {\n          p = pc1.setRemoteDescription(desc);\n        }\n        p.then(() => {}, onSetSessionDescriptionError);\n      },\n      onSetSessionDescriptionError\n  );\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  localStream.getTracks().forEach(track => track.stop());\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n  bandwidthSelector.disabled = true;\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('Received remote stream');\n    lastRemoteStart = performance.now();\n    lastRemoteFullSizeDelay = 0;\n  }\n}\n\nfunction getOtherPc(pc) {\n  return pc === pc1 ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return pc === pc1 ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(event) {\n  getOtherPc(this)\n      .addIceCandidate(event.candidate)\n      .then(onAddIceCandidateSuccess)\n      .catch(onAddIceCandidateError);\n\n  console.log(`${getName(this)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log('Failed to add ICE Candidate: ' + error.toString());\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log('Failed to set session description: ' + error.toString());\n}\n\n// renegotiate bandwidth on the fly.\nbandwidthSelector.onchange = () => {\n  bandwidthSelector.disabled = true;\n  const bandwidth = bandwidthSelector.options[bandwidthSelector.selectedIndex].value;\n  setBandwidth(bandwidth)\n      .then(() => {\n        bandwidthSelector.disabled = false;\n      });\n};\n\nfunction setBandwidth(bandwidthInKbps) {\n  // In modern browsers, use RTCRtpSender.setParameters to change bandwidth without\n  // (local) renegotiation. Note that this will be within the envelope of\n  // the initial maximum bandwidth negotiated via SDP.\n  if ((adapter.browserDetails.browser === 'chrome' ||\n       adapter.browserDetails.browser === 'safari' ||\n       (adapter.browserDetails.browser === 'firefox' &&\n        adapter.browserDetails.version >= 64)) &&\n      'RTCRtpSender' in window &&\n      'setParameters' in window.RTCRtpSender.prototype) {\n    const sender = pc1.getSenders()[0];\n    const parameters = sender.getParameters();\n    if (!parameters.encodings) {\n      parameters.encodings = [{}];\n    }\n    if (bandwidthInKbps === 'unlimited') {\n      delete parameters.encodings[0].maxBitrate;\n    } else {\n      parameters.encodings[0].maxBitrate = bandwidthInKbps * 1000;\n    }\n    return sender.setParameters(parameters);\n  }\n  // Fallback to the SDP changes with local renegotiation as way of limiting\n  // the bandwidth.\n  return pc1.createOffer()\n      .then(offer => pc1.setLocalDescription(offer))\n      .then(() => {\n        const desc = {\n          type: pc1.remoteDescription.type,\n          sdp: bandwidthInKbps === 'unlimited' ?\n          removeBandwidthRestriction(pc1.remoteDescription.sdp) :\n          updateBandwidthRestriction(pc1.remoteDescription.sdp, bandwidthInKbps)\n        };\n        console.log('Applying bandwidth restriction to setRemoteDescription:\\n' +\n        desc.sdp);\n        return pc1.setRemoteDescription(desc);\n      })\n      .catch(onSetSessionDescriptionError);\n};\n\nfunction updateBandwidthRestriction(sdp, bandwidth) {\n  let modifier = 'AS';\n  if (adapter.browserDetails.browser === 'firefox') {\n    bandwidth = (bandwidth >>> 0) * 1000;\n    modifier = 'TIAS';\n  }\n  if (sdp.indexOf('b=' + modifier + ':') === -1) {\n    // insert b= after c= line.\n    sdp = sdp.replace(/c=IN (.*)\\r\\n/, 'c=IN $1\\r\\nb=' + modifier + ':' + bandwidth + '\\r\\n');\n  } else {\n    sdp = sdp.replace(new RegExp('b=' + modifier + ':.*\\r\\n'), 'b=' + modifier + ':' + bandwidth + '\\r\\n');\n  }\n  return sdp;\n}\n\nfunction removeBandwidthRestriction(sdp) {\n  return sdp.replace(/b=AS:.*\\r\\n/, '').replace(/b=TIAS:.*\\r\\n/, '');\n}\n\n// query getStats every second\nwindow.setInterval(() => {\n  if (!pc1) {\n    return;\n  }\n  const sender = pc1.getSenders()[0];\n  if (!sender) {\n    return;\n  }\n  sender.getStats().then(res => {\n    res.forEach(report => {\n      let bytes;\n      let headerBytes;\n      let packets;\n      if (report.type === 'outbound-rtp') {\n        if (report.isRemote) {\n          return;\n        }\n        const now = report.timestamp;\n        bytes = report.bytesSent;\n        headerBytes = report.headerBytesSent;\n\n        packets = report.packetsSent;\n        if (lastResult && lastResult.has(report.id)) {\n          // calculate bitrate\n          const bitrate = 8 * (bytes - lastResult.get(report.id).bytesSent) /\n            (now - lastResult.get(report.id).timestamp);\n          const headerrate = 8 * (headerBytes - lastResult.get(report.id).headerBytesSent) /\n            (now - lastResult.get(report.id).timestamp);\n\n          // append to chart\n          bitrateSeries.addPoint(now, bitrate);\n          headerrateSeries.addPoint(now, headerrate);\n          bitrateGraph.setDataSeries([bitrateSeries, headerrateSeries]);\n          bitrateGraph.updateEndDate();\n\n          // calculate number of packets and append to chart\n          packetSeries.addPoint(now, packets -\n            lastResult.get(report.id).packetsSent);\n          packetGraph.setDataSeries([packetSeries]);\n          packetGraph.updateEndDate();\n        }\n      }\n    });\n    lastResult = res;\n  });\n}, 1000);\n\n// Return a number between 0 and maxValue based on the input number,\n// so that the output changes smoothly up and down.\nfunction triangle(number, maxValue) {\n  const modulus = (maxValue + 1) * 2;\n  return Math.abs(number % modulus - maxValue);\n}\n\nfunction syntheticVideoStream({width = 640, height = 480, signal} = {}) {\n  const canvas = Object.assign(\n      document.createElement('canvas'), {width, height}\n  );\n  const ctx = canvas.getContext('2d');\n  const stream = canvas.captureStream();\n\n  let count = 0;\n  setInterval(() => {\n    // Use relatively-prime multipliers to get a color roll\n    const r = triangle(count*2, 255);\n    const g = triangle(count*3, 255);\n    const b = triangle(count*5, 255);\n    ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n    count += 1;\n    const boxSize=80;\n    ctx.fillRect(0, 0, width, height);\n    // Add some bouncing boxes in contrast color to add a little more noise.\n    const rContrast = (r + 128)%256;\n    const gContrast = (g + 128)%256;\n    const bContrast = (b + 128)%256;\n    ctx.fillStyle = `rgb(${rContrast}, ${gContrast}, ${bContrast})`;\n    const xpos = triangle(count*5, width - boxSize);\n    const ypos = triangle(count*7, height - boxSize);\n    ctx.fillRect(xpos, ypos, boxSize, boxSize);\n    const xpos2 = triangle(count*11, width - boxSize);\n    const ypos2 = triangle(count*13, height - boxSize);\n    ctx.fillRect(xpos2, ypos2, boxSize, boxSize);\n    // If signal is set (0-255), add a constant-color box of that luminance to\n    // the video frame at coordinates 20 to 60 in both X and Y direction.\n    // (big enough to avoid color bleed from surrounding video in some codecs,\n    // for more stable tests).\n    if (signal != undefined) {\n      ctx.fillStyle = `rgb(${signal}, ${signal}, ${signal})`;\n      ctx.fillRect(20, 20, 40, 40);\n    }\n  }, 100);\n  return stream;\n}\n"
  },
  {
    "path": "src/content/peerconnection/change-codecs/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/change-codecs/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <p>This sample shows how to setup a connection between two peers using\n        <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection</a> and\n        choose the preferred video codec to use (when that functionality is available.)\n    </p>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div class=\"box\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <div class=\"box\">\n        <span>Codec preferences:</span>\n        <select id=\"codecPreferences\" disabled>\n            <option selected value=\"\">Default</option>\n        </select>\n        <div id=\"actualCodec\"></div>\n    </div>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as\n        well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/change-codecs\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/change-codecs/js/main.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.addEventListener('click', start);\ncallButton.addEventListener('click', call);\nhangupButton.addEventListener('click', hangup);\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('resize', () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');\n    startTime = null;\n  }\n});\n\nconst codecPreferences = document.getElementById('codecPreferences');\nconst supportsSetCodecPreferences = window.RTCRtpTransceiver &&\n  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;\n\nlet localStream;\nlet pc1;\nlet pc2;\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia({video: true});\n    console.log('Received local stream');\n    localVideo.srcObject = stream;\n    localStream = stream;\n    callButton.disabled = false;\n  } catch (e) {\n    alert(`getUserMedia() error: ${e.name}`);\n  }\n  if (supportsSetCodecPreferences) {\n    const {codecs} = RTCRtpReceiver.getCapabilities('video');\n    codecs.forEach(codec => {\n      if (['video/red', 'video/ulpfec', 'video/rtx', 'video/flexfec-03'].includes(codec.mimeType)) {\n        return;\n      }\n      const option = document.createElement('option');\n      option.value = (codec.mimeType + ' ' + (codec.sdpFmtpLine || '')).trim();\n      option.innerText = option.value;\n      codecPreferences.appendChild(option);\n    });\n    codecPreferences.disabled = false;\n  }\n}\n\nasync function call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = localStream.getVideoTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  const configuration = {};\n  console.log('RTCPeerConnection configuration:', configuration);\n  pc1 = new RTCPeerConnection(configuration);\n  console.log('Created local peer connection object pc1');\n  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));\n  pc2 = new RTCPeerConnection(configuration);\n  console.log('Created remote peer connection object pc2');\n  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));\n  pc2.addEventListener('track', gotRemoteStream);\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Added local stream to pc1');\n  codecPreferences.disabled = true;\n\n  try {\n    console.log('pc1 createOffer start');\n    const offer = await pc1.createOffer();\n    await onCreateOfferSuccess(offer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nasync function onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  try {\n    await pc1.setLocalDescription(desc);\n    onSetLocalSuccess(pc1);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 setRemoteDescription start');\n  try {\n    await pc2.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 createAnswer start');\n  try {\n    const answer = await pc2.createAnswer();\n    await onCreateAnswerSuccess(answer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  // Set codec preferences on the receiving side.\n  if (e.track.kind === 'video' && supportsSetCodecPreferences) {\n    const preferredCodec = codecPreferences.options[codecPreferences.selectedIndex];\n    if (preferredCodec.value !== '') {\n      const [mimeType, sdpFmtpLine] = preferredCodec.value.split(' ');\n      const {codecs} = RTCRtpReceiver.getCapabilities('video');\n      const selectedCodecIndex = codecs.findIndex(c => c.mimeType === mimeType && c.sdpFmtpLine === sdpFmtpLine);\n      const selectedCodec = codecs[selectedCodecIndex];\n      codecs.splice(selectedCodecIndex, 1);\n      codecs.unshift(selectedCodec);\n      e.transceiver.setCodecPreferences(codecs);\n      console.log('Receiver\\'s preferred video codec', selectedCodec);\n    }\n  }\n\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('pc2 received remote stream');\n  }\n}\n\nasync function onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  try {\n    await pc2.setLocalDescription(desc);\n    onSetLocalSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n  console.log('pc1 setRemoteDescription start');\n  try {\n    await pc1.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc1);\n\n    // Display the video codec that is actually used.\n    setTimeout(async () => {\n      const stats = await pc1.getStats();\n      stats.forEach(stat => {\n        if (!(stat.type === 'outbound-rtp' && stat.kind === 'video')) {\n          return;\n        }\n        const codec = stats.get(stat.codecId);\n        document.getElementById('actualCodec').innerText = 'Using ' + codec.mimeType +\n            (codec.sdpFmtpLine ? ' ' + codec.sdpFmtpLine + ' ' : '') +\n            ', payloadType=' + codec.payloadType + '.';\n        if (stat.encoderImplementation) {\n          document.getElementById('actualCodec').innerText += ' Encoder: \"' + stat.encoderImplementation + '\".';\n        }\n        if (stat.powerEfficientEncoder !== undefined) {\n          document.getElementById('actualCodec').innerText += ' Power efficient: ' + stat.powerEfficientEncoder + '.';\n        }\n      });\n    }, 1000);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n}\n\nasync function onIceCandidate(pc, event) {\n  try {\n    await (getOtherPc(pc).addIceCandidate(event.candidate));\n    onAddIceCandidateSuccess(pc);\n  } catch (e) {\n    onAddIceCandidateError(pc, e);\n  }\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n  codecPreferences.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/change-codecs/js/test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/change-codecs/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection with setCodecPreferences', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  ['video/VP8'].forEach(codec => {\n    it('establishes a connection', async () => {\n      await driver.findElement(webdriver.By.id('startButton')).click();\n\n      await driver.wait(() => driver.executeScript(() => {\n        return localStream !== null; // eslint-disable-line no-undef\n      }));\n      await driver.wait(() => driver.executeScript(() => {\n        return codecPreferences.disabled === false; // eslint-disable-line no-undef\n      }));\n      await driver.findElement(webdriver.By.id('codecPreferences')).click();\n      await driver.findElement(webdriver.By.css('option[value=\\'video/VP8\\']')).click();\n\n      await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n      await driver.findElement(webdriver.By.id('callButton')).click();\n\n      await Promise.all([\n        await driver.wait(() => driver.executeScript(() => {\n          return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n        })),\n        await driver.wait(() => driver.executeScript(() => {\n          return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n        })),\n      ]);\n\n      await driver.wait(() => driver.executeScript(() => {\n        return document.getElementById('remoteVideo').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n      }));\n\n      await driver.wait(() => driver.executeScript(() => {\n        return document.getElementById('actualCodec').innerText !== '';\n      }));\n      const actualCodec = await driver.findElement(webdriver.By.id('actualCodec')).getAttribute('innerText');\n      expect(actualCodec.startsWith('Using ' + codec)).toBe(true);\n    });\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/channel/css/main.css",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/channel/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection between two tabs</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <p>This sample shows how to setup a connection between two peers in different tabs using\n        <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection</a>\n        and <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API\">Broadcast Channel</a>\n    </p>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div class=\"box\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <p>Click the start button in two tabs (of the same browser; can be in different windows) to make a call</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/channel\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/channel/js/main.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst hangupButton = document.getElementById('hangupButton');\nhangupButton.disabled = true;\n\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlet pc;\nlet localStream;\n\nconst signaling = new BroadcastChannel('webrtc');\nsignaling.onmessage = e => {\n  if (!localStream) {\n    console.log('not ready yet');\n    return;\n  }\n  switch (e.data.type) {\n    case 'offer':\n      handleOffer(e.data);\n      break;\n    case 'answer':\n      handleAnswer(e.data);\n      break;\n    case 'candidate':\n      handleCandidate(e.data);\n      break;\n    case 'ready':\n      // A second tab joined. This tab will initiate a call unless in a call already.\n      if (pc) {\n        console.log('already in call, ignoring');\n        return;\n      }\n      makeCall();\n      break;\n    case 'bye':\n      if (pc) {\n        hangup();\n      }\n      break;\n    default:\n      console.log('unhandled', e);\n      break;\n  }\n};\n\nstartButton.onclick = async () => {\n  localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});\n  localVideo.srcObject = localStream;\n\n\n  startButton.disabled = true;\n  hangupButton.disabled = false;\n\n  signaling.postMessage({type: 'ready'});\n};\n\nhangupButton.onclick = async () => {\n  hangup();\n  signaling.postMessage({type: 'bye'});\n};\n\nasync function hangup() {\n  if (pc) {\n    pc.close();\n    pc = null;\n  }\n  localStream.getTracks().forEach(track => track.stop());\n  localStream = null;\n  startButton.disabled = false;\n  hangupButton.disabled = true;\n};\n\nfunction createPeerConnection() {\n  pc = new RTCPeerConnection();\n  pc.onicecandidate = e => {\n    const message = {\n      type: 'candidate',\n      candidate: null,\n    };\n    if (e.candidate) {\n      message.candidate = e.candidate.candidate;\n      message.sdpMid = e.candidate.sdpMid;\n      message.sdpMLineIndex = e.candidate.sdpMLineIndex;\n    }\n    signaling.postMessage(message);\n  };\n  pc.ontrack = e => remoteVideo.srcObject = e.streams[0];\n  localStream.getTracks().forEach(track => pc.addTrack(track, localStream));\n}\n\nasync function makeCall() {\n  await createPeerConnection();\n\n  const offer = await pc.createOffer();\n  signaling.postMessage({type: 'offer', sdp: offer.sdp});\n  await pc.setLocalDescription(offer);\n}\n\nasync function handleOffer(offer) {\n  if (pc) {\n    console.error('existing peerconnection');\n    return;\n  }\n  await createPeerConnection();\n  await pc.setRemoteDescription(offer);\n\n  const answer = await pc.createAnswer();\n  signaling.postMessage({type: 'answer', sdp: answer.sdp});\n  await pc.setLocalDescription(answer);\n}\n\nasync function handleAnswer(answer) {\n  if (!pc) {\n    console.error('no peerconnection');\n    return;\n  }\n  await pc.setRemoteDescription(answer);\n}\n\nasync function handleCandidate(candidate) {\n  if (!pc) {\n    console.error('no peerconnection');\n    return;\n  }\n  if (!candidate.candidate) {\n    await pc.addIceCandidate(null);\n  } else {\n    await pc.addIceCandidate(candidate);\n  }\n}\n\n"
  },
  {
    "path": "src/content/peerconnection/channel/js/test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/channel/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection and broadcast channels', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(async () => {\n    await driver.get(url);\n  });\n\n  it('establishes a connection and hangs up', async () => {\n    const firstTab = await driver.getWindowHandle();\n\n    await driver.switchTo().window(firstTab);\n    await driver.findElement(webdriver.By.id('startButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    // Create a second tab, switch to it.\n    await driver.switchTo().newWindow('tab');\n    const secondTab = await driver.getWindowHandle();\n    await driver.get(url);\n\n    await driver.switchTo().window(secondTab);\n    await driver.findElement(webdriver.By.id('startButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    // Assert state in first tab.\n    await driver.switchTo().window(firstTab);\n    await driver.wait(() => driver.executeScript(() => {\n      return pc && pc.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('remoteVideo').readyState === 4;\n    }));\n\n    // Assert state in second tab.\n    await driver.switchTo().window(secondTab);\n    await driver.wait(() => driver.executeScript(() => {\n      return pc && pc.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('remoteVideo').readyState === 4;\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/constraints/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 25px 0;\n  vertical-align: top;\n  width: 134px;\n}\n\ndiv#getUserMedia {\n  padding: 0 0 8px 0;\n}\n\ndiv.input {\n  display: inline-block;\n  margin: 0 4px 0 0;\n  vertical-align: top;\n  width: 310px;\n}\n\ndiv.input > div {\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\ndiv.output {\n  background-color: #eee;\n  display: inline-block;\n  font-family: 'Inconsolata', 'Courier New', monospace;\n  font-size: 0.9em;\n  padding: 10px 10px 10px 25px;\n  position: relative;\n  top: 10px;\n  white-space: pre;\n  width: 270px;\n}\n\nsection#statistics div {\n  display: inline-block;\n  font-family: 'Inconsolata', 'Courier New', monospace;\n  vertical-align: top;\n  width: 308px;\n}\n\nsection#statistics div#senderStats {\n  margin: 0 20px 0 0;\n}\n\nsection#constraints > div {\n  margin: 0 0 20px 0;\n}\n\nsection#video > div {\n  display: inline-block;\n  margin: 0 20px 0 0;\n  vertical-align: top;\n  width: calc(50% - 22px);\n}\n\nsection#video > div div {\n  font-size: 0.9em;\n  margin: 0 0 0.5em 0;\n  width: 320px;\n}\n\nh2 {\n  margin: 0 0 1em 0;\n}\n\nsection#constraints label {\n  display: inline-block;\n  width: 156px;\n}\n\nsection {\n  margin: 0 0 20px 0;\n  padding: 0 0 15px 0;\n}\n\nsection#video {\n  width: calc(100% + 20px);\n}\n\nvideo {\n  --width: 90%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 10px 0;\n}\n\n@media screen and (max-width: 720px) {\n  button {\n    font-weight: 500;\n    height: 56px;\n    line-height: 1.3em;\n    width: 90px;\n  }\n\n  div#getUserMedia {\n    padding: 0 0 40px 0;\n  }\n\n  section#statistics div {\n    width: calc(50% - 14px);\n  }\n\n  video {\n    height: 96px;\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/constraints/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Constraints and statistics</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Constraints &amp; statistics</span>\n    </h1>\n\n    <section id=\"blurb\">\n        <p>This demo shows ways to use constraints and statistics in WebRTC applications.</p>\n        <p>Set camera constraints, and click <strong>Get media</strong> to (re)open the camera with these included.\n            Click <strong>Connect</strong> to create a (local) peer connection. The RTCPeerConnection objects <code>pc1</code>\n            and <code>pc2</code> can be inspected from the console.</p>\n        <p>Setting a value to zero will remove that constraint. </p>\n        <p>The lefthand video shows the output of <code>getUserMedia()</code>; on the right is the video after being\n            passed through the peer connection. The transmission bitrate is displayed below the righthand video.</p>\n    </section>\n\n    <div>\n        <button id=\"getMedia\">Get media</button>\n        <button id=\"connect\" disabled>Connect</button>\n        <button id=\"hangup\" disabled>Hang Up</button>\n    </div>\n\n\n    <section id=\"constraints\">\n        <div id=\"getUserMedia\">\n            <div class=\"input\">\n                <h2>Camera constraints</h2>\n                <div id=\"minWidth\">\n                    <label>Min width <span>300</span>px:</label>\n                    <input type=\"range\" min=\"0\" max=\"4096\" value=\"300\">\n                </div>\n                <div id=\"maxWidth\">\n                    <label>Max width <span>640</span>px:</label>\n                    <input type=\"range\" min=\"0\" max=\"4096\" value=\"640\">\n                </div>\n                <div id=\"minHeight\">\n                    <label>Min height <span>200</span>px:</label>\n                    <input type=\"range\" min=\"0\" max=\"2160\" value=\"200\">\n                </div>\n                <div id=\"maxHeight\">\n                    <label>Max height <span>480</span>px:</label>\n                    <input type=\"range\" min=\"0\" max=\"2160\" value=\"480\">\n                </div>\n                <div id=\"minFramerate\">\n                    <label>Min frameRate <span>0</span>fps:</label>\n                    <input type=\"range\" min=\"0\" max=\"60\" value=\"0\">\n                </div>\n                <div id=\"maxFramerate\">\n                    <label>Max frameRate <span>0</span>fps:</label>\n                    <input type=\"range\" min=\"0\" max=\"60\" value=\"0\">\n                </div>\n            </div>\n            <div id=\"getUserMediaConstraints\" class=\"output\"></div>\n        </div>\n\n    </section>\n\n    <section id=\"video\">\n        <div id=\"localVideo\">\n            <video playsinline autoplay muted></video>\n            <div></div>\n        </div>\n        <div id=\"remoteVideo\">\n            <video playsinline autoplay muted></video>\n            <div></div>\n            <div id=\"bitrate\"></div>\n            <div id=\"peer\"></div>\n        </div>\n    </section>\n\n    <section id=\"statistics\">\n        <label>Update statistics every second</label>\n        <input type=\"checkbox\" id=\"updateStats\" checked>\n        <br>\n        <div id=\"senderStats\"></div>\n        <div id=\"receiverStats\"></div>\n    </section>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/constraints\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/constraints/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst getMediaButton = document.querySelector('button#getMedia');\nconst connectButton = document.querySelector('button#connect');\nconst hangupButton = document.querySelector('button#hangup');\n\ngetMediaButton.onclick = getMedia;\nconnectButton.onclick = createPeerConnection;\nhangupButton.onclick = hangup;\n\nconst minWidthInput = document.querySelector('div#minWidth input');\nconst maxWidthInput = document.querySelector('div#maxWidth input');\nconst minHeightInput = document.querySelector('div#minHeight input');\nconst maxHeightInput = document.querySelector('div#maxHeight input');\nconst minFramerateInput = document.querySelector('div#minFramerate input');\nconst maxFramerateInput = document.querySelector('div#maxFramerate input');\n\nminWidthInput.onchange = maxWidthInput.onchange =\n  minHeightInput.onchange = maxHeightInput.onchange =\n    minFramerateInput.onchange = maxFramerateInput.onchange = displayRangeValue;\n\nconst getUserMediaConstraintsDiv = document.querySelector('div#getUserMediaConstraints');\nconst bitrateDiv = document.querySelector('div#bitrate');\nconst peerDiv = document.querySelector('div#peer');\nconst senderStatsDiv = document.querySelector('div#senderStats');\nconst receiverStatsDiv = document.querySelector('div#receiverStats');\n\nconst localVideo = document.querySelector('div#localVideo video');\nconst remoteVideo = document.querySelector('div#remoteVideo video');\nconst localVideoStatsDiv = document.querySelector('div#localVideo div');\nconst remoteVideoStatsDiv = document.querySelector('div#remoteVideo div');\n\nconst updateStats = document.querySelector('input#updateStats');\n\nlet pc1;\nlet pc2;\nlet localStream;\nlet bytesPrev;\nlet timestampPrev;\n\nmain();\n\nfunction main() {\n  displayGetUserMediaConstraints();\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n\n  // query stats one last time.\n  Promise\n      .all([\n        pc2\n            .getStats()\n            .then(showRemoteStats, err => console.log(err)),\n        pc1\n            .getStats()\n            .then(showLocalStats, err => console.log(err))\n      ])\n      .then(() => {\n        pc1 = null;\n        pc2 = null;\n      });\n\n  localStream.getTracks().forEach(track => track.stop());\n  localStream = null;\n\n  hangupButton.disabled = true;\n  getMediaButton.disabled = false;\n}\n\nfunction getMedia() {\n  getMediaButton.disabled = true;\n  if (localStream) {\n    localStream.getTracks().forEach(track => track.stop());\n    const videoTracks = localStream.getVideoTracks();\n    for (let i = 0; i !== videoTracks.length; ++i) {\n      videoTracks[i].stop();\n    }\n  }\n  navigator.mediaDevices.getUserMedia(getUserMediaConstraints())\n      .then(gotStream)\n      .catch(e => {\n        const message = `getUserMedia error: ${e.name}\\nThis may mean invalid constraints.`;\n        alert(message);\n        console.log(message);\n        getMediaButton.disabled = false;\n      });\n}\n\nfunction gotStream(stream) {\n  connectButton.disabled = false;\n  console.log('GetUserMedia succeeded');\n  localStream = stream;\n  localVideo.srcObject = stream;\n}\n\nfunction getUserMediaConstraints() {\n  const constraints = {};\n  constraints.audio = true;\n  constraints.video = {};\n  if (minWidthInput.value !== '0') {\n    constraints.video.width = {};\n    constraints.video.width.min = minWidthInput.value;\n  }\n  if (maxWidthInput.value !== '0') {\n    constraints.video.width = constraints.video.width || {};\n    constraints.video.width.max = maxWidthInput.value;\n  }\n  if (minHeightInput.value !== '0') {\n    constraints.video.height = {};\n    constraints.video.height.min = minHeightInput.value;\n  }\n  if (maxHeightInput.value !== '0') {\n    constraints.video.height = constraints.video.height || {};\n    constraints.video.height.max = maxHeightInput.value;\n  }\n  if (minFramerateInput.value !== '0') {\n    constraints.video.frameRate = {};\n    constraints.video.frameRate.min = minFramerateInput.value;\n  }\n  if (maxFramerateInput.value !== '0') {\n    constraints.video.frameRate = constraints.video.frameRate || {};\n    constraints.video.frameRate.max = maxFramerateInput.value;\n  }\n\n  return constraints;\n}\n\nfunction displayGetUserMediaConstraints() {\n  const constraints = getUserMediaConstraints();\n  console.log('getUserMedia constraints', constraints);\n  getUserMediaConstraintsDiv.textContent = JSON.stringify(constraints, null, '    ');\n}\n\nfunction createPeerConnection() {\n  connectButton.disabled = true;\n  hangupButton.disabled = false;\n\n  bytesPrev = 0;\n  timestampPrev = 0;\n  pc1 = new RTCPeerConnection(null);\n  pc2 = new RTCPeerConnection(null);\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('pc1 creating offer');\n  pc1.onnegotiationneeded = () => console.log('Negotiation needed - pc1');\n  pc2.onnegotiationneeded = () => console.log('Negotiation needed - pc2');\n  pc1.onicecandidate = e => {\n    console.log('Candidate pc1');\n    pc2\n        .addIceCandidate(e.candidate)\n        .then(onAddIceCandidateSuccess, onAddIceCandidateError);\n  };\n  pc2.onicecandidate = e => {\n    console.log('Candidate pc2');\n    pc1\n        .addIceCandidate(e.candidate)\n        .then(onAddIceCandidateSuccess, onAddIceCandidateError);\n  };\n  pc2.ontrack = e => {\n    if (remoteVideo.srcObject !== e.streams[0]) {\n      console.log('pc2 got stream');\n      remoteVideo.srcObject = e.streams[0];\n    }\n  };\n  pc1.createOffer().then(\n      desc => {\n        console.log('pc1 offering');\n        pc1.setLocalDescription(desc);\n        pc2.setRemoteDescription(desc);\n        pc2.createAnswer().then(\n            desc2 => {\n              console.log('pc2 answering');\n              pc2.setLocalDescription(desc2);\n              pc1.setRemoteDescription(desc2);\n            },\n            err => console.log(err)\n        );\n      },\n      err => console.log(err)\n  );\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log(`Failed to add Ice Candidate: ${error.toString()}`);\n}\n\nfunction showRemoteStats(results) {\n  const statsString = dumpStats(results);\n  receiverStatsDiv.innerHTML = `<h2>Receiver stats</h2>${statsString}`;\n  // calculate video bitrate\n  results.forEach(report => {\n    const now = report.timestamp;\n\n    let bitrate;\n    if (report.type === 'inbound-rtp' && report.mediaType === 'video') {\n      const bytes = report.bytesReceived;\n      if (timestampPrev) {\n        bitrate = 8 * (bytes - bytesPrev) / (now - timestampPrev);\n        bitrate = Math.floor(bitrate);\n      }\n      bytesPrev = bytes;\n      timestampPrev = now;\n    }\n    if (bitrate) {\n      bitrate += ' kbits/sec';\n      bitrateDiv.innerHTML = `<strong>Bitrate:</strong>${bitrate}`;\n    }\n  });\n\n  // figure out the peer's ip\n  let activeCandidatePair = null;\n  let remoteCandidate = null;\n\n  // Search for the candidate pair, spec-way first.\n  results.forEach(report => {\n    if (report.type === 'transport') {\n      activeCandidatePair = results.get(report.selectedCandidatePairId);\n    }\n  });\n  // Fallback for Firefox.\n  if (!activeCandidatePair) {\n    results.forEach(report => {\n      if (report.type === 'candidate-pair' && report.selected) {\n        activeCandidatePair = report;\n      }\n    });\n  }\n  if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {\n    remoteCandidate = results.get(activeCandidatePair.remoteCandidateId);\n  }\n  if (remoteCandidate) {\n    if (remoteCandidate.address && remoteCandidate.port) {\n      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.address}:${remoteCandidate.port}`;\n    } else if (remoteCandidate.ip && remoteCandidate.port) {\n      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.ip}:${remoteCandidate.port}`;\n    } else if (remoteCandidate.ipAddress && remoteCandidate.portNumber) {\n      // Fall back to old names.\n      peerDiv.innerHTML = `<strong>Connected to:</strong>${remoteCandidate.ipAddress}:${remoteCandidate.portNumber}`;\n    }\n  }\n}\n\nfunction showLocalStats(results) {\n  const statsString = dumpStats(results);\n  senderStatsDiv.innerHTML = `<h2>Sender stats</h2>${statsString}`;\n}\n\n// Display statistics\nsetInterval(() => {\n  if (!updateStats.checked) {\n    return;\n  }\n  if (pc1 && pc2) {\n    pc2\n        .getStats()\n        .then(showRemoteStats, err => console.log(err));\n    pc1\n        .getStats()\n        .then(showLocalStats, err => console.log(err));\n  } else {\n    console.log('Not connected yet');\n  }\n  // Collect some stats from the video tags.\n  if (localVideo.videoWidth) {\n    const width = localVideo.videoWidth;\n    const height = localVideo.videoHeight;\n    localVideoStatsDiv.innerHTML = `<strong>Video dimensions:</strong> ${width}x${height}px`;\n  }\n  if (remoteVideo.videoWidth) {\n    const rHeight = remoteVideo.videoHeight;\n    const rWidth = remoteVideo.videoWidth;\n    remoteVideoStatsDiv.innerHTML = `<strong>Video dimensions:</strong> ${rWidth}x${rHeight}px`;\n  }\n}, 1000);\n\n// Dumping a stats variable as a string.\n// might be named toString?\nfunction dumpStats(results) {\n  let statsString = '';\n  results.forEach(report => {\n    statsString += '<h3>Report type=';\n    statsString += report.type;\n    statsString += '</h3>\\n';\n    statsString += `id: ${report.id}<br>`;\n    statsString += `timestamp: ${report.timestamp}<br>`;\n    Object.keys(report).forEach(key => {\n      if (['id', 'timestamp', 'type'].includes(key)) return;\n      if (typeof report[key] === 'object') {\n        statsString += `${key}: ${JSON.stringify(report[key])}<br>`;\n      } else {\n        statsString += `${key}: ${report[key]}<br>`;\n      }\n    });\n  });\n  return statsString;\n}\n\n// Utility to show the value of a range in a sibling span element\nfunction displayRangeValue(e) {\n  const span = e.target.parentElement.querySelector('span');\n  span.textContent = e.target.value;\n  displayGetUserMediaConstraints();\n}\n"
  },
  {
    "path": "src/content/peerconnection/create-offer/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\nbutton {\n  margin: 20px 10px 0 0;\n  width: 100px;\n}\n\ndiv#constraints {\n  margin: 0 0 20px 0;\n}\n\ndiv#numAudioTracks {\n  margin: 0 0 20px 0;\n}\n\ndiv#constraints div {\n  margin: 0 0 10px 0;\n}\n\ndiv#constraints input {\n  margin: 0 10px 0 0;\n  position: relative;\n  top: -2px;\n}\n\ndiv#numAudioTracks input {\n  max-width: 30%;\n  position: relative;\n  top: 2px;\n  width: 200px;\n}\n\nlabel {\n  font-weight: 500;\n  margin: 0 10px 0 0;\n}\n\ntextarea {\n  height: 200px;\n  width: 100%;\n}\n"
  },
  {
    "path": "src/content/peerconnection/create-offer/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>createOffer() output</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>createOffer() output</span>\n    </h1>\n\n    <p>This page tests the <code>createOffer()</code> method. It creates a peer connection, then prints out the SDP\n        generated by <code>createOffer()</code>, with the number of desired audio <code>MediaStreamTrack</code>s and the\n        checked constraints. Currently, only audio tracks can be added, as there is no programmatic way to generate\n        video tracks. (Web Audio is used to generate the audio tracks.)</p>\n\n    <div id=\"numAudioTracks\">\n        <label for=\"numAudioTracksInput\">Number of audio tracks:</label>\n        <input id=\"numAudioTracksInput\" max=\"8\" min=\"0\" type=\"range\" value=\"1\"/>\n        <span id=\"numAudioTracksDisplay\">1</span>\n    </div>\n\n    <div id=\"constraints\">\n        <div>\n            <input id=\"audio\" type=\"checkbox\"/><label for=\"audio\">Offer to receive audio</label>\n        </div>\n        <div>\n            <input id=\"video\" type=\"checkbox\"/><label for=\"video\">Offer to receive video</label>\n        </div>\n        <div>\n            <input id=\"vad\" type=\"checkbox\"/><label for=\"vad\">Voice activity detection</label>\n        </div>\n        <div>\n            <input id=\"restart\" type=\"checkbox\"/><label for=\"restart\">Ice restart</label>\n        </div>\n    </div>\n\n    <textarea id=\"output\"></textarea>\n\n    <button id=\"createOffer\">Create offer</button>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/create-offer\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/create-offer/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst audioInput = document.querySelector('input#audio');\nconst restartInput = document.querySelector('input#restart');\nconst vadInput = document.querySelector('input#vad');\nconst videoInput = document.querySelector('input#video');\n\nconst numAudioTracksInput = document.querySelector('div#numAudioTracks input');\nconst numAudioTracksDisplay = document.querySelector('span#numAudioTracksDisplay');\nconst outputTextarea = document.querySelector('textarea#output');\nconst createOfferButton = document.querySelector('button#createOffer');\n\ncreateOfferButton.addEventListener('click', createOffer);\nnumAudioTracksInput.addEventListener('change', e => numAudioTracksDisplay.innerText = e.target.value);\n\nasync function createOffer() {\n  outputTextarea.value = '';\n  const peerConnection = window.peerConnection = new RTCPeerConnection(null);\n  const numRequestedAudioTracks = parseInt(numAudioTracksInput.value);\n\n  for (let i = 0; i < numRequestedAudioTracks; i++) {\n    const acx = new AudioContext();\n    const dst = acx.createMediaStreamDestination();\n\n    // Fill up the peer connection with numRequestedAudioTracks number of tracks.\n    const track = dst.stream.getTracks()[0];\n    peerConnection.addTrack(track, dst.stream);\n  }\n\n  const offerOptions = {\n    // New spec states offerToReceiveAudio/Video are of type long (due to\n    // having to tell how many \"m\" lines to generate).\n    // http://w3c.github.io/webrtc-pc/#idl-def-RTCOfferAnswerOptions.\n    offerToReceiveAudio: (audioInput.checked) ? 1 : 0,\n    offerToReceiveVideo: (videoInput.checked) ? 1 : 0,\n    iceRestart: restartInput.checked,\n    voiceActivityDetection: vadInput.checked\n  };\n\n  try {\n    const offer = await peerConnection.createOffer(offerOptions);\n    await peerConnection.setLocalDescription(offer);\n    outputTextarea.value = offer.sdp;\n  } catch (e) {\n    outputTextarea.value = `Failed to create offer: ${e}`;\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/dtmf/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 20px 0;\n  width: 96px;\n}\n\ndiv#dialPad button {\n  background-color: #ddd;\n  border: 1px solid #ccc;\n  color: black;\n  font-size: 1em;\n  font-weight: 400;\n  height: 40px;\n  margin: 0 10px 10px 0;\n  width: 40px;\n}\n\ndiv#dialPad button:hover {\n  background-color: #aaa;\n}\n\ndiv#dialPad button:active {\n  background-color: #888;\n}\n\ndiv#dialPad {\n  display: inline-block;\n  margin: 0 20px 20px 0;\n  vertical-align: top;\n}\n\ndiv#parameters {\n  margin: 0 0 25px 0;\n}\n\ndiv#parameters > div {\n  height: 28px;\n  margin: 0 0 10px 0;\n}\n\ndiv#dtmf {\n  background-color: #eee;\n  display: inline-block;\n  height: 180px;\n  margin: 0 0 20px 0;\n  padding: 5px 5px 5px 10px;\n  width: calc(100% - 239px);\n}\n\ndiv#dtmf div {\n  font-family: 'Inconsolata', 'Courier New', monospace;\n}\n\ndiv#sentTones {\n  display: inline-block;\n  line-height: 1.2em;\n}\n\ndiv#dtmfStatus {\n  margin: 0 0 10px 0;\n}\n\ndiv#parameters input[type = range] {\n  font-size: 1em;\n  width: 85px;\n}\n\ndiv#parameters input#tones {\n  width: calc(100% - 78px);\n}\n\ndiv#parameters label {\n  display: inline-block;\n  font-weight: 400;\n  height: 28px;\n  position: relative;\n  top: 4px;\n  vertical-align: top;\n  width: 68px;\n}\n\n\n"
  },
  {
    "path": "src/content/peerconnection/dtmf/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>DTMF</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link href=\"https://fonts.googleapis.com/css?family=Inconsolata\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Send DTMF tones</span></h1>\n\n    <div id=\"dialPad\">\n        <div>\n            <button>1</button>\n            <button>2</button>\n            <button>3</button>\n            <button>4</button>\n        </div>\n        <div>\n            <button>5</button>\n            <button>6</button>\n            <button>7</button>\n            <button>8</button>\n        </div>\n        <div>\n            <button>9</button>\n            <button>0</button>\n            <button>*</button>\n            <button>#</button>\n        </div>\n        <div>\n            <button>A</button>\n            <button>B</button>\n            <button>C</button>\n            <button>D</button>\n        </div>\n    </div>\n\n    <div id=\"dtmf\">\n        <h2>Sent tones</h2>\n        <div id=\"dtmfStatus\"></div>\n        <input type=\"text\" disabled style=\"width: 80%\" id=\"sentTones\" />\n        <audio autoplay=\"autoplay\"></audio>\n    </div>\n\n    <div id=\"parameters\">\n        <div>\n            <label for=\"duration\">Duration:</label>\n            <input id=\"duration\" type=\"range\" value=\"500\" min=\"70\" max=\"6000\">\n            <span id=\"durationValue\">500</span>\n        </div>\n        <div>\n            <label for=\"gap\">Gap:</label>\n            <input id=\"gap\" type=\"range\" value=\"50\" min=\"50\" max=\"1000\">\n            <span id=\"gapValue\">50</span>\n        </div>\n        <div>\n            <label for=\"tones\">Tones:</label>\n            <input id=\"tones\" type=\"text\"\n                   value=\"1199##9,6633221,9966332,9966332,1199##9,6633221\"/>\n        </div>\n    </div>\n\n    <div id=\"buttons\">\n        <button id=\"callButton\">Call</button>\n        <button id=\"sendTonesButton\">Send tones</button>\n        <button id=\"hangupButton\">Hang up</button>\n    </div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/dtmf\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/dtmf/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst callButton = document.querySelector('button#callButton');\nconst sendTonesButton = document.querySelector('button#sendTonesButton');\nconst hangupButton = document.querySelector('button#hangupButton');\nconst durationInput = document.querySelector('input#duration');\nconst gapInput = document.querySelector('input#gap');\nconst tonesInput = document.querySelector('input#tones');\nconst durationValue = document.querySelector('span#durationValue');\nconst gapValue = document.querySelector('span#gapValue');\nconst sentTonesInput = document.querySelector('input#sentTones');\nconst dtmfStatusDiv = document.querySelector('div#dtmfStatus');\nconst audio = document.querySelector('audio');\n\nlet pc1;\nlet pc2;\nlet localStream;\nlet dtmfSender;\n\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 0\n};\n\ndurationInput.oninput = () => {\n  durationValue.textContent = durationInput.value;\n};\n\ngapInput.oninput = () => {\n  gapValue.textContent = gapInput.value;\n};\n\nasync function main() {\n  addDialPadHandlers();\n\n  sendTonesButton.disabled = true;\n  hangupButton.disabled = true;\n\n  callButton.addEventListener('click', e => call());\n  sendTonesButton.addEventListener('click', e => handleSendTonesClick());\n  hangupButton.addEventListener('click', e => hangup());\n}\n\nasync function gotStream(stream) {\n  console.log('Received local stream');\n  localStream = stream;\n  const audioTracks = localStream.getAudioTracks();\n  if (audioTracks.length > 0) {\n    console.log(`Using Audio device: ${audioTracks[0].label}`);\n  }\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n  try {\n    const offer = await pc1.createOffer(offerOptions);\n    await gotLocalOffer(offer);\n  } catch (e) {\n    console.log('Failed to create session description:', e);\n  }\n}\n\nasync function call() {\n  console.log('Starting call');\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));\n  pc2.addEventListener('track', e => gotRemoteStream(e));\n\n  console.log('Requesting local stream');\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: false});\n    await gotStream(stream);\n  } catch (e) {\n    console.log('getUserMedia() error:', e);\n  }\n\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  sendTonesButton.disabled = false;\n}\n\nasync function gotLocalOffer(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  pc1.setLocalDescription(desc);\n  pc2.setRemoteDescription(desc);\n  try {\n    const answer = await pc2.createAnswer();\n    gotRemoteAnswer(answer);\n  } catch (e) {\n    console.log('Failed to create session description:', e);\n  }\n}\n\nfunction gotRemoteAnswer(desc) {\n  pc2.setLocalDescription(desc);\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  pc1.setRemoteDescription(desc);\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  localStream = null;\n  dtmfSender = null;\n  callButton.disabled = false;\n  hangupButton.disabled = true;\n  sendTonesButton.disabled = true;\n  dtmfStatusDiv.textContent = 'DTMF deactivated';\n}\n\nfunction gotRemoteStream(e) {\n  if (audio.srcObject !== e.streams[0]) {\n    audio.srcObject = e.streams[0];\n    console.log('Received remote stream');\n\n    if (!pc1.getSenders) {\n      alert('This demo requires the RTCPeerConnection method getSenders() which is not support by this browser.');\n      return;\n    }\n    const senders = pc1.getSenders();\n    const audioSender = senders.find(sender => sender.track && sender.track.kind === 'audio');\n    if (!audioSender) {\n      console.log('No local audio track to send DTMF with\\n');\n      return;\n    }\n    if (!audioSender.dtmf) {\n      alert('This demo requires DTMF which is not support by this browser.');\n      return;\n    }\n    dtmfSender = audioSender.dtmf;\n    dtmfStatusDiv.textContent = 'DTMF available';\n    console.log('Got DTMFSender\\n');\n    dtmfSender.ontonechange = dtmfOnToneChange;\n  }\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nasync function onIceCandidate(pc, event) {\n  try {\n    await getOtherPc(pc).addIceCandidate(event.candidate);\n    console.log(`${getName(pc)} ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}`);\n  } catch (e) {\n    console.log('Error adding ice candidate:', e);\n  }\n}\n\nfunction dtmfOnToneChange(tone) {\n  if (tone) {\n    console.log(`Sent DTMF tone: ${tone.tone}`);\n    sentTonesInput.value += `${tone.tone} `;\n  }\n}\n\nfunction sendTones(tones) {\n  // firefox doesn't implement canInsertDTMF, so assume it's always available\n  // Ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1623193\n  if (dtmfSender && (typeof (dtmfSender.canInsertDTMF) === 'undefined' || dtmfSender.canInsertDTMF)) {\n    const duration = durationInput.value;\n    const gap = gapInput.value;\n    console.log('Tones, duration, gap: ', tones, duration, gap);\n    dtmfSender.insertDTMF(tones, duration, gap);\n  }\n}\n\nfunction handleSendTonesClick() {\n  sendTones(tonesInput.value);\n}\n\nfunction addDialPadHandlers() {\n  const dialPad = document.querySelector('div#dialPad');\n  const buttons = dialPad.querySelectorAll('button');\n  for (let i = 0; i !== buttons.length; ++i) {\n    buttons[i].addEventListener('click', (event) => sendTones(event.target.textContent));\n  }\n}\n\nmain();\n"
  },
  {
    "path": "src/content/peerconnection/dtmf/js/test.js",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/*\n/* eslint-env node */\n'use strict';\n\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/dtmf/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection dtmf', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(async () => {\n    await driver.get(url);\n    await driver.findElement(webdriver.By.id('callButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.executeScript(() => {\n      return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n  });\n\n  it('sends the digit 1', async () => {\n    await driver.findElement(webdriver.By.css('#dialPad>div:nth-child(1)>button:nth-child(1)')).click();\n    await driver.wait(driver.executeScript(() => {\n      document.getElementById('sentTones').value.length !== 0;\n    }));\n    const sentTones = await driver.findElement(webdriver.By.id('sentTones')).getAttribute('value');\n    expect(sentTones).toBe('1 ');\n  });\n\n  it('sends the digit 9', async () => {\n    await driver.findElement(webdriver.By.css('#dialPad>div:nth-child(3)>button:nth-child(1)')).click();\n    await driver.wait(driver.executeScript(() => {\n      document.getElementById('sentTones').value.length !== 0;\n    }));\n    const sentTones = await driver.findElement(webdriver.By.id('sentTones')).getAttribute('value');\n    expect(sentTones).toBe('9 ');\n  });\n\n  it('sends the #', async () => {\n    await driver.findElement(webdriver.By.css('#dialPad>div:nth-child(3)>button:nth-child(4)')).click();\n    await driver.wait(driver.executeScript(() => {\n      document.getElementById('sentTones').value.length !== 0;\n    }));\n    const sentTones = await driver.findElement(webdriver.By.id('sentTones')).getAttribute('value');\n    expect(sentTones).toBe('# ');\n  });\n\n  it('sends the A', async () => {\n    await driver.findElement(webdriver.By.css('#dialPad>div:nth-child(4)>button:nth-child(1)')).click();\n    await driver.wait(driver.executeScript(() => {\n      document.getElementById('sentTones').value.length !== 0;\n    }));\n    const sentTones = await driver.findElement(webdriver.By.id('sentTones')).getAttribute('value');\n    expect(sentTones).toBe('A ');\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/endtoend-encryption/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n   <meta http-equiv=\"refresh\"\n   content=\"0; url=//webrtc.github.io/samples/src/content/insertable-streams/endtoend-encryption/\">\n   <title>Page move</title>\n</head>\n<body>\n   <p>The page has moved to:\n   <a href=\"//webrtc.github.io/samples/src/content/insertable-streams/endtoend-encryption/\">this page</a></p>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/multiple/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  margin: 0 0 20px 0;\n  --width: 40%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n\nvideo#video1 {\n  margin: 0 20px 20px 0;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 8px);\n  }\n\n  video#video1 {\n    margin: 0 10px 10px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/multiple/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Multiple peer connections</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Multiple peer connections</span>\n    </h1>\n\n    <video id=\"video1\" playsinline autoplay muted></video>\n    <video id=\"video2\" playsinline autoplay></video>\n    <video id=\"video3\" playsinline autoplay></video>\n\n    <div>\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <p>View the console to see logging and to inspect the <code>MediaStream</code> object <code>localStream</code>.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/multiple\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/multiple/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.onclick = start;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\nconst video1 = document.querySelector('video#video1');\nconst video2 = document.querySelector('video#video2');\nconst video3 = document.querySelector('video#video3');\n\n// eslint-disable-next-line prefer-const\nlet preferredVideoCodecMimeType = 'video/VP8';\n\nlet localStream;\nlet pc1Local;\nlet pc1Remote;\nlet pc2Local;\nlet pc2Remote;\n\nconst supportsSetCodecPreferences = window.RTCRtpTransceiver &&\n  'setCodecPreferences' in window.RTCRtpTransceiver.prototype;\nfunction maybeSetCodecPreferences(trackEvent) {\n  if (!supportsSetCodecPreferences) return;\n  if (trackEvent.track.kind === 'video' && preferredVideoCodecMimeType) {\n    const {codecs} = RTCRtpReceiver.getCapabilities('video');\n    const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredVideoCodecMimeType);\n    const selectedCodec = codecs[selectedCodecIndex];\n    codecs.splice(selectedCodecIndex, 1);\n    codecs.unshift(selectedCodec);\n    trackEvent.transceiver.setCodecPreferences(codecs);\n  }\n}\n\nasync function start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  localStream = await navigator.mediaDevices.getUserMedia({\n    audio: true,\n    video: true\n  });\n  video1.srcObject = localStream;\n  callButton.disabled = false;\n}\n\nasync function call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting calls');\n  const audioTracks = localStream.getAudioTracks();\n  const videoTracks = localStream.getVideoTracks();\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  // Create an RTCPeerConnection via the polyfill.\n  pc1Local = new RTCPeerConnection();\n  pc1Remote = new RTCPeerConnection();\n  pc1Remote.ontrack = e => gotRemoteStream(e, video2);\n  console.log('pc1: created local and remote peer connection objects');\n\n  pc2Local = new RTCPeerConnection();\n  pc2Remote = new RTCPeerConnection();\n  pc2Remote.ontrack = e => gotRemoteStream(e, video3);\n  console.log('pc2: created local and remote peer connection objects');\n  localStream.getTracks().forEach(track => {\n    pc1Local.addTrack(track, localStream);\n    pc2Local.addTrack(track, localStream);\n  });\n  await Promise.all([\n    negotiate(pc1Local, pc1Remote),\n    negotiate(pc2Local, pc2Remote),\n  ]);\n}\n\nasync function negotiate(localPc, remotePc) {\n  localPc.onicecandidate = e => remotePc.addIceCandidate(e.candidate);\n  remotePc.onicecandidate = e => localPc.addIceCandidate(e.candidate);\n\n  await localPc.setLocalDescription();\n  await remotePc.setRemoteDescription(localPc.localDescription);\n  await remotePc.setLocalDescription();\n  await localPc.setRemoteDescription(remotePc.localDescription);\n}\n\nfunction hangup() {\n  console.log('Ending calls');\n  pc1Local.close();\n  pc1Remote.close();\n  pc2Local.close();\n  pc2Remote.close();\n  pc1Local = pc1Remote = null;\n  pc2Local = pc2Remote = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n\nfunction gotRemoteStream(e, videoObject) {\n  maybeSetCodecPreferences(e);\n  if (videoObject.srcObject !== e.streams[0]) {\n    videoObject.srcObject = e.streams[0];\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/multiple/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/multiple/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('multiple peerconnections', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes multiple connections and hangs up', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await Promise.all([\n      driver.wait(() => driver.executeScript(() => {\n        return pc1Remote && pc1Remote.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2Remote && pc2Remote.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return document.getElementById('video2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return document.getElementById('video3').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n      })),\n    ]);\n\n    await driver.findElement(webdriver.By.id('hangupButton')).click();\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return pc1Remote === null; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2Remote === null; // eslint-disable-line no-undef\n      })),\n    ]);\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/multiple-relay/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\nbutton {\n  margin: 20px 10px 0 0;\n  width: 100px;\n}\n\ndiv#buttons {\n  margin: 0 0 20px 0;\n}\n\ndiv#status {\n  height: 2em;\n  margin: 1em 0 0 0;\n}\n\ninput#audio {\n  margin: 0 0.5em 0 0;\n  position: relative;\n  top: -1px;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n\n"
  },
  {
    "path": "src/content/peerconnection/multiple-relay/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection relay</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection relay</span>\n    </h1>\n\n    <div id=\"videos\">\n        <video id=\"video1\" playsinline autoplay muted></video>\n        <video id=\"video2\" playsinline autoplay></video>\n    </div>\n\n    <section><input type=\"checkbox\" id=\"audio\"><label for=\"audio\">Include audio (supported in Chrome 49 and above)</label></section>\n\n    <div id=\"buttons\">\n        <button id=\"start\">Start</button>\n        <button id=\"call\" disabled>Call</button>\n        <button id=\"insertRelay\" disabled>Insert relay</button>\n        <button id=\"hangup\" disabled>Hang Up</button>\n    </div>\n\n    <div id=\"status\"></div>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/multiple-relay\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"../../../js/videopipe.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/multiple-relay/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global VideoPipe */\n\nconst video1 = document.querySelector('video#video1');\nconst video2 = document.querySelector('video#video2');\n\nconst statusDiv = document.querySelector('div#status');\n\nconst audioCheckbox = document.querySelector('input#audio');\n\nconst startButton = document.querySelector('button#start');\nconst callButton = document.querySelector('button#call');\nconst insertRelayButton = document.querySelector('button#insertRelay');\nconst hangupButton = document.querySelector('button#hangup');\n\nstartButton.onclick = start;\ncallButton.onclick = call;\ninsertRelayButton.onclick = insertRelay;\nhangupButton.onclick = hangup;\n\nconst pipes = [];\n\nlet localStream;\nlet remoteStream;\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  video1.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nfunction gotremoteStream(stream) {\n  remoteStream = stream;\n  video2.srcObject = stream;\n  console.log('Received remote stream');\n  console.log(`${pipes.length} element(s) in chain`);\n  statusDiv.textContent = `${pipes.length} element(s) in chain`;\n  insertRelayButton.disabled = false;\n}\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  const options = audioCheckbox.checked ? {audio: true, video: true} : {audio: false, video: true};\n  navigator.mediaDevices\n      .getUserMedia(options)\n      .then(gotStream)\n      .catch(function(e) {\n        alert('getUserMedia() failed');\n        console.log('getUserMedia() error: ', e);\n      });\n}\n\nfunction call() {\n  callButton.disabled = true;\n  insertRelayButton.disabled = false;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  pipes.push(new VideoPipe(localStream, gotremoteStream));\n}\n\nfunction insertRelay() {\n  pipes.push(new VideoPipe(remoteStream, gotremoteStream));\n  insertRelayButton.disabled = true;\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  while (pipes.length > 0) {\n    const pipe = pipes.pop();\n    pipe.close();\n  }\n  insertRelayButton.disabled = true;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/munge-sdp/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 20px 0;\n  vertical-align: top;\n  width: 155px;\n}\ndiv#buttons {\n  border-bottom: 1px solid #eee;\n  margin: 1em 0 1em 0;\n  padding: 0 0 1em 0;\n}\ndiv#local {\n  margin: 0 20px 0 0;\n}\ndiv#preview {\n  border-bottom: 1px solid #eee;\n  margin: 0 0 1em 0;\n  padding: 0 0 0.5em 0;\n}\ndiv#preview > div {\n  display: inline-block;\n  vertical-align: top;\n  width: calc(50% - 12px);\n}\ndiv#select {\n  margin: 0 0 1em 0;\n}\ndiv#selectSource {\n  margin: 0 0 1em 0;\n}\ndiv.source {\n  display: inline-block;\n  margin: 0 0 1em 0;\n}\nform {\n  margin: 0 0 1em 0;\n  white-space: nowrap;\n}\nh2 {\n  margin: 0 0 0.5em 0;\n}\nlabel {\n  margin: 0 0.4em 0 0;\n}\ntextarea {\n  color: #444;\n  font-size: 0.9em;\n  font-weight: 300;\n  height: 7.0em;\n  padding: 5px;\n  width: calc(100% - 10px);\n}\nvideo {\n  height: 225px;\n}\n\n@media screen and (max-width: 550px) {\n  button {\n    font-weight: 500;\n    height: 56px;\n    line-height: 1.3em;\n    margin: 0 7px 15px 0;\n    width: 86px;\n  }\n  button:nth-child(3n+0) {\n    margin: 0 0 15px 0;\n  }\n  video {\n    height: 96px;\n  }\n}\n\n@media screen and (max-width: 800px) {\n  button {\n    margin: 0 15px 15px 0;\n    width: 155px;\n  }\n  div#selectSource {\n    margin: 0 0 0.5em 0;\n  }\n  div.source {\n    margin: 0 0 .2em 0;\n  }\n  select {\n    margin: 0 1.5em 0 0;\n  }\n  textarea {\n    font-size: 0.7em;\n  }\n}\n\n@media screen and (max-width: 500px) {\n  textarea {\n    font-size: 0.5em;\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/munge-sdp/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Munge SDP</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Munge SDP</span>\n    </h1>\n\n    <div id=\"buttons\">\n        <button id=\"getMedia\">Get media</button>\n        <button id=\"createPeerConnection\" disabled>Create peer connection</button>\n        <button id=\"createOffer\" disabled>Create offer</button>\n        <button id=\"setOffer\" disabled>Set offer</button>\n        <button id=\"createAnswer\" disabled>Create answer</button>\n        <button id=\"setAnswer\" disabled>Set answer</button>\n        <button id=\"hangup\" disabled>Hang up</button>\n    </div>\n    <p><b>Note:</b> SDP \"munging\", i.e. modifying the SDP between <i>createOffer</i>/<i>createAnswer</i> and <i>setLocalDescription</i>\n      is a nonstandard feature which may not work as expected. While some browsers support some modifications,\n      <a href=\"https://w3c.github.io/webrtc-pc/#dom-peerconnection-setlocaldescription\">the W3C standard</a> forbids it.</p>\n    <p id=\"munge-error\"></p>\n\n    <div id=\"preview\">\n        <div id=\"local\">\n            <h2>Local</h2>\n            <video playsinline autoplay muted></video>\n            <h2>Offer SDP</h2>\n            <textarea></textarea>\n            <br>\n            <br>\n        </div>\n        <div id=\"remote\">\n            <h2>Remote</h2>\n            <video playsinline autoplay></video>\n            <h2>Answer SDP</h2>\n            <textarea></textarea>\n        </div>\n    </div>\n\n    <p>View the console to see logging.</p>\n\n    <p>The <code>RTCPeerConnection</code> objects <code>pc1</code> and <code>pc2</code>\n        are in global scope, so you can inspect them in the console as well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a\n            href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcpeerconnection\"\n            title=\"RTCPeerConnection section of HTML5 Rocks article about WebRTC\">Getting Started With WebRTC</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/munge-sdp\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/munge-sdp/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst getMediaButton = document.querySelector('button#getMedia');\nconst createPeerConnectionButton = document.querySelector('button#createPeerConnection');\nconst createOfferButton = document.querySelector('button#createOffer');\nconst setOfferButton = document.querySelector('button#setOffer');\nconst createAnswerButton = document.querySelector('button#createAnswer');\nconst setAnswerButton = document.querySelector('button#setAnswer');\nconst hangupButton = document.querySelector('button#hangup');\nlet dataChannelDataReceived;\n\ngetMediaButton.onclick = getMedia;\ncreatePeerConnectionButton.onclick = createPeerConnection;\ncreateOfferButton.onclick = createOffer;\nsetOfferButton.onclick = setOffer;\ncreateAnswerButton.onclick = createAnswer;\nsetAnswerButton.onclick = setAnswer;\nhangupButton.onclick = hangup;\n\nconst offerSdpTextarea = document.querySelector('div#local textarea');\nconst answerSdpTextarea = document.querySelector('div#remote textarea');\n\nconst localVideo = document.querySelector('div#local video');\nconst remoteVideo = document.querySelector('div#remote video');\n\nlet pc1;\nlet pc2;\nlet localStream;\nlet sendChannel;\nlet receiveChannel;\nconst dataChannelOptions = {ordered: true};\nlet dataChannelCounter = 0;\nlet sendDataLoop;\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nasync function getMedia() {\n  getMediaButton.disabled = true;\n\n  if (localStream) {\n    localVideo.srcObject = null;\n    localStream.getTracks().forEach(track => track.stop());\n  }\n  console.log('Requesting local stream');\n  try {\n    const userMedia = await navigator.mediaDevices.getUserMedia({audio: true, video: true});\n    gotStream(userMedia);\n  } catch (e) {\n    console.log('navigator.getUserMedia error: ', e);\n  }\n}\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  localVideo.srcObject = stream;\n  localStream = stream;\n  createPeerConnectionButton.disabled = false;\n}\n\nfunction createPeerConnection() {\n  createPeerConnectionButton.disabled = true;\n  createOfferButton.disabled = false;\n  console.log('Starting call');\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n\n  pc1 = new RTCPeerConnection();\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  sendChannel = pc1.createDataChannel('sendDataChannel', dataChannelOptions);\n  sendChannel.onopen = onSendChannelStateChange;\n  sendChannel.onclose = onSendChannelStateChange;\n  sendChannel.onerror = onSendChannelStateChange;\n\n  pc2 = new RTCPeerConnection();\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n  pc2.ondatachannel = receiveChannelCallback;\n\n  localStream.getTracks()\n      .forEach(track => pc1.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n}\n\nfunction onSetSessionDescriptionSuccess() {\n  console.log('Set session description success.');\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n  document.getElementById('munge-error').innerText = error.toString();\n}\n\nasync function createOffer() {\n  try {\n    const offer = await pc1.createOffer(offerOptions);\n    gotDescription1(offer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nasync function setOffer() {\n  // Restore the SDP from the textarea. Ensure we use CRLF which is what is generated\n  // even though https://tools.ietf.org/html/rfc4566#section-5 requires\n  // parsers to handle both LF and CRLF.\n  const sdp = offerSdpTextarea.value\n      .split('\\n')\n      .map(l => l.trim())\n      .join('\\r\\n');\n  const offer = {\n    type: 'offer',\n    sdp: sdp\n  };\n  console.log(`Modified Offer from pc1\\n${sdp}`);\n\n  try {\n    await pc1.setLocalDescription(offer);\n    onSetSessionDescriptionSuccess();\n    setOfferButton.disabled = true;\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n    return;\n  }\n\n  try {\n    await pc2.setRemoteDescription(offer);\n    onSetSessionDescriptionSuccess();\n    createAnswerButton.disabled = false;\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n    return;\n  }\n}\n\nfunction gotDescription1(description) {\n  offerSdpTextarea.disabled = false;\n  offerSdpTextarea.value = description.sdp;\n  createOfferButton.disabled = true;\n  setOfferButton.disabled = false;\n}\n\nasync function createAnswer() {\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  try {\n    const answer = await pc2.createAnswer();\n    gotDescription2(answer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nasync function setAnswer() {\n  setAnswerButton.disabled = false;\n  // Restore the SDP from the textarea. Ensure we use CRLF which is what is generated\n  // even though https://tools.ietf.org/html/rfc4566#section-5 requires\n  // parsers to handle both LF and CRLF.\n  const sdp = answerSdpTextarea.value\n      .split('\\n')\n      .map(l => l.trim())\n      .join('\\r\\n');\n  const answer = {\n    type: 'answer',\n    sdp: sdp\n  };\n\n  try {\n    // eslint-disable-next-line no-unused-vars\n    const ignore = await pc2.setLocalDescription(answer);\n    onSetSessionDescriptionSuccess();\n    setAnswerButton.disabled = true;\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n    return;\n  }\n\n  console.log(`Modified Answer from pc2\\n${sdp}`);\n  try {\n    // eslint-disable-next-line no-unused-vars\n    const ignore = await pc1.setRemoteDescription(answer);\n    onSetSessionDescriptionSuccess();\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n    return;\n  }\n  hangupButton.disabled = false;\n  createOfferButton.disabled = false;\n}\n\nfunction gotDescription2(description) {\n  answerSdpTextarea.disabled = false;\n  answerSdpTextarea.value = description.sdp;\n  createAnswerButton.disabled = true;\n  setAnswerButton.disabled = false;\n}\n\nfunction sendData() {\n  if (sendChannel.readyState === 'open') {\n    sendChannel.send(dataChannelCounter);\n    console.log(`DataChannel send counter: ${dataChannelCounter}`);\n    dataChannelCounter++;\n  }\n}\n\nfunction hangup() {\n  remoteVideo.srcObject = null;\n  console.log('Ending call');\n  localStream.getTracks().forEach(track => track.stop());\n  sendChannel.close();\n  if (receiveChannel) {\n    receiveChannel.close();\n  }\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  offerSdpTextarea.disabled = true;\n  answerSdpTextarea.disabled = true;\n  getMediaButton.disabled = false;\n  createPeerConnectionButton.disabled = true;\n  createOfferButton.disabled = true;\n  setOfferButton.disabled = true;\n  createAnswerButton.disabled = true;\n  setAnswerButton.disabled = true;\n  hangupButton.disabled = true;\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('Received remote stream');\n  }\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nasync function onIceCandidate(pc, event) {\n  try {\n    // eslint-disable-next-line no-unused-vars\n    const ignore = await getOtherPc(pc).addIceCandidate(event.candidate);\n    onAddIceCandidateSuccess(pc);\n  } catch (e) {\n    onAddIceCandidateError(pc, e);\n  }\n\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log(`Failed to add Ice Candidate: ${error.toString()}`);\n}\n\nfunction receiveChannelCallback(event) {\n  console.log('Receive Channel Callback');\n  receiveChannel = event.channel;\n  receiveChannel.onmessage = onReceiveMessageCallback;\n  receiveChannel.onopen = onReceiveChannelStateChange;\n  receiveChannel.onclose = onReceiveChannelStateChange;\n}\n\nfunction onReceiveMessageCallback(event) {\n  dataChannelDataReceived = event.data;\n  console.log(`DataChannel receive counter: ${dataChannelDataReceived}`);\n}\n\nfunction onSendChannelStateChange() {\n  const readyState = sendChannel.readyState;\n  console.log(`Send channel state is: ${readyState}`);\n  if (readyState === 'open') {\n    sendDataLoop = setInterval(sendData, 1000);\n  } else {\n    clearInterval(sendDataLoop);\n  }\n}\n\nfunction onReceiveChannelStateChange() {\n  const readyState = receiveChannel.readyState;\n  console.log(`Receive channel state is: ${readyState}`);\n}\n"
  },
  {
    "path": "src/content/peerconnection/munge-sdp/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/munge-sdp/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection sdp munging', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection and allows hangup and new offer', async () => {\n    await driver.findElement(webdriver.By.id('getMedia')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('createPeerConnection')).isEnabled());\n    await driver.findElement(webdriver.By.id('createPeerConnection')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('createOffer')).isEnabled());\n    await driver.findElement(webdriver.By.id('createOffer')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('setOffer')).isEnabled());\n    await driver.findElement(webdriver.By.id('setOffer')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('createAnswer')).isEnabled());\n    await driver.findElement(webdriver.By.id('createAnswer')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('setAnswer')).isEnabled());\n    await driver.findElement(webdriver.By.id('setAnswer')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('hangup')).isEnabled());\n    await driver.wait(() => driver.findElement(webdriver.By.id('createOffer')).isEnabled());\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n  });\n\n  // TODO: add test to ensure the text fields are properly filled\n});\n"
  },
  {
    "path": "src/content/peerconnection/negotiate-timing/css/main.css",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/negotiate-timing/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection - Renegotiate</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection negotiation timing</span></h1>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div>\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"renegotiateButton\">Renegotiate</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n    <div>\n      <p>\n        Video sections after renegotiating: <input type=\"number\" id=\"videoSections\" value=\"5\">\n      </p>\n    </div>\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as\n        well.</p>\n    <p>\n      <div id=\"log\">\n        Log goes here\n      </div>\n    </p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/negotiate-timing\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/negotiate-timing/js/main.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst renegotiateButton = document.getElementById('renegotiateButton');\nconst hangupButton = document.getElementById('hangupButton');\nconst log = document.getElementById('log');\nconst videoSectionsField = document.getElementById('videoSections');\n\ncallButton.disabled = true;\nhangupButton.disabled = true;\nrenegotiateButton.disabled = true;\nstartButton.onclick = start;\ncallButton.onclick = call;\nrenegotiateButton.onclick = renegotiate;\nhangupButton.onclick = hangup;\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlet audioTransceiver;\nlet audioImpairmentAtStart = 0;\n\nlet result;\n\n// Allows configuration of bundlePolicy et al.\n// eslint-disable-next-line prefer-const\nlet configuration = null;\n\n// Preferring a certain codec is an expert option without GUI.\n// eslint-disable-next-line prefer-const\nlet preferredVideoCodecMimeType = undefined; // e.g. 'video/VP8'\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.onresize = () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  console.warn('RESIZE', remoteVideo.videoWidth, remoteVideo.videoHeight);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log(`Setup time: ${elapsedTime.toFixed(3)}ms`);\n    startTime = null;\n  }\n};\n\nlet localStream;\nlet pc1;\nlet pc2;\n\nfunction logToScreen(text) {\n  log.append(document.createElement('br'));\n  log.append(text);\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  const stream = await navigator.mediaDevices\n      .getUserMedia({\n        audio: true,\n        video: true\n      });\n  console.log('Received local stream');\n  localVideo.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nasync function runOfferAnswer() {\n  const startTime = performance.now();\n  const result = {};\n  const offer = await pc1.createOffer();\n  const markTime1 = performance.now();\n  result.callerCreateOffer = markTime1 - startTime;\n  await pc1.setLocalDescription(offer);\n  const markTime2 = performance.now();\n  result.callerSetLocalDescription = markTime2 - markTime1;\n  await pc2.setRemoteDescription(offer);\n  const markTime3 = performance.now();\n  result.calleeSetRemoteDescription = markTime3 - markTime2;\n  const answer = await pc2.createAnswer();\n  const markTime4 = performance.now();\n  result.calleeCreateAnswer = markTime4 - markTime3;\n  await pc1.setRemoteDescription(answer);\n  const markTime5 = performance.now();\n  result.callerSetRemoteDescription = markTime5 - markTime4;\n  await pc2.setLocalDescription(answer);\n  const markTime6 = performance.now();\n  result.calleeSetLocalDescription = markTime6 - markTime5;\n  result.elapsedTime = markTime6 - startTime;\n  return result;\n}\n\nasync function call() {\n  callButton.disabled = true;\n  renegotiateButton.disabled = false;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const audioTracks = localStream.getAudioTracks();\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  pc1 = new RTCPeerConnection(configuration);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(configuration);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);\n  pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);\n  pc2.addEventListener('track', gotRemoteStream, {once: true});\n  if (preferredVideoCodecMimeType) {\n    pc2.ontrack = (e) => {\n      if (e.track.kind === 'video') {\n        const {codecs} = RTCRtpReceiver.getCapabilities('video');\n        const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredVideoCodecMimeType);\n        const selectedCodec = codecs[selectedCodecIndex];\n        codecs.splice(selectedCodecIndex, 1);\n        codecs.unshift(selectedCodec);\n        e.transceiver.setCodecPreferences(codecs);\n      }\n    };\n  }\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Added local stream to pc1');\n\n  await runOfferAnswer();\n  console.log('Initial negotiation complete');\n}\n\nfunction gotRemoteStream(e) {\n  console.log('gotRemoteStream', e.track, e.streams[0]);\n  if (e.streams[0]) {\n    // reset srcObject to work around minor bugs in Chrome and Edge.\n    remoteVideo.srcObject = null;\n    remoteVideo.srcObject = e.streams[0];\n  }\n}\n\nasync function onIceCandidate(pc, event) {\n  if (event.candidate) {\n    console.log(`${getName(pc)} emitted ICE candidate for index ${event.candidate.sdpMLineIndex}:\\n${event.candidate.candidate}`);\n  } else {\n    console.log(`${getName(pc)} ICE NULL candidate`);\n  }\n  await getOtherPc(pc).addIceCandidate(event.candidate);\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event, state: ', pc.iceConnectionState);\n  }\n}\n\nfunction adjustTransceiverCounts(pc, videoCount) {\n  const currentVideoTransceivers = pc.getTransceivers().filter(tr => tr.receiver.track.kind == 'video');\n  const currentVideoCount = currentVideoTransceivers.length;\n  if (currentVideoCount < videoCount) {\n    console.log('Adding ' + (videoCount - currentVideoCount) + ' transceivers');\n    for (let i = currentVideoCount; i < videoCount; ++i) {\n      pc.addTransceiver('video');\n    }\n  } else if (currentVideoCount > videoCount) {\n    console.log('Stopping ' + (currentVideoCount - videoCount) + ' transceivers');\n    for (let i = videoCount; i < currentVideoCount; ++i) {\n      currentVideoTransceivers[i].stop();\n    }\n  } else {\n    console.log(`No adjustment, video count is ${currentVideoCount}, target was ${videoCount}`);\n  }\n}\n\nasync function getAudioImpairment(audioTransceiver) {\n  const stats = await audioTransceiver.receiver.getStats();\n  let currentImpairment;\n  stats.forEach(stat => {\n    if (stat.type == 'inbound-rtp') {\n      currentImpairment = stat.concealedSamples;\n    }\n  });\n  console.log('Found impairment value ', currentImpairment);\n  return currentImpairment;\n}\n\nasync function baselineAudioImpairment(pc) {\n  audioTransceiver = pc.getTransceivers().find(tr => tr.receiver.track.kind == 'audio');\n  console.log('Found audio transceiver');\n  audioImpairmentAtStart = await getAudioImpairment(audioTransceiver);\n}\n\nasync function measureAudioImpairment(pc) {\n  const startTime = performance.now();\n  const audioImpairmentNow = await getAudioImpairment(audioTransceiver);\n  console.log('Measurement took ' + (performance.now() - startTime) + ' msec');\n  return audioImpairmentNow - audioImpairmentAtStart;\n}\n\n\nasync function renegotiate() {\n  renegotiateButton.disabled = true;\n  adjustTransceiverCounts(pc1, parseInt(videoSectionsField.value));\n  await baselineAudioImpairment(pc2);\n  const previousVideoTransceiverCount = pc2.getTransceivers().filter(tr => tr.receiver.track.kind == 'video').length;\n  result = await runOfferAnswer();\n  console.log(`Renegotiate finished after ${result.elapsedTime} milliseconds`);\n  const currentVideoTransceiverCount = pc2.getTransceivers().filter(tr => tr.receiver.track.kind == 'video').length;\n  result.audioImpairment = await measureAudioImpairment(pc2);\n  logToScreen(`Negotiation from ${previousVideoTransceiverCount} to ${currentVideoTransceiverCount} video transceivers took ${result.elapsedTime.toFixed(2)} milliseconds, audio impairment ${result.audioImpairment}`);\n  console.log('Results: ', JSON.stringify(result, ' ', 2));\n  renegotiateButton.disabled = false;\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n\n  console.log('Releasing camera');\n  const videoTracks = localStream.getVideoTracks();\n  videoTracks.forEach(videoTrack => {\n    videoTrack.stop();\n    localStream.removeTrack(videoTrack);\n  });\n  localVideo.srcObject = null;\n\n  hangupButton.disabled = true;\n  callButton.disabled = true;\n  renegotiateButton.disabled = true;\n  startButton.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/negotiate-timing/js/test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/negotiate-timing/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection with negotiation timing', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection, renegotiates and hangs up', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('remoteVideo').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('renegotiateButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('renegotiateButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('log').innerText !== 'Log goes here';\n    }));\n    const logText = await driver.findElement(webdriver.By.id('log')).getAttribute('innerText');\n    expect(logText.split('\\n').length).toBe(2);\n\n    await driver.findElement(webdriver.By.id('hangupButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return pc1 === null; // eslint-disable-line no-undef\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/pc1/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/pc1/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <p>This sample shows how to setup a connection between two peers using\n        <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection</a>.\n    </p>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div class=\"box\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as\n        well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/pc1\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/pc1/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.addEventListener('click', start);\ncallButton.addEventListener('click', call);\nhangupButton.addEventListener('click', hangup);\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('resize', () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight} - Time since pageload ${performance.now().toFixed(0)}ms`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');\n    startTime = null;\n  }\n});\n\nlet localStream;\nlet pc1;\nlet pc2;\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nasync function start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});\n    console.log('Received local stream');\n    localVideo.srcObject = stream;\n    localStream = stream;\n    callButton.disabled = false;\n  } catch (e) {\n    alert(`getUserMedia() error: ${e.name}`);\n  }\n}\n\nasync function call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const configuration = {};\n  console.log('RTCPeerConnection configuration:', configuration);\n  pc1 = new RTCPeerConnection(configuration);\n  console.log('Created local peer connection object pc1');\n  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));\n  pc2 = new RTCPeerConnection(configuration);\n  console.log('Created remote peer connection object pc2');\n  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));\n  pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));\n  pc2.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2, e));\n  pc2.addEventListener('track', gotRemoteStream);\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Added local stream to pc1');\n\n  try {\n    console.log('pc1 createOffer start');\n    const offer = await pc1.createOffer(offerOptions);\n    await onCreateOfferSuccess(offer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nasync function onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  try {\n    await pc1.setLocalDescription(desc);\n    onSetLocalSuccess(pc1);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 setRemoteDescription start');\n  try {\n    await pc2.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError();\n  }\n\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  try {\n    const answer = await pc2.createAnswer();\n    await onCreateAnswerSuccess(answer);\n  } catch (e) {\n    onCreateSessionDescriptionError(e);\n  }\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('pc2 received remote stream');\n  }\n}\n\nasync function onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  try {\n    await pc2.setLocalDescription(desc);\n    onSetLocalSuccess(pc2);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n  console.log('pc1 setRemoteDescription start');\n  try {\n    await pc1.setRemoteDescription(desc);\n    onSetRemoteSuccess(pc1);\n  } catch (e) {\n    onSetSessionDescriptionError(e);\n  }\n}\n\nasync function onIceCandidate(pc, event) {\n  try {\n    await (getOtherPc(pc).addIceCandidate(event.candidate));\n    onAddIceCandidateSuccess(pc);\n  } catch (e) {\n    onAddIceCandidateError(pc, e);\n  }\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n  }\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/pc1/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/pc1/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('simple peerconnection', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection and hangs up', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('remoteVideo').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n\n    await driver.findElement(webdriver.By.id('hangupButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return pc1 === null; // eslint-disable-line no-undef\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/per-frame-callback/css/main.css",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n\nbutton {\n  margin: 0 20px 0 0;\n  width: 96px;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.label {\n  display: inline-block;\n  font-weight: 400;\n  width: 120px;\n}\n\ndiv.graph-container {\n  float: left;\n  margin: 0.5em;\n  width: calc(50% - 1em);\n}\n\na#viewSource {\n  clear: both;\n}\n"
  },
  {
    "path": "src/content/peerconnection/per-frame-callback/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection and requestVideoFrameCallback()</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link href=\"https://fonts.googleapis.com/css?family=Inconsolata\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection and requestVideoFrameCallback()</span>\n    </h1>\n\n    <div id=\"notsupported\" style=\"display:none\">\n      requestVideoAnimationCallback is not supported in your browser.\n    </div>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div id=\"buttons\">\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n    <div class=\"graph-container\" id=\"localDelayGraph\">\n        <div>Local capture delay (converted to milliseconds)</div>\n        <canvas id=\"localDelayCanvas\"></canvas>\n    </div>\n    <div class=\"graph-container\" id=\"networkDelayGraph\">\n        <div>Network delay (milliseconds)</div>\n        <canvas id=\"networkDelayCanvas\"></canvas>\n    </div>\n    <br/>\n    <div class=\"graph-container\" id=\"timeGraph\">\n        <div>Render delay (milliseconds)</div>\n        <canvas id=\"timeCanvas\"></canvas>\n    </div>\n    <div class=\"graph-container\" id=\"processingGraph\">\n        <div>Processing time (converted to milliseconds)</div>\n        <canvas id=\"processingCanvas\"></canvas>\n    </div>\n    <p>For more information about requestVideoFrameCallback, see <a href=\"https://web.dev/requestvideoframecallback-rvfc/\"\n                                                            title=\"Perform efficient per-video-frame operations on video with requestVideoFrameCallback()\">Perform efficient per-video-frame operations on video with requestVideoFrameCallback()</a>.</p>\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/per-frame-callback\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/third_party/graph.js\"></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/per-frame-callback/js/main.js",
    "content": "/*\n *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* global TimelineDataSeries, TimelineGraphView */\n\n'use strict';\n\nconst remoteVideo = document.querySelector('video#remoteVideo');\nconst localVideo = document.querySelector('video#localVideo');\nconst callButton = document.querySelector('button#callButton');\nconst hangupButton = document.querySelector('button#hangupButton');\nconst bandwidthSelector = document.querySelector('select#bandwidth');\nhangupButton.disabled = true;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\nif (!('requestVideoFrameCallback' in HTMLVideoElement.prototype)) {\n  document.getElementById('notsupported').style.display = 'block';\n  callButton.disabled = true;\n}\n\nlet localDelayGraph;\nlet localDelaySeries;\n\nlet maxLocalDelay = -1;\nlocalVideo.requestVideoFrameCallback(function rVFC(now, metaData) {\n  // For graph purposes, take the maximum over a window.\n  maxLocalDelay = Math.max(1000 * (metaData.expectedDisplayTime - metaData.captureTime), maxLocalDelay);\n\n  if (metaData.presentedFrames % windowSize !== 0) {\n    localVideo.requestVideoFrameCallback(rVFC);\n    return;\n  }\n  // The graph library does not like the performance.now() style `now`.\n  localDelaySeries.addPoint(Date.now(), maxLocalDelay);\n  localDelayGraph.setDataSeries([localDelaySeries]);\n  localDelayGraph.updateEndDate();\n\n  maxLocalDelay = -1;\n\n  localVideo.requestVideoFrameCallback(rVFC);\n});\n\nlet processingGraph;\nlet processingSeries;\n\nlet timeGraph;\nlet timeSeries;\n\nlet networkDelayGraph;\nlet networkDelaySeries;\n\nlet maxProcessingDuration = -1;\nlet maxRenderTime = -1;\nlet maxNetworkDelay = -1;\nconst windowSize = 30;\nremoteVideo.requestVideoFrameCallback(function rVFC(now, metaData) {\n  // For graph purposes, take the maximum over a window.\n  maxProcessingDuration = Math.max(1000 * metaData.processingDuration, maxProcessingDuration);\n  maxRenderTime = Math.max(metaData.expectedDisplayTime - metaData.receiveTime, maxRenderTime);\n  // Note: captureTime is currently only present when there are bidirectional streams.\n  maxNetworkDelay = Math.max(metaData.receiveTime - metaData.captureTime, maxNetworkDelay);\n\n  if (metaData.presentedFrames % windowSize !== 0) {\n    remoteVideo.requestVideoFrameCallback(rVFC);\n    return;\n  }\n  // The graph library does not like the performance.now() style `now`.\n  processingSeries.addPoint(Date.now(), maxProcessingDuration);\n  processingGraph.setDataSeries([processingSeries]);\n  processingGraph.updateEndDate();\n\n  timeSeries.addPoint(Date.now(), maxRenderTime);\n  timeGraph.setDataSeries([timeSeries]);\n  timeGraph.updateEndDate();\n\n  networkDelaySeries.addPoint(Date.now(), maxNetworkDelay);\n  networkDelayGraph.setDataSeries([networkDelaySeries]);\n  networkDelayGraph.updateEndDate();\n\n  maxProcessingDuration = -1;\n  maxRenderTime = -1;\n  maxNetworkDelay = -1;\n  maxLocalDelay = -1;\n\n  remoteVideo.requestVideoFrameCallback(rVFC);\n});\n\n// Mostly copied from pc1/bandwidth sample.\nlet pc1;\nlet pc2;\nlet localStream;\n\nfunction gotStream(stream) {\n  hangupButton.disabled = false;\n  console.log('Received local stream');\n  localStream = stream;\n  localVideo.srcObject = stream;\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  // Currently the captureTime on the remote end requires bidirectional video.\n  localStream.getTracks().forEach(track => pc2.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n\n  pc1.createOffer().then(\n      gotDescription1,\n      onCreateSessionDescriptionError\n  );\n\n  processingSeries = new TimelineDataSeries();\n  processingGraph = new TimelineGraphView('processingGraph', 'processingCanvas');\n  processingGraph.updateEndDate();\n\n  timeSeries = new TimelineDataSeries();\n  timeGraph = new TimelineGraphView('timeGraph', 'timeCanvas');\n  timeGraph.updateEndDate();\n\n  networkDelaySeries = new TimelineDataSeries();\n  networkDelayGraph = new TimelineGraphView('networkDelayGraph', 'networkDelayCanvas');\n  networkDelayGraph.updateEndDate();\n\n  localDelaySeries = new TimelineDataSeries();\n  localDelayGraph = new TimelineGraphView('localDelayGraph', 'localDelayCanvas');\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log('Failed to create session description: ' + error.toString());\n}\n\nfunction call() {\n  callButton.disabled = true;\n  console.log('Starting call');\n  pc1 = new RTCPeerConnection();\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = onIceCandidate.bind(pc1);\n\n  pc2 = new RTCPeerConnection();\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = onIceCandidate.bind(pc2);\n  pc2.ontrack = gotRemoteStream;\n\n  console.log('Requesting local stream');\n  navigator.mediaDevices.getUserMedia({video: true})\n      .then(gotStream)\n      .catch(e => alert('getUserMedia() error: ' + e.name));\n}\n\nfunction gotDescription1(desc) {\n  console.log('Offer from pc1 \\n' + desc.sdp);\n  pc1.setLocalDescription(desc).then(\n      () => {\n        pc2.setRemoteDescription(desc)\n            .then(() => pc2.createAnswer().then(gotDescription2, onCreateSessionDescriptionError),\n                onSetSessionDescriptionError);\n      }, onSetSessionDescriptionError\n  );\n}\n\nfunction gotDescription2(desc) {\n  pc2.setLocalDescription(desc).then(\n      () => {\n        console.log('Answer from pc2 \\n' + desc.sdp);\n        return pc1.setRemoteDescription(desc);\n      },\n      onSetSessionDescriptionError\n  );\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  localStream.getTracks().forEach(track => track.stop());\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n  bandwidthSelector.disabled = true;\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('Received remote stream');\n  }\n}\n\nfunction getOtherPc(pc) {\n  return pc === pc1 ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return pc === pc1 ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(event) {\n  getOtherPc(this)\n      .addIceCandidate(event.candidate)\n      .then(onAddIceCandidateSuccess)\n      .catch(onAddIceCandidateError);\n\n  console.log(`${getName(this)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log('Failed to add ICE Candidate: ' + error.toString());\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log('Failed to set session description: ' + error.toString());\n}\n"
  },
  {
    "path": "src/content/peerconnection/perfect-negotiation/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\ndiv.box {\n  margin: 1em;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/perfect-negotiation/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC Javascript code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Perfect Negotiation</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Perfect Negotiation</span></h1>\n\n    <p>This sample shows how to setup a connection between two peers using\n        <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection\">RTCPeerConnection</a>\n        with the <a href=\"https://w3c.github.io/webrtc-pc/#perfect-negotiation-example\">Perfect Negotiation</a>\n        usage pattern.</p>\n\n    <p>Perfect Negotiation supports both endpoints sending offers. The pattern intelligently handles the\n        situation of \"glare\" (both peers making an offer at the same time, causing a collision) by having\n        one peer be \"polite\" and the other peer be \"impolite\". In the event of an offer collision, the\n        polite peer rolls back its offer in order to process the impolite peer's incoming offer. Once\n        back to the stable signaling state, the polite peer's <code>onnegotiationneeded</code> fires again and a\n        follow-up O/A is completed.</p>\n\n    <p>Click both peers' Start button to create local streams.\n        Then press the Swap Sending Track button to modify which transceiver is sending;\n        this will be negotiated and displayed as a remote track on the other peer's iframe.</p>\n\n    <div id=\"iframes\">\n        <iframe id=\"polite\"></iframe>\n        <iframe id=\"impolite\"></iframe>\n    </div>\n\n    <div id=\"buttons\">\n        <button onclick=\"swapOnBoth(true)\">Swap on both (glare): Polite to offer first</button>\n        <button onclick=\"swapOnBoth(false)\">Swap on both (glare): Impolite to offer first</button>\n    </div>\n\n    <p>The JavaScript console shows logs for the negotiation steps.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/perfect-negotiation\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" type=\"module\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/perfect-negotiation/js/main.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nimport {peer} from './peer.js';\n\nconst politeIframe = document.getElementById('polite');\nconst impoliteIframe = document.getElementById('impolite');\nlet counter = 0;\n\n/**\n * @param {Window} target\n * @param {string} cmd\n */\nexport async function run(target, cmd) {\n  const id = `result${counter++}`;\n  target.postMessage({run: {cmd, id}}, '*');\n  return new Promise(resolve => void window.addEventListener('message', function listen({data}) {\n    if (!(id in data)) return;\n    window.removeEventListener('message', listen);\n    resolve(data[id]);\n  }));\n}\n\n/**\n * @param {number} r1 Video 1 Red channel level [0-1]\n * @param {number} g1 Video 1 Green channel level [0-1]\n * @param {number} b1 Video 1 Blue channel level [0-1]\n * @param {number} r2 Video 2 Red channel level [0-1]\n * @param {number} g2 Video 2 Green channel level [0-1]\n * @param {number} b2 Video 2 Blue channel level [0-1]\n */\nexport function startLocalVideo(r1, g1, b1, r2, g2, b2) {\n  const whiteNoise = (width, height, r, g, b) => {\n    const canvas = Object.assign(document.createElement('canvas'), {width, height});\n    const ctx = canvas.getContext('2d');\n    ctx.fillRect(0, 0, width, height);\n    const p = ctx.getImageData(0, 0, width, height);\n    const draw = () => {\n      for (let i = 0; i < p.data.length; i++) {\n        const color = Math.random() * 255;\n        p.data[i++] = color * r;\n        p.data[i++] = color * g;\n        p.data[i++] = color * b;\n      }\n      ctx.putImageData(p, 0, 0);\n      requestAnimationFrame(draw);\n    };\n    requestAnimationFrame(draw);\n    return canvas.captureStream();\n  };\n  const localVideo1 = document.getElementById('localVideo1');\n  localVideo1.srcObject = whiteNoise(32, 32, r1, g1, b1);\n  localVideo1.play();\n  const localVideo2 = document.getElementById('localVideo2');\n  localVideo2.srcObject = whiteNoise(32, 32, r2, g2, b2);\n  localVideo2.play();\n}\n\n/**\n * @param {HTMLIFrameElement} el\n * @param {boolean} polite\n * @param {number} r1 Video 1 Red channel level [0-1]\n * @param {number} g1 Video 1 Green channel level [0-1]\n * @param {number} b1 Video 1 Blue channel level [0-1]\n * @param {number} r2 Video 2 Red channel level [0-1]\n * @param {number} g2 Video 2 Green channel level [0-1]\n * @param {number} b2 Video 2 Blue channel level [0-1]\n */\nasync function setupIframe(el, polite, r1, g1, b1, r2, g2, b2) {\n  el.srcdoc = `<!DOCTYPE html>\n      <html>\n      <body>\n      <h3 style=\"font-family: 'Roboto', sans-serif; font-weight: 400;\">${polite ? 'Polite' : 'Impolite'} Peer's iframe</h3>\n      <div id=\"buttons\">\n        <button onclick=\"(${startLocalVideo.toString()})(${r1},${g1},${b1},${r2},${g2},${b2});\" id=\"${polite ? 'politeStart' : 'impoliteStart'}\">Start</button>\n        <button onclick=\"window.parent.run(window, 'swapTransceivers');\">Swap Sending Track</button>\n      </div>\n      <p id=\"videos\">\n        <video id=\"localVideo1\" autoplay></video>\n        <video id=\"localVideo2\" autoplay></video>\n        <video id=\"remoteVideo\" autoplay></video>\n      </p>\n      </body>\n      <script>\n        (${peer.toString()})(window.parent.document.getElementById(\"${polite ? 'impolite' : 'polite'}\").contentWindow, ${polite});\n      </script>\n      <html>`;\n  await new Promise(resolve => el.onload = resolve);\n}\n\nexport async function swapOnBoth(politeFirst) { // eslint-disable-line no-unused-vars\n  if (politeFirst) {\n    run(politeIframe.contentWindow, 'swapTransceivers');\n    run(impoliteIframe.contentWindow, 'swapTransceivers');\n  } else {\n    run(impoliteIframe.contentWindow, 'swapTransceivers');\n    run(politeIframe.contentWindow, 'swapTransceivers');\n  }\n}\n\nasync function setupIframes() {\n  await setupIframe(politeIframe, true, 0, 1, 0, 0, 1, 1);\n  await setupIframe(impoliteIframe, false, 1, 0, 0, 1, 0, 1);\n}\n\nwindow.run = run;\nwindow.swapOnBoth = swapOnBoth;\nwindow.peer = peer;\nexport {peer};\n\nsetupIframes();\n"
  },
  {
    "path": "src/content/peerconnection/perfect-negotiation/js/peer.js",
    "content": "/*\n *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/**\n * Establishes a Peer Connection with `other` using the Perfect Negotiation pattern.\n * @param {Window} other\n * @param {boolean} polite\n * @param {function(Error): void} fail\n * @return {RTCPeerConnection} Peer Connection\n */\nfunction peer(other, polite, fail = undefined) { // eslint-disable-line no-unused-vars\n  if (!fail) fail = e => void send(window.parent, {error: `${e.name}: ${e.message}`});\n  const send = (target, msg) => void target.postMessage(JSON.parse(JSON.stringify(msg)), '*');\n  const log = str => void console.log(`[${polite ? 'POLITE' : 'IMPOLITE'}] ${str}`);\n  const assert_equals = !window.assert_equals ?\n      (a, b, msg) => a === b || void fail(new Error(`${msg} expected ${b} but got ${a}`)) :\n      window.assert_equals;\n  const pc = new RTCPeerConnection();\n  const localVideo1 = document.getElementById('localVideo1');\n  const localVideo2 = document.getElementById('localVideo2');\n  const remoteVideo = document.getElementById('remoteVideo');\n  const transceiversForSending = [];\n  const commands = {\n    async swapTransceivers() {\n      log('swapTransceivers');\n      const stream1 = localVideo1.srcObject;\n      const stream2 = localVideo2.srcObject;\n      if (transceiversForSending.length == 0) {\n        // This is the first time swapTransceivers is called.\n        // Add the initial transceivers, which are remembered for future swaps.\n        transceiversForSending.push(\n            pc.addTransceiver(stream1.getTracks()[0], {streams: [stream1], direction: 'sendonly'}));\n        transceiversForSending.push(\n            pc.addTransceiver('video', {streams: [stream2], direction: 'inactive'}));\n        return;\n      }\n      // We have sent before. Swap which transceiver is the sending one.\n      if (transceiversForSending[0].direction == 'sendonly') {\n        transceiversForSending[0].direction = 'inactive';\n        transceiversForSending[0].sender.replaceTrack(null);\n        transceiversForSending[1].direction = 'sendonly';\n        transceiversForSending[1].sender.replaceTrack(stream2.getTracks()[0]);\n      } else {\n        transceiversForSending[1].direction = 'inactive';\n        transceiversForSending[1].sender.replaceTrack(null);\n        transceiversForSending[0].direction = 'sendonly';\n        transceiversForSending[0].sender.replaceTrack(stream1.getTracks()[0]);\n      }\n    },\n  };\n  try {\n    pc.ontrack = e => {\n      log('ontrack');\n      remoteVideo.srcObject = new MediaStream();\n      remoteVideo.srcObject.addTrack(e.track);\n    };\n    pc.onicecandidate = ({candidate}) => void send(other, {candidate});\n    let makingOffer = false;\n    let ignoreOffer = false;\n    let srdAnswerPending = false;\n    pc.onnegotiationneeded = async () => {\n      try {\n        log('SLD due to negotiationneeded');\n        assert_equals(pc.signalingState, 'stable', 'negotiationneeded always fires in stable state');\n        assert_equals(makingOffer, false, 'negotiationneeded not already in progress');\n        makingOffer = true;\n        await pc.setLocalDescription();\n        assert_equals(pc.signalingState, 'have-local-offer', 'negotiationneeded not racing with onmessage');\n        assert_equals(pc.localDescription.type, 'offer', 'negotiationneeded SLD worked');\n        send(other, {description: pc.localDescription});\n      } catch (e) {\n        fail(e);\n      } finally {\n        makingOffer = false;\n      }\n    };\n    window.onmessage = async ({data: {description, candidate, run}}) => {\n      try {\n        if (description) {\n          // If we have a setRemoteDescription() answer operation pending, then\n          // we will be \"stable\" by the time the next setRemoteDescription() is\n          // executed, so we count this being stable when deciding whether to\n          // ignore the offer.\n          const isStable =\n              pc.signalingState == 'stable' ||\n              (pc.signalingState == 'have-local-offer' && srdAnswerPending);\n          ignoreOffer =\n              description.type == 'offer' && !polite && (makingOffer || !isStable);\n          if (ignoreOffer) {\n            log('glare - ignoring offer');\n            return;\n          }\n          srdAnswerPending = description.type == 'answer';\n          log(`SRD(${description.type})`);\n          await pc.setRemoteDescription(description);\n          srdAnswerPending = false;\n          if (description.type == 'offer') {\n            assert_equals(pc.signalingState, 'have-remote-offer', 'Remote offer');\n            assert_equals(pc.remoteDescription.type, 'offer', 'SRD worked');\n            log('SLD to get back to stable');\n            await pc.setLocalDescription();\n            assert_equals(pc.signalingState, 'stable', 'onmessage not racing with negotiationneeded');\n            assert_equals(pc.localDescription.type, 'answer', 'onmessage SLD worked');\n            send(other, {description: pc.localDescription});\n          } else {\n            assert_equals(pc.remoteDescription.type, 'answer', 'Answer was set');\n            assert_equals(pc.signalingState, 'stable', 'answered');\n            pc.dispatchEvent(new Event('negotiated'));\n          }\n        } else if (candidate) {\n          try {\n            await pc.addIceCandidate(candidate);\n          } catch (e) {\n            if (!ignoreOffer) throw e;\n          }\n        } else if (run) {\n          send(window.parent, {[run.id]: await commands[run.cmd]() || 0});\n        }\n      } catch (e) {\n        fail(e);\n      }\n    };\n  } catch (e) {\n    fail(e);\n  }\n  return pc;\n}\n\nexport {peer};\n"
  },
  {
    "path": "src/content/peerconnection/pr-answer/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n\n    <title>PeerConnection PRANSWER Demo</title>\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n\n    <!-- Load the polyfill to switch-hit between Chrome and Firefox -->\n    <style>\n        video {\n            border: 5px solid black;\n            width: 320px;\n            height: 240px;\n        }\n    </style>\n</head>\n<body>\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Use pranswer when setting up a peer connection</span>\n    </h1>\n\n    <video id=\"vid1\" playsinline autoplay muted></video>\n    <video id=\"vid2\" playsinline autoplay></video>\n    <br>\n    <button id=\"callButton\">Call</button>\n    <button id=\"acceptButton\">Accept</button>\n    <button id=\"hangUpButton\">Hang Up</button>\n    <p>View the console to see logging and to inspect the <code>MediaStream</code> object <code>localStream</code>.</p>\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/pr-answer\" title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n\n"
  },
  {
    "path": "src/content/peerconnection/pr-answer/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst vid1 = document.getElementById('vid1');\nconst vid2 = document.getElementById('vid2');\nconst callButton = document.getElementById('callButton');\nconst acceptButton = document.getElementById('acceptButton');\nconst hangUpButton = document.getElementById('hangUpButton');\n\ncallButton.addEventListener('click', start);\nacceptButton.addEventListener('click', accept);\nhangUpButton.addEventListener('click', stop);\n\ncallButton.disabled = true;\nacceptButton.disabled = true;\nhangUpButton.disabled = true;\n\nlet pc1 = null;\nlet pc2 = null;\nlet localStream;\nconst remoteStream = new MediaStream();\n\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  vid1.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nnavigator.mediaDevices\n    .getUserMedia({\n      audio: true,\n      video: true\n    })\n    .then(gotStream)\n    .catch(e => alert(`getUserMedia() error: ${e}`));\n\nfunction start() {\n  callButton.disabled = true;\n  acceptButton.disabled = false;\n  hangUpButton.disabled = false;\n  console.log('Starting Call');\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using Video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using Audio device: ${audioTracks[0].label}`);\n  }\n\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n\n  pc1.createOffer(offerOptions).then(gotDescription1, onCreateSessionDescriptionError);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n  stop();\n}\n\nfunction onCreateAnswerError(error) {\n  console.log(`Failed to set createAnswer: ${error.toString()}`);\n  stop();\n}\n\nfunction onSetLocalDescriptionError(error) {\n  console.log(`Failed to set setLocalDescription: ${error.toString()}`);\n  stop();\n}\n\nfunction onSetLocalDescriptionSuccess() {\n  console.log('localDescription success.');\n}\n\nfunction gotDescription1(desc) {\n  pc1.setLocalDescription(desc).then(\n      onSetLocalDescriptionSuccess,\n      onSetLocalDescriptionError\n  );\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  pc2.setRemoteDescription(desc);\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  pc2.createAnswer().then(gotDescription2, onCreateSessionDescriptionError);\n}\n\nfunction gotDescription2(desc) {\n  // Provisional answer, set a=inactive & set sdp type to pranswer.\n  desc.sdp = desc.sdp.replace(/a=recvonly/g, 'a=inactive');\n  desc.type = 'pranswer';\n  pc2.setLocalDescription(desc).then(onSetLocalDescriptionSuccess, onSetLocalDescriptionError);\n  console.log(`Pranswer from pc2\\n${desc.sdp}`);\n  pc1.setRemoteDescription(desc);\n}\n\nfunction gotDescription3(desc) {\n  // Final answer, setting a=recvonly & sdp type to answer.\n  desc.sdp = desc.sdp.replace(/a=inactive/g, 'a=recvonly');\n  desc.type = 'answer';\n  pc2.setLocalDescription(desc).then(onSetLocalDescriptionSuccess, onSetLocalDescriptionError);\n  console.log(`Answer from pc2\\n${desc.sdp}`);\n  pc1.setRemoteDescription(desc);\n}\n\nfunction accept() {\n  pc2.createAnswer().then(gotDescription3, onCreateAnswerError);\n  acceptButton.disabled = true;\n  callButton.disabled = true;\n}\n\nfunction stop() {\n  console.log('Ending Call' + '\\n\\n');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  acceptButton.disabled = true;\n  callButton.disabled = false;\n  hangUpButton.disabled = true;\n}\n\nfunction gotRemoteStream(e) {\n  vid2.srcObject = remoteStream;\n  remoteStream.addTrack(e.track, remoteStream);\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log(`Failed to add Ice Candidate: ${error.toString()}`);\n}\n"
  },
  {
    "path": "src/content/peerconnection/restart-ice/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 100%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0;\n}\n\ndiv#video > div {\n  display: inline-block;\n  margin: 0 5px 0 0;\n  vertical-align: top;\n  width: calc(50% - 22px);\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/restart-ice/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>ICE Restart</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <div id=\"video\">\n        <div>\n            <video id=\"localVideo\" playsinline autoplay muted></video>\n            <label for=\"localCandidateId\">Local candidate id: </label>\n            <p id=\"localCandidateId\">Not connected.</p>\n        </div>\n        <div>\n            <video id=\"remoteVideo\" playsinline autoplay muted></video>\n            <label for=\"localCandidateId\">Remote candidate id: </label>\n            <p id=\"remoteCandidateId\">Not connected.</p>\n        </div>\n    </div>\n\n    <div>\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"restartButton\">Restart ICE</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can\n        inspect them in the console as well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/restart-ice\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/restart-ice/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst restartButton = document.getElementById('restartButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nrestartButton.disabled = true;\nstartButton.onclick = start;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\nrestartButton.onclick = restart;\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nconst useSelectedCandidatePairChange = window.RTCIceTransport && 'onselectedcandidatepairchange' in RTCIceTransport.prototype;\n\nremoteVideo.onresize = () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');\n    startTime = null;\n    // Have run these functions again in order to get the getStats() reports\n    // with type candidatePair and populate the candidate id\n    // elements.\n    checkStats(pc1);\n    checkStats(pc2);\n  }\n};\n\nlet localStream;\nlet pc1;\nlet pc2;\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  localVideo.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  navigator.mediaDevices\n      .getUserMedia({\n        audio: true,\n        video: true\n      })\n      .then(gotStream)\n      .catch(e => alert(`getUserMedia() error: ${e.name}`));\n}\n\n// Simulate an ice restart.\nfunction restart() {\n  restartButton.disabled = true;\n  offerOptions.iceRestart = true;\n  console.log('pc1 createOffer restart');\n  pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);\n}\n\nfunction call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const servers = null;\n  pc1 = window.pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = window.pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc1.oniceconnectionstatechange = e => {\n    onIceStateChange(pc1, e);\n    if (pc1 && pc1.iceConnectionState === 'connected') {\n      restartButton.disabled = false;\n    }\n  };\n  pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream)\n  );\n  console.log('Added local stream to pc1');\n\n  console.log('pc1 createOffer start');\n  pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  pc1.setLocalDescription(desc).then(() => onSetLocalSuccess(pc1), onSetSessionDescriptionError);\n  console.log('pc2 setRemoteDescription start');\n  pc2.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  pc2.createAnswer().then(onCreateAnswerSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('pc2 received remote stream');\n  }\n}\n\nfunction onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  pc2.setLocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc1 setRemoteDescription start');\n  pc1.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);\n\n  if (useSelectedCandidatePairChange) {\n    pc1.getSenders()[0].transport.iceTransport.onselectedcandidatepairchange = () => {\n      checkStats(pc1);\n      if (pc1.iceConnectionState === 'connected') {\n        restartButton.disabled = false;\n      }\n    };\n    pc2.getSenders()[0].transport.iceTransport.onselectedcandidatepairchange = () => {\n      checkStats(pc2);\n    };\n  }\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n    if (!useSelectedCandidatePairChange) {\n      if (pc.iceConnectionState === 'connected' ||\n        pc.iceConnectionState === 'completed') {\n        checkStats(pc);\n      }\n    }\n  }\n}\n\nfunction checkStats(pc) {\n  pc.getStats(null).then(results => {\n    // figure out the peer's ip\n    let activeCandidatePair = null;\n    let remoteCandidate = null;\n\n    // Search for the candidate pair, spec-way first.\n    results.forEach(report => {\n      if (report.type === 'transport') {\n        activeCandidatePair = results.get(report.selectedCandidatePairId);\n      }\n    });\n    // Fallback for Firefox.\n    if (!activeCandidatePair) {\n      results.forEach(report => {\n        if (report.type === 'candidate-pair' && report.state === 'succeeded' && report.selected) {\n          activeCandidatePair = report;\n        }\n      });\n    }\n    if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {\n      results.forEach(report => {\n        if (report.type === 'remote-candidate' && report.id === activeCandidatePair.remoteCandidateId) {\n          remoteCandidate = report;\n        }\n      });\n    }\n    console.log(remoteCandidate);\n    if (remoteCandidate && remoteCandidate.id) {\n      // TODO: update a div showing the remote ip/port?\n      document.getElementById(pc === pc1 ? 'localCandidateId' : 'remoteCandidateId').textContent = remoteCandidate.id;\n    }\n  });\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  restartButton.disabled = true;\n  callButton.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/restart-ice/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/restart-ice/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection ice restart', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection and changes candidates on restart', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.executeScript(() => {\n      return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('remoteVideo').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n    await driver.wait(() => driver.findElement(webdriver.By.id('restartButton')).isEnabled());\n\n    const firstCandidateIds = await Promise.all([\n      await driver.findElement(webdriver.By.id('localCandidateId')).getAttribute('innerText'),\n      await driver.findElement(webdriver.By.id('remoteCandidateId')).getAttribute('innerText'),\n    ]);\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('restartButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('restartButton')).click();\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('restartButton')).isEnabled());\n\n    const secondCandidateIds = await Promise.all([\n      await driver.findElement(webdriver.By.id('localCandidateId')).getAttribute('innerText'),\n      await driver.findElement(webdriver.By.id('remoteCandidateId')).getAttribute('innerText'),\n    ]);\n\n    expect(secondCandidateIds[0]).not.toBe(firstCandidateIds[0]);\n    expect(secondCandidateIds[1]).not.toBe(firstCandidateIds[1]);\n  });\n});\n"
  },
  {
    "path": "src/content/peerconnection/states/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 20px 0;\n  width: 86.5px;\n}\ndiv#buttons {\n  border-bottom: 1px solid #eee;\n  margin: 0 0 20px 0;\n}\ndiv#states {\n  border-bottom: 1px solid #eee;\n}\ndiv#states > div {\n  margin: 0 0 20px 0;\n  min-height: 24px; /* to cope with Unicode character size :^| */\n}\ndiv.label {\n  display: inline-block;\n  font-weight: 400;\n  width: 111px;\n}\ndiv.value {\n  display: inline-block;\n}\nvideo {\n  margin: 0 0 20px 0;\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\nvideo#video1 {\n  margin: 0 20px 20px 0;\n}\n\n@media screen and (min-width: 730px) {\n  video {\n    height: 231px;\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/states/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection: states</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection: states</span>\n    </h1>\n\n    <video id=\"video1\" playsinline autoplay muted></video>\n    <video id=\"video2\" playsinline autoplay></video>\n\n    <div id=\"buttons\">\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <div id=\"states\">\n        <div>\n            <div class=\"label\">PC1 signaling state:</div>\n            <div id=\"pc1SignalState\" class=\"value\"></div>\n        </div>\n        <div>\n            <div class=\"label\">PC1 ICE state:</div>\n            <div id=\"pc1IceState\" class=\"value\"></div>\n        </div>\n        <div>\n            <div class=\"label\">PC1 connection state:</div>\n            <div id=\"pc1ConnState\" class=\"value\"></div>\n        </div>\n        <div>\n            <div class=\"label\">PC2 signaling state:</div>\n            <div id=\"pc2SignalState\" class=\"value\"></div>\n        </div>\n        <div>\n            <div class=\"label\">PC2 ICE state:</div>\n            <div id=\"pc2IceState\" class=\"value\"></div>\n        </div>\n        <div>\n            <div class=\"label\">PC2 connection state:</div>\n            <div id=\"pc2ConnState\" class=\"value\"></div>\n        </div>\n    </div>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/states\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/states/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst video1 = document.querySelector('video#video1');\nconst video2 = document.querySelector('video#video2');\n\nconst startButton = document.querySelector('button#startButton');\nconst callButton = document.querySelector('button#callButton');\nconst hangupButton = document.querySelector('button#hangupButton');\nstartButton.disabled = false;\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.onclick = start;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\nconst pc1SignalStateDiv = document.querySelector('div#pc1SignalState');\nconst pc1IceStateDiv = document.querySelector('div#pc1IceState');\nconst pc1ConnStateDiv = document.querySelector('div#pc1ConnState');\nconst pc2SignalStateDiv = document.querySelector('div#pc2SignalState');\nconst pc2IceStateDiv = document.querySelector('div#pc2IceState');\nconst pc2ConnStateDiv = document.querySelector('div#pc2ConnState');\n\nlet localStream;\nlet pc1;\nlet pc2;\n\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  video1.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  navigator.mediaDevices\n      .getUserMedia({\n        audio: true,\n        video: true\n      })\n      .then(gotStream)\n      .catch(e => alert('getUserMedia() error: ', e.name));\n}\n\nfunction call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using Video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using Audio device: ${audioTracks[0].label}`);\n  }\n  const servers = null;\n\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onsignalingstatechange = stateCallback1;\n\n  pc1SignalStateDiv.textContent = pc1.signalingState;\n  pc1IceStateDiv.textContent = pc1.iceConnectionState;\n  pc1ConnStateDiv.textContent = pc1.connectionState;\n\n  pc1.oniceconnectionstatechange = iceStateCallback1;\n  pc1.onconnectionstatechange = connStateCallback1;\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onsignalingstatechange = stateCallback2;\n\n  pc2SignalStateDiv.textContent = pc2.signalingState;\n  pc2IceStateDiv.textContent = pc2.iceConnectionState;\n  pc2ConnStateDiv.textContent = pc2.connectionState;\n  pc2.oniceconnectionstatechange = iceStateCallback2;\n  pc2.onconnectionstatechange = connStateCallback2;\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Adding Local Stream to peer connection');\n  pc1.createOffer(offerOptions).then(gotDescription1, onCreateSessionDescriptionError);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction gotDescription1(description) {\n  pc1.setLocalDescription(description);\n  console.log(`Offer from pc1:\\n${description.sdp}`);\n  pc2.setRemoteDescription(description);\n  pc2.createAnswer().then(gotDescription2, onCreateSessionDescriptionError);\n}\n\nfunction gotDescription2(description) {\n  pc2.setLocalDescription(description);\n  console.log(`Answer from pc2\\n${description.sdp}`);\n  pc1.setRemoteDescription(description);\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1SignalStateDiv.textContent += ` => ${pc1.signalingState}`;\n  pc2SignalStateDiv.textContent += ` => ${pc2.signalingState}`;\n  pc1IceStateDiv.textContent += ` => ${pc1.iceConnectionState}`;\n  pc2IceStateDiv.textContent += ` => ${pc2.iceConnectionState}`;\n  pc1ConnStateDiv.textContent += ` => ${pc1.connectionState}`;\n  pc2ConnStateDiv.textContent += ` => ${pc2.connectionState}`;\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n\nfunction gotRemoteStream(e) {\n  if (video2.srcObject !== e.streams[0]) {\n    video2.srcObject = e.streams[0];\n    console.log('Got remote stream');\n  }\n}\n\nfunction stateCallback1() {\n  let state;\n  if (pc1) {\n    state = pc1.signalingState;\n    console.log(`pc1 state change callback, state: ${state}`);\n    pc1SignalStateDiv.textContent += ` => ${state}`;\n  }\n}\n\nfunction stateCallback2() {\n  let state;\n  if (pc2) {\n    state = pc2.signalingState;\n    console.log(`pc2 state change callback, state: ${state}`);\n    pc2SignalStateDiv.textContent += ` => ${state}`;\n  }\n}\n\nfunction iceStateCallback1() {\n  let iceState;\n  if (pc1) {\n    iceState = pc1.iceConnectionState;\n    console.log(`pc1 ICE connection state change callback, state: ${iceState}`);\n    pc1IceStateDiv.textContent += ` => ${iceState}`;\n  }\n}\n\nfunction iceStateCallback2() {\n  let iceState;\n  if (pc2) {\n    iceState = pc2.iceConnectionState;\n    console.log(`pc2 ICE connection state change callback, state: ${iceState}`);\n    pc2IceStateDiv.textContent += ` => ${iceState}`;\n  }\n}\n\nfunction connStateCallback1() {\n  if (pc1) {\n    const {connectionState} = pc1;\n    console.log(`pc1 connection state change callback, state: ${connectionState}`);\n    pc1ConnStateDiv.textContent += ` => ${connectionState}`;\n  }\n}\n\nfunction connStateCallback2() {\n  if (pc2) {\n    const {connectionState} = pc2;\n    console.log(`pc2 connection state change callback, state: ${connectionState}`);\n    pc2ConnStateDiv.textContent += ` => ${connectionState}`;\n  }\n}\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  console.log(`Failed to add Ice Candidate: ${error.toString()}`);\n}\n"
  },
  {
    "path": "src/content/peerconnection/states/js/test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/states/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection states', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('establishes a connection and hangs up', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await Promise.all([\n      await driver.wait(() => driver.executeScript(() => {\n        return pc1 && pc1.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n      await driver.wait(() => driver.executeScript(() => {\n        return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n      })),\n    ]);\n\n    await driver.wait(() => driver.executeScript(() => {\n      return document.getElementById('video2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;\n    }));\n\n    const pc1States = {\n      signaling: await driver.findElement(webdriver.By.id('pc1SignalState')).getAttribute('innerText'),\n      ice: await driver.findElement(webdriver.By.id('pc1IceState')).getAttribute('innerText'),\n      connection: await driver.findElement(webdriver.By.id('pc1ConnState')).getAttribute('innerText'),\n    };\n    expect(pc1States.signaling).toBe('stable => have-local-offer => stable');\n    expect(pc1States.ice).toBe('new => checking => connected');\n    expect(pc1States.connection).toBe('new => connecting => connected');\n\n    const pc2States = {\n      signaling: await driver.findElement(webdriver.By.id('pc2SignalState')).getAttribute('innerText'),\n      ice: await driver.findElement(webdriver.By.id('pc2IceState')).getAttribute('innerText'),\n      connection: await driver.findElement(webdriver.By.id('pc2ConnState')).getAttribute('innerText'),\n    };\n    expect(pc2States.signaling).toBe('stable => have-remote-offer => stable');\n    expect(pc2States.ice).toBe('new => checking => connected');\n    expect(pc2States.connection).toBe('new => connecting => connected');\n\n    await driver.findElement(webdriver.By.id('hangupButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return pc1 === null; // eslint-disable-line no-undef\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/trickle-ice/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 20px 10px 0 0;\n  width: 130px;\n}\nbutton#gather {\n  display: block;\n}\nsection#iceServers input {\n  margin: 0 0 10px;\n  width: 260px;\n}\nsection#iceConstraints label {\n  margin: 0 1em 0 0;\n}\nsection#iceServers label {\n  display: inline-block;\n  width: 150px;\n}\n\nsection#iceOptions label {\n  display: inline-block;\n  width: 200px;\n}\n\nselect#servers {\n  font-size: 1em;\n  padding: 5px;\n  width: 420px;\n}\ndiv#iceTransports span {\n  margin: 0 1em 0 0;\n}\ntable#candidates {\n  font-size: 0.7em;\n  overflow-y: auto;\n  text-align: right;\n  width: 100%;\n}\nth {\nfont-weight: bold;\n}\nth:nth-child(3),td:nth-child(3) {\n  text-align: left\n}\nth:nth-child(6),td:nth-child(6) {\n  text-align: left\n}\n\n.gray {\n  color: gray\n}\n\n#poolValue {\n  display: inline-block;\n  width: 30px\n}\n\n#getUserMediaPermissions {\n  display: none;\n}\n\n#error-note {\n  display: none;\n}\n"
  },
  {
    "path": "src/content/peerconnection/trickle-ice/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Trickle ICE</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\">\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Trickle ICE</span></h1>\n\n    <section>\n\n        <p>This page tests the trickle ICE functionality in a WebRTC implementation. It creates a PeerConnection with\n            the specified ICEServers, and then starts candidate gathering for a session with a single audio stream. As\n            candidates are gathered, they are displayed in the text box below, along with an indication when candidate\n            gathering is complete.</p>\n\n        <p>Note that if no getUserMedia permissions for this origin are persisted only candidates from a single\n            interface will be gathered in Chrome. See <a\n                    href=\"https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-01\">the RTCWEB IP address handling\n                recommendations draft</a> for details.<span id=\"getUserMediaPermissions\">You have given permission, candidate from multiple interface will be gathered.</span>\n        </p>\n\n        <p>Individual STUN and TURN servers can be added using the Add server / Remove server controls below; in\n            addition, the type of candidates released to the application can be controlled via the IceTransports\n            constraint.</p>\n\n        <p>If you test a STUN server, it works if you can gather a candidate with type \"srflx\".\n           If you test a TURN server, it works if you can gather a candidate with type \"relay\".</p>\n        <p>If you test just a single TURN/UDP server, this page even allows you to detect when you are using the wrong\n            credential to authenticate.</p>\n\n    </section>\n\n    <section id=\"iceServers\">\n\n        <h2>ICE servers</h2>\n\n        <select id=\"servers\" size=\"4\">\n        </select>\n\n        <div>\n            <label for=\"url\">STUN or TURN URI:</label>\n            <input id=\"url\">\n        </div>\n\n        <div>\n            <label for=\"username\">TURN username:</label>\n            <input id=\"username\">\n        </div>\n\n        <div>\n            <label for=\"password\">TURN password:</label>\n            <input id=\"password\">\n        </div>\n\n        <div>\n            <button id=\"add\">Add Server</button>\n            <button id=\"remove\">Remove Server</button>\n            <button id=\"reset\">Reset to defaults</button>\n        </div>\n\n    </section>\n\n    <section id=\"iceOptions\">\n\n        <h2>ICE options</h2>\n\n        <div id=\"iceTransports\">\n            <label for=\"transports\"><span>IceTransports value:</span></label>\n            <input type=\"radio\" name=\"transports\" value=\"all\" id=\"all\" checked>\n            <span>all</span>\n            <input type=\"radio\" name=\"transports\" value=\"relay\" id=\"relay\">\n            <span>relay</span>\n        </div>\n\n    </section>\n\n    <section>\n        <div>\n            <input type=\"checkbox\" name=\"getUserMedia\" id=\"getUserMedia\">\n            <label for=\"getUserMedia\"><span>Acquire microphone/camera permissions</span></label>\n        </div>\n\n    </section>\n\n    <section>\n\n        <table id=\"candidates\">\n            <thead id=\"candidatesHead\">\n            <tr>\n                <th>Time</th>\n                <th>Type</th>\n                <th>Foundation</th>\n                <th>Protocol</th>\n                <th>Address</th>\n                <th>Port</th>\n                <th>Priority</th>\n                <th>URL (if present)</th>\n                <th>relayProtocol (if present)</th>\n            </tr>\n            </thead>\n            <tbody id=\"candidatesBody\"></tbody>\n        </table>\n        <button id=\"gather\">Gather candidates</button>\n        <div id=\"error-note\">Note: errors from onicecandidateerror above are not necessarily fatal. For example an IPv6 DNS lookup may fail but relay candidates can still be gathered via IPv4.</div>\n        <div id=\"error\"></div>\n    </section>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/trickle-ice\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/trickle-ice/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst addButton = document.querySelector('button#add');\nconst candidateTBody = document.querySelector('tbody#candidatesBody');\nconst gatherButton = document.querySelector('button#gather');\nconst passwordInput = document.querySelector('input#password');\nconst removeButton = document.querySelector('button#remove');\nconst resetButton = document.querySelector('button#reset');\nconst servers = document.querySelector('select#servers');\nconst urlInput = document.querySelector('input#url');\nconst usernameInput = document.querySelector('input#username');\nconst getUserMediaInput = document.querySelector('input#getUserMedia');\n\naddButton.onclick = addServer;\ngatherButton.onclick = start;\nremoveButton.onclick = removeServer;\nresetButton.onclick = (e) => {\n  window.localStorage.clear();\n  document.querySelectorAll('select#servers option').forEach(option => option.remove());\n  const serversSelect = document.querySelector('select#servers');\n  setDefaultServer(serversSelect);\n};\n\nlet begin;\nlet pc;\nlet stream;\nlet candidates;\n\nconst allServersKey = 'servers';\n\nfunction setDefaultServer(serversSelect) {\n  const option = document.createElement('option');\n  option.value = '{\"urls\":[\"stun:stun.l.google.com:19302\"]}';\n  option.text = 'stun:stun.l.google.com:19302';\n  option.ondblclick = selectServer;\n  serversSelect.add(option);\n}\n\nfunction writeServersToLocalStorage() {\n  const serversSelect = document.querySelector('select#servers');\n  const allServers = JSON.stringify(Object.values(serversSelect.options).map(o => JSON.parse(o.value)));\n  window.localStorage.setItem(allServersKey, allServers);\n}\n\nfunction readServersFromLocalStorage() {\n  document.querySelectorAll('select#servers option').forEach(option => option.remove());\n  const serversSelect = document.querySelector('select#servers');\n  const storedServers = window.localStorage.getItem(allServersKey);\n\n  if (storedServers === null || storedServers === '') {\n    setDefaultServer(serversSelect);\n  } else {\n    JSON.parse(storedServers).forEach((server, key) => {\n      const o = document.createElement('option');\n      o.value = JSON.stringify(server);\n      o.text = server.urls[0];\n      o.ondblclick = selectServer;\n      serversSelect.add(o);\n    });\n  }\n}\n\nfunction selectServer(event) {\n  const option = event.target;\n  const value = JSON.parse(option.value);\n  urlInput.value = value.urls[0];\n  usernameInput.value = value.username || '';\n  passwordInput.value = value.credential || '';\n}\n\nfunction addServer() {\n  if (urlInput.value === '' && usernameInput.value === '' && passwordInput.value === '') {\n    // Ignore since this leads to invisible items being added to the list.\n    console.warn('Not adding empty ICE server input');\n    return;\n  }\n  // Store the ICE server as a stringified JSON object in option.value.\n  const option = document.createElement('option');\n  const iceServer = {\n    urls: [urlInput.value],\n    username: usernameInput.value,\n    credential: passwordInput.value\n  };\n  option.value = JSON.stringify(iceServer);\n  option.text = `${urlInput.value} `;\n  const username = usernameInput.value;\n  const password = passwordInput.value;\n  if (username || password) {\n    option.text += (` [${username}:${password}]`);\n  }\n  option.ondblclick = selectServer;\n  servers.add(option);\n  urlInput.value = usernameInput.value = passwordInput.value = '';\n  writeServersToLocalStorage();\n}\n\nfunction removeServer() {\n  for (let i = servers.options.length - 1; i >= 0; --i) {\n    if (servers.options[i].selected) {\n      servers.remove(i);\n    }\n  }\n  writeServersToLocalStorage();\n}\n\nasync function start() {\n  // Clean out the table.\n  while (candidateTBody.firstChild) {\n    candidateTBody.removeChild(candidateTBody.firstChild);\n  }\n\n  gatherButton.disabled = true;\n  if (getUserMediaInput.checked) {\n    stream = await navigator.mediaDevices.getUserMedia({audio: true});\n  }\n  getUserMediaInput.disabled = true;\n\n  // Read the values from the input boxes.\n  const iceServers = [];\n  for (let i = 0; i < servers.length; ++i) {\n    iceServers.push(JSON.parse(servers[i].value));\n  }\n  const transports = document.getElementsByName('transports');\n  let iceTransports;\n  for (let i = 0; i < transports.length; ++i) {\n    if (transports[i].checked) {\n      iceTransports = transports[i].value;\n      break;\n    }\n  }\n\n  // Create a PeerConnection with no streams, but force a m=audio line.\n  const config = {\n    iceServers: iceServers,\n    iceTransportPolicy: iceTransports,\n  };\n\n  const offerOptions = {offerToReceiveAudio: 1};\n  // Whether we gather IPv6 candidates.\n  // Whether we only gather a single set of candidates for RTP and RTCP.\n\n  console.log(`Creating new PeerConnection with config=${JSON.stringify(config)}`);\n  const errDiv = document.getElementById('error');\n  errDiv.innerText = '';\n  let desc;\n  try {\n    pc = new RTCPeerConnection(config);\n    pc.onicecandidate = iceCallback;\n    pc.onicegatheringstatechange = gatheringStateChange;\n    pc.onicecandidateerror = iceCandidateError;\n    if (stream) {\n      stream.getTracks().forEach(track => pc.addTrack(track, stream));\n    }\n    desc = await pc.createOffer(offerOptions);\n  } catch (err) {\n    errDiv.innerText = `Error creating offer: ${err}`;\n    gatherButton.disabled = false;\n    return;\n  }\n  begin = window.performance.now();\n  candidates = [];\n  pc.setLocalDescription(desc);\n}\n\n// Parse the uint32 PRIORITY field into its constituent parts from RFC 5245,\n// type preference, local preference, and (256 - component ID).\n// ex: 126 | 32252 | 255 (126 is host preference, 255 is component ID 1)\nfunction formatPriority(priority) {\n  return [\n    priority >> 24,\n    (priority >> 8) & 0xFFFF,\n    priority & 0xFF\n  ].join(' | ');\n}\n\nfunction appendCell(row, val) {\n  const cell = document.createElement('td');\n  cell.textContent = val;\n  row.appendChild(cell);\n}\n\n// Try to determine authentication failures and unreachable TURN\n// servers by using heuristics on the candidate types gathered.\nfunction getFinalResult() {\n  let result = 'Done';\n\n  // if more than one server is used, it can not be determined\n  // which server failed.\n  if (servers.length === 1) {\n    const server = JSON.parse(servers[0].value);\n\n    // get the candidates types (host, srflx, relay)\n    const types = candidates.map((cand) => cand.type);\n\n    // If the server is a TURN server we should have a relay candidate.\n    // If we did not get a relay candidate but a srflx candidate\n    // authentication might have failed.\n    // If we did not get  a relay candidate or a srflx candidate\n    // we could not reach the TURN server. Either it is not running at\n    // the target address or the clients access to the port is blocked.\n    //\n    // This only works for TURN/UDP since we do not get\n    // srflx candidates from TURN/TCP.\n    if (server.urls[0].indexOf('turn:') === 0 &&\n      server.urls[0].indexOf('?transport=tcp') === -1) {\n      if (types.indexOf('relay') === -1) {\n        if (types.indexOf('srflx') > -1) {\n          // a binding response but no relay candidate suggests auth failure.\n          result = 'Authentication failed?';\n        } else {\n          // either the TURN server is down or the clients access is blocked.\n          result = 'Not reachable?';\n        }\n      }\n    }\n  }\n  return result;\n}\n\nasync function iceCallback(event) {\n  const elapsed = ((window.performance.now() - begin) / 1000).toFixed(3);\n  const row = document.createElement('tr');\n  if (event.candidate) {\n    if (event.candidate.candidate === '') {\n      return;\n    }\n    appendCell(row, elapsed);\n    const {candidate} = event;\n    let url;\n    // Until url is available from the candidate, to to polyfill.\n    if (['srflx', 'relay'].includes(candidate.type) && !candidate.url) {\n      const stats = await pc.getStats();\n      stats.forEach(report => {\n        if (!url && report.type === 'local-candidate' &&\n            report.address === candidate.address &&\n            report.port === candidate.port) {\n          url = report.url;\n        }\n      });\n    }\n\n    appendCell(row, candidate.type);\n    appendCell(row, candidate.foundation);\n    appendCell(row, candidate.protocol);\n    appendCell(row, candidate.address);\n    appendCell(row, candidate.port);\n    appendCell(row, formatPriority(candidate.priority));\n    appendCell(row, candidate.url || url || '');\n    appendCell(row, candidate.relayProtocol || '');\n    candidates.push(candidate);\n  }\n  candidateTBody.appendChild(row);\n}\n\nfunction gatheringStateChange() {\n  if (pc.iceGatheringState !== 'complete') {\n    return;\n  }\n  const elapsed = ((window.performance.now() - begin) / 1000).toFixed(3);\n  const row = document.createElement('tr');\n  appendCell(row, elapsed);\n  appendCell(row, getFinalResult());\n  pc.close();\n  pc = null;\n  if (stream) {\n    stream.getTracks().forEach(track => track.stop());\n    stream = null;\n  }\n  gatherButton.disabled = false;\n  getUserMediaInput.disabled = false;\n  candidateTBody.appendChild(row);\n}\n\nfunction iceCandidateError(e) {\n  // The interesting attributes of the error are\n  // * the url (which allows looking up the server)\n  // * the errorCode and errorText\n  document.getElementById('error-note').style.display = 'block';\n  document.getElementById('error').innerText += 'The server ' + e.url +\n    ' returned an error with code=' + e.errorCode + ':\\n' +\n    e.errorText + '\\n';\n}\n\nreadServersFromLocalStorage();\n\n// check if we have getUserMedia permissions.\nnavigator.mediaDevices\n    .enumerateDevices()\n    .then(function(devices) {\n      devices.forEach(function(device) {\n        if (device.label !== '') {\n          document.getElementById('getUserMediaPermissions').style.display = 'block';\n        }\n      });\n    });\n"
  },
  {
    "path": "src/content/peerconnection/trickle-ice/js/test.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n\n'use strict';\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/trickle-ice/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('Trickle-Ice', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n  afterEach(() => {\n    return driver.executeScript(() => localStorage.clear());\n  });\n\n  it('gathers a candidate', async () => {\n    await driver.findElement(webdriver.By.id('gather')).click();\n    await driver.wait(() => driver.executeScript(() => pc === null && candidates.length > 0), 30 * 1000); // eslint-disable-line no-undef\n  });\n\n  it('loads server data on double click', async () => {\n    const element = await driver.findElement(webdriver.By.css('#servers>option'));\n    const actions = driver.actions({async: true});\n    await actions.doubleClick(element).perform();\n    const value = await driver.findElement(webdriver.By.id('url')).getAttribute('value');\n    expect(value).not.toBe('');\n  });\n\n  it('adding a server', async () => {\n    await driver.findElement(webdriver.By.id('url'))\n        .sendKeys('stun:stun.l.google.com:19302');\n    await driver.findElement(webdriver.By.id('add')).click();\n    const length = await driver.findElement(webdriver.By.css('#servers'))\n        .getAttribute('length');\n    expect(length >>> 0).toBe(2);\n  });\n\n  it('removing a server', async () => {\n    await driver.findElement(webdriver.By.css('#servers>option')).click();\n    await driver.findElement(webdriver.By.id('remove')).click();\n    const length = await driver.findElement(webdriver.By.css('#servers'))\n        .getAttribute('length');\n    expect(length >>> 0).toBe(0);\n  });\n});\n"
  },
  {
    "path": "src/content/peerconnection/upgrade/css/main.css",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n    margin: 0;\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 0 20px 0;\n  vertical-align: top;\n}\n\nvideo#localVideo {\n  margin: 0 20px 20px 0;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    width: 83px;\n    margin: 0 11px 10px 0;\n  }\n\n  video {\n    height: 90px;\n    margin: 0 0 10px 0;\n    width: calc(50% - 7px);\n  }\n  video#localVideo {\n    margin: 0 10px 20px 0;\n  }\n\n}\n"
  },
  {
    "path": "src/content/peerconnection/upgrade/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection - upgrade</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Peer connection</span></h1>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay></video>\n\n    <div>\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"upgradeButton\">Turn on video</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as\n        well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/upgrade\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/upgrade/js/main.js",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst upgradeButton = document.getElementById('upgradeButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nupgradeButton.disabled = true;\nstartButton.onclick = start;\ncallButton.onclick = call;\nupgradeButton.onclick = upgrade;\nhangupButton.onclick = hangup;\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', function() {\n  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.onresize = () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  console.warn('RESIZE', remoteVideo.videoWidth, remoteVideo.videoHeight);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log(`Setup time: ${elapsedTime.toFixed(3)}ms`);\n    startTime = null;\n  }\n};\n\nlet localStream;\nlet pc1;\nlet pc2;\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 0\n};\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  localVideo.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  navigator.mediaDevices\n      .getUserMedia({\n        audio: true,\n        video: false\n      })\n      .then(gotStream)\n      .catch(e => alert(`getUserMedia() error: ${e.name}`));\n}\n\nfunction call() {\n  callButton.disabled = true;\n  upgradeButton.disabled = false;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const audioTracks = localStream.getAudioTracks();\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);\n  pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Added local stream to pc1');\n\n  console.log('pc1 createOffer start');\n  pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  pc1.setLocalDescription(desc).then(() => onSetLocalSuccess(pc1), onSetSessionDescriptionError);\n  console.log('pc2 setRemoteDescription start');\n  pc2.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  pc2.createAnswer().then(onCreateAnswerSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  console.log('gotRemoteStream', e.track, e.streams[0]);\n\n  // reset srcObject to work around minor bugs in Chrome and Edge.\n  remoteVideo.srcObject = null;\n  remoteVideo.srcObject = e.streams[0];\n}\n\nfunction onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  pc2.setLocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc1 setRemoteDescription start');\n  pc1.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n  }\n}\n\nfunction upgrade() {\n  upgradeButton.disabled = true;\n  navigator.mediaDevices\n      .getUserMedia({video: true})\n      .then(stream => {\n        const videoTracks = stream.getVideoTracks();\n        if (videoTracks.length > 0) {\n          console.log(`Using video device: ${videoTracks[0].label}`);\n        }\n        localStream.addTrack(videoTracks[0]);\n        localVideo.srcObject = null;\n        localVideo.srcObject = localStream;\n        pc1.addTrack(videoTracks[0], localStream);\n        return pc1.createOffer();\n      })\n      .then(offer => pc1.setLocalDescription(offer))\n      .then(() => pc2.setRemoteDescription(pc1.localDescription))\n      .then(() => pc2.createAnswer())\n      .then(answer => pc2.setLocalDescription(answer))\n      .then(() => pc1.setRemoteDescription(pc2.localDescription));\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n\n  const videoTracks = localStream.getVideoTracks();\n  videoTracks.forEach(videoTrack => {\n    videoTrack.stop();\n    localStream.removeTrack(videoTrack);\n  });\n  localVideo.srcObject = null;\n  localVideo.srcObject = localStream;\n\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n"
  },
  {
    "path": "src/content/peerconnection/upgrade/js/test.js",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n/* eslint-env node */\n'use strict';\n\nconst webdriver = require('selenium-webdriver');\nconst seleniumHelpers = require('../../../../../test/webdriver');\n\nlet driver;\nconst path = '/src/content/peerconnection/upgrade/index.html';\nconst url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;\n\ndescribe('peerconnection upgrade from audio-only to audio-video', () => {\n  beforeAll(async () => {\n    driver = await seleniumHelpers.buildDriver();\n  });\n  afterAll(() => {\n    return driver.quit();\n  });\n\n  beforeEach(() => {\n    return driver.get(url);\n  });\n\n  it('upgrades to video', async () => {\n    await driver.findElement(webdriver.By.id('startButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return localStream !== null; // eslint-disable-line no-undef\n    }));\n    await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('callButton')).click();\n\n    await driver.wait(() => driver.executeScript(() => {\n      return pc2 && pc2.connectionState === 'connected'; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('upgradeButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('upgradeButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return remoteVideo.videoWidth > 0; // eslint-disable-line no-undef\n    }));\n\n    await driver.wait(() => driver.findElement(webdriver.By.id('hangupButton')).isEnabled());\n    await driver.findElement(webdriver.By.id('hangupButton')).click();\n    await driver.wait(() => driver.executeScript(() => {\n      return pc1 === null; // eslint-disable-line no-undef\n    }));\n  });\n});\n\n"
  },
  {
    "path": "src/content/peerconnection/video-analyzer/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n   <meta http-equiv=\"refresh\"\n   content=\"0; url=//webrtc.github.io/samples/src/content/insertable-streams/video-analyzer/\">\n   <title>Page move</title>\n</head>\n<body>\n   <p>The page has moved to:\n   <a href=\"//webrtc.github.io/samples/src/content/insertable-streams/video-analyzer/\">this page</a></p>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-input/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\naudio {\n  margin: 0 0 20px 0;\n  width: 50%;\n}\n\nbutton {\n  margin: 0 20px 20px 0;\n  width: 89px;\n}\n\ndiv#options {\n  margin: 0 0 20px 0;\n}\n\ndiv#status {\n  background-color: #eee;\n  margin: 0 0 20px 0;\n  min-height: 140px;\n  overflow-y: scroll;\n  padding: 0 0 0 10px;\n  width: 50%;\n}\n\ninput[type='checkbox'] {\n  margin: 0 10px 0 0;\n  position: relative;\n  top: -2px;\n}\n\nlabel {\n  font-weight: 400;\n}\n\nli {\n  margin: 0 0 10px 0;\n}\n\nul {\n  list-style-type: square;\n  padding: 0 0 0 18px;\n}\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-input/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Web Audio input</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a>\n        <span>Web Audio input</span></h1>\n\n    <audio autoplay controls></audio>\n\n    <div id=\"options\">\n        <input type=\"checkbox\" id=\"renderLocally\" disabled><label for=\"renderLocally\">Add local audio to output</label>\n    </div>\n\n    <div id=\"buttons\">\n        <button id=\"start\">Start</button>\n        <button id=\"stop\" disabled>Stop</button>\n    </div>\n\n    <div id=\"status\"></div>\n    <div id=\"errorMsg\"></div>\n\n    <p>Capture microphone input and stream it to a peer with processing applied to the audio.</p>\n\n    <p>The audio stream is:</p>\n\n    <ul>\n        <li>Recorded using <a href=\"https://developers.google.com/web/updates/2012/09/Live-Web-Audio-Input-Enabled\"\n                              title=\"Live web audio input comes to Google's Chrome Canary\">live web audio input in\n            chrome://flags </a>.\n        </li>\n        <li>Filtered using an HP filter with fc=1500 Hz.</li>\n        <li>Encoded using <a href=\"http://www.opus-codec.org/\" title=\"Opus Codec\">Opus</a>.</li>\n        <li>Transmitted (in loopback) to a remote peer using <a\n                href=\"http://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcpeerconnection-interface\"\n                title=\"RTCPeerConnection Interface\">RTCPeerConnection</a> where it is decoded.\n        </li>\n        <li>Finally, the received remote stream is used as source to an &lt;audio&gt; element and played out locally.\n        </li>\n    </ul>\n\n    <p>Press any key to add an effect to the transmitted audio while talking.</p>\n\n    <p>Please note that:</p>\n    <ul>\n        <li>Sample rate and channel configuration must be the same for input and\n            output sides on Windows.\n        </li>\n        <li>Only the default microphone device can be used for capturing.</li>\n    </ul>\n\n    <p>For more information, see <a href=\"https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/webrtc-integration.html\"\n                                    title=\"Example 3: Capture microphone input and stream it out to a peer with a processing effect applied to the audio\">WebRTC\n        integration with the Web Audio API</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/webaudio-input\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"js/webaudioextended.js\"></script>\n<script src=\"js/main.js\" async></script>\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-input/js/main.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n/* global WebAudioExtended */\n\nconst audioElement = document.querySelector('audio');\n\nconst startButton = document.querySelector('button#start');\nconst stopButton = document.querySelector('button#stop');\nstartButton.onclick = start;\nstopButton.onclick = stop;\n\nconst renderLocallyCheckbox = document.querySelector('input#renderLocally');\nrenderLocallyCheckbox.onclick = toggleRenderLocally;\n\ndocument.addEventListener('keydown', handleKeyDown, false);\n\nlet localStream;\nlet pc1;\nlet pc2;\n\nconst webAudio = new WebAudioExtended();\nwebAudio.loadSound('audio/Shamisen-C4.wav');\n\nfunction start() {\n  webAudio.start();\n  const constraints = {\n    audio: true,\n    video: false\n  };\n  navigator.mediaDevices\n      .getUserMedia(constraints)\n      .then(handleSuccess)\n      .catch(handleFailure);\n  startButton.disabled = true;\n  stopButton.disabled = false;\n}\n\nfunction stop() {\n  webAudio.stop();\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  startButton.enabled = true;\n  stopButton.enabled = false;\n  renderLocallyCheckbox.disabled = true;\n  localStream.getTracks().forEach(track => track.stop());\n}\n\nfunction handleSuccess(stream) {\n  renderLocallyCheckbox.disabled = false;\n  const audioTracks = stream.getAudioTracks();\n  if (audioTracks.length === 1) {\n    console.log('Got one audio track:', audioTracks);\n    const filteredStream = webAudio.applyFilter(stream);\n    const servers = null;\n    pc1 = new RTCPeerConnection(servers); // eslint-disable-line new-cap\n    console.log('Created local peer connection object pc1');\n    pc1.onicecandidate = e => onIceCandidate(pc1, e);\n    pc2 = new RTCPeerConnection(servers); // eslint-disable-line new-cap\n    console.log('Created remote peer connection object pc2');\n    pc2.onicecandidate = e => onIceCandidate(pc2, e);\n    pc2.ontrack = gotRemoteStream;\n    filteredStream.getTracks().forEach(track => pc1.addTrack(track, filteredStream));\n    pc1.createOffer().then(gotDescription1).catch(error => console.log(`createOffer failed: ${error}`));\n\n    stream.oninactive = () => {\n      console.log('Stream inactive:', stream);\n      startButton.disabled = false;\n      stopButton.disabled = true;\n    };\n\n    localStream = stream;\n  } else {\n    logError('The media stream contains an invalid number of audio tracks.');\n    stream.getTracks().forEach(track => track.stop());\n  }\n}\n\nfunction handleFailure(error) {\n  startButton.disabled = false;\n  stopButton.disabled = true;\n  logError(`Failed to get access to local media. Error: ${error.name}`);\n}\n\nfunction gotDescription1(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n\n  pc1.setLocalDescription(desc);\n  pc2.setRemoteDescription(desc);\n  pc2.createAnswer()\n      .then(gotDescription2)\n      .catch(error => logError(`createAnswer failed: ${error}`));\n}\n\nfunction gotDescription2(desc) {\n  console.log(`Answer from pc2\\n${desc.sdp}`);\n  pc2.setLocalDescription(desc);\n  pc1.setRemoteDescription(desc);\n}\n\nfunction gotRemoteStream(e) {\n  if (audioElement.srcObject !== e.streams[0]) {\n    audioElement.srcObject = e.streams[0];\n  }\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess() {\n  console.log('AddIceCandidate success.');\n}\n\nfunction onAddIceCandidateError(error) {\n  logError(`Failed to add Ice Candidate: ${error.toString()}`);\n}\n\nfunction handleKeyDown() {\n  webAudio.addEffect();\n}\n\nfunction toggleRenderLocally() {\n  console.log('Render locally: ', renderLocallyCheckbox.checked);\n  webAudio.renderLocally(renderLocallyCheckbox.checked);\n}\n\nfunction logError(error) {\n  document.querySelector('#errorMsg').innerHTML = error;\n}\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-input/js/webaudioextended.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n'use strict';\n\n// WebAudioExtended helper class which takes care of the WebAudio related parts.\n\nfunction WebAudioExtended() {\n  window.AudioContext = window.AudioContext || window.webkitAudioContext;\n  /* global AudioContext */\n  this.context = new AudioContext();\n  this.soundBuffer = null;\n}\n\nWebAudioExtended.prototype.start = function() {\n  this.filter = this.context.createBiquadFilter();\n  this.filter.type = 'highpass';\n  this.filter.frequency.setValueAtTime(1500, this.context.currentTime + 1);\n};\n\nWebAudioExtended.prototype.applyFilter = function(stream) {\n  this.mic = this.context.createMediaStreamSource(stream);\n  this.mic.connect(this.filter);\n  this.peer = this.context.createMediaStreamDestination();\n  this.filter.connect(this.peer);\n  return this.peer.stream;\n};\n\nWebAudioExtended.prototype.renderLocally = function(enabled) {\n  if (enabled) {\n    this.mic.connect(this.context.destination);\n  } else {\n    this.mic.disconnect(0);\n    this.mic.connect(this.filter);\n  }\n};\n\nWebAudioExtended.prototype.stop = function() {\n  this.mic.disconnect(0);\n  this.filter.disconnect(0);\n  this.mic = null;\n  this.peer = null;\n};\n\nWebAudioExtended.prototype.addEffect = function() {\n  const effect = this.context.createBufferSource();\n  effect.buffer = this.soundBuffer;\n  if (this.peer) {\n    effect.connect(this.peer);\n    effect.start(0);\n  }\n};\n\nWebAudioExtended.prototype.loadCompleted = function() {\n  this.context.decodeAudioData(this.request.response, function(buffer) {\n    this.soundBuffer = buffer;\n  }.bind(this));\n};\n\nWebAudioExtended.prototype.loadSound = function(url) {\n  this.request = new XMLHttpRequest();\n  this.request.open('GET', url, true);\n  this.request.responseType = 'arraybuffer';\n  this.request.onload = this.loadCompleted.bind(this);\n  this.request.send();\n};\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-output/css/main.css",
    "content": "/*\n*  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n*\n*  Use of this source code is governed by a BSD-style license\n*  that can be found in the LICENSE file in the root of the source\n*  tree.\n*/\n\nbutton {\n  margin: 0 20px 0 0;\n  width: 83px;\n}\n\nbutton#hangupButton {\n  margin: 0;\n}\n\ncanvas {\n  background-color: #666;\n  vertical-align: top;\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n\nvideo {\n  --width: 45%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n  margin: 0 20px 20px 0;\n}\n\nvideo#remoteVideo {\n  display: none;\n}\n\n@media screen and (max-width: 400px) {\n  button {\n    margin: 0 11px 10px 0;\n    width: 83px;\n  }\n}\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-output/index.html",
    "content": "<!DOCTYPE html>\n<!--\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n-->\n<html>\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"description\" content=\"WebRTC code samples\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1\">\n    <meta itemprop=\"description\" content=\"Client-side WebRTC code samples\">\n    <meta itemprop=\"image\" content=\"../../../images/webrtc-icon-192x192.png\">\n    <meta itemprop=\"name\" content=\"WebRTC code samples\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta id=\"theme-color\" name=\"theme-color\" content=\"#ffffff\">\n\n    <base target=\"_blank\">\n\n    <title>Peer connection as input to Web Audio</title>\n\n    <link rel=\"icon\" sizes=\"192x192\" href=\"../../../images/webrtc-icon-192x192.png\">\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n    <link rel=\"stylesheet\" href=\"../../../css/main.css\"/>\n    <link rel=\"stylesheet\" href=\"css/main.css\"/>\n\n</head>\n\n<body>\n\n<div id=\"container\">\n\n    <h1><a href=\"//webrtc.github.io/samples/\" title=\"WebRTC samples homepage\">WebRTC samples</a> <span>Peer connection as input to Web Audio</span>\n    </h1>\n\n    <video id=\"localVideo\" playsinline autoplay muted></video>\n    <video id=\"remoteVideo\" playsinline autoplay muted></video>\n\n    <canvas></canvas>\n\n    <div>\n        <button id=\"startButton\">Start</button>\n        <button id=\"callButton\">Call</button>\n        <button id=\"hangupButton\">Hang Up</button>\n    </div>\n\n    <p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>\n        objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can\n        inspect them in the console as well.</p>\n\n    <p>For more information about RTCPeerConnection, see <a href=\"http://www.html5rocks.com/en/tutorials/webrtc/basics/\"\n                                                            title=\"HTML5 Rocks article about WebRTC by Sam Dutton\">Getting\n        Started With WebRTC</a>.</p>\n\n\n    <a href=\"https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/webaudio-output\"\n       title=\"View source for this page on GitHub\" id=\"viewSource\">View source on GitHub</a>\n\n</div>\n\n<script src=\"https://webrtc.github.io/adapter/adapter-latest.js\"></script>\n<script src=\"../../../js/third_party/streamvisualizer.js\"></script>\n<script src=\"js/main.js\" async></script>\n\n<script src=\"../../../js/lib/ga.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/content/peerconnection/webaudio-output/js/main.js",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n\n/* globals StreamVisualizer */\n\n'use strict';\n\nconst startButton = document.getElementById('startButton');\nconst callButton = document.getElementById('callButton');\nconst hangupButton = document.getElementById('hangupButton');\ncallButton.disabled = true;\nhangupButton.disabled = true;\nstartButton.onclick = start;\ncallButton.onclick = call;\nhangupButton.onclick = hangup;\n\nconst canvas = document.querySelector('canvas');\n\nlet startTime;\nconst localVideo = document.getElementById('localVideo');\nconst remoteVideo = document.getElementById('remoteVideo');\n\nlocalVideo.addEventListener('loadedmetadata', () => {\n  return console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('loadedmetadata', () => {\n  return console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);\n});\n\nremoteVideo.addEventListener('resize', () => {\n  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);\n  // We'll use the first onsize callback as an indication that video has started\n  // playing out.\n  if (startTime) {\n    const elapsedTime = window.performance.now() - startTime;\n    console.log(`Setup time: ${elapsedTime.toFixed(3)}ms`);\n    startTime = null;\n  }\n});\n\nlet localStream;\nlet pc1;\nlet pc2;\nconst offerOptions = {\n  offerToReceiveAudio: 1,\n  offerToReceiveVideo: 1\n};\n\nfunction getName(pc) {\n  return (pc === pc1) ? 'pc1' : 'pc2';\n}\n\nfunction getOtherPc(pc) {\n  return (pc === pc1) ? pc2 : pc1;\n}\n\nfunction gotStream(stream) {\n  console.log('Received local stream');\n  localVideo.srcObject = stream;\n  localStream = stream;\n  callButton.disabled = false;\n}\n\nfunction start() {\n  console.log('Requesting local stream');\n  startButton.disabled = true;\n  navigator.mediaDevices\n      .getUserMedia({\n        audio: true,\n        video: true\n      })\n      .then(gotStream)\n      .catch(e => alert(`getUserMedia() error: ${e.name}`));\n}\n\nfunction call() {\n  callButton.disabled = true;\n  hangupButton.disabled = false;\n  console.log('Starting call');\n  startTime = window.performance.now();\n  const videoTracks = localStream.getVideoTracks();\n  const audioTracks = localStream.getAudioTracks();\n  if (videoTracks.length > 0) {\n    console.log(`Using video device: ${videoTracks[0].label}`);\n  }\n  if (audioTracks.length > 0) {\n    console.log(`Using audio device: ${audioTracks[0].label}`);\n  }\n  const servers = null;\n  pc1 = new RTCPeerConnection(servers);\n  console.log('Created local peer connection object pc1');\n  pc1.onicecandidate = e => onIceCandidate(pc1, e);\n  pc2 = new RTCPeerConnection(servers);\n  console.log('Created remote peer connection object pc2');\n  pc2.onicecandidate = e => onIceCandidate(pc2, e);\n  pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);\n  pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);\n  pc2.ontrack = gotRemoteStream;\n\n  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));\n  console.log('Added local stream to pc1');\n\n  console.log('pc1 createOffer start');\n  pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onCreateSessionDescriptionError(error) {\n  console.log(`Failed to create session description: ${error.toString()}`);\n}\n\nfunction onCreateOfferSuccess(desc) {\n  console.log(`Offer from pc1\\n${desc.sdp}`);\n  console.log('pc1 setLocalDescription start');\n  pc1.setLocalDescription(desc).then(() => onSetLocalSuccess(pc1), onSetSessionDescriptionError);\n  console.log('pc2 setRemoteDescription start');\n  pc2.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc2 createAnswer start');\n  // Since the 'remote' side has no media stream we need\n  // to pass in the right constraints in order for it to\n  // accept the incoming offer of audio and video.\n  pc2.createAnswer().then(onCreateAnswerSuccess, onCreateSessionDescriptionError);\n}\n\nfunction onSetLocalSuccess(pc) {\n  console.log(`${getName(pc)} setLocalDescription complete`);\n}\n\nfunction onSetRemoteSuccess(pc) {\n  console.log(`${getName(pc)} setRemoteDescription complete`);\n}\n\nfunction onSetSessionDescriptionError(error) {\n  console.log(`Failed to set session description: ${error.toString()}`);\n}\n\nfunction gotRemoteStream(e) {\n  if (remoteVideo.srcObject !== e.streams[0]) {\n    remoteVideo.srcObject = e.streams[0];\n    console.log('pc2 received remote stream');\n    const streamVisualizer = new StreamVisualizer(e.streams[0], canvas);\n    streamVisualizer.start();\n  }\n}\n\n\nfunction onCreateAnswerSuccess(desc) {\n  console.log(`Answer from pc2:\\n${desc.sdp}`);\n  console.log('pc2 setLocalDescription start');\n  pc2.setLocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);\n  console.log('pc1 setRemoteDescription start');\n  pc1.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);\n}\n\nfunction onIceCandidate(pc, event) {\n  getOtherPc(pc)\n      .addIceCandidate(event.candidate)\n      .then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));\n  console.log(`${getName(pc)} ICE candidate:\\n${event.candidate ? event.candidate.candidate : '(null)'}`);\n}\n\nfunction onAddIceCandidateSuccess(pc) {\n  console.log(`${getName(pc)} addIceCandidate success`);\n}\n\nfunction onAddIceCandidateError(pc, error) {\n  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);\n}\n\nfunction onIceStateChange(pc, event) {\n  if (pc) {\n    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);\n    console.log('ICE state change event: ', event);\n  }\n}\n\nfunction hangup() {\n  console.log('Ending call');\n  pc1.close();\n  pc2.close();\n  pc1 = null;\n  pc2 = null;\n  hangupButton.disabled = true;\n  callButton.disabled = false;\n}\n"
  },
  {
    "path": "src/css/main.css",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n.hidden {\n  display: none;\n}\n\n.highlight {\n  background-color: #eee;\n  font-size: 1.2em;\n  margin: 0 0 30px 0;\n  padding: 0.2em 1.5em;\n}\n\n.warning {\n  color: red;\n  font-weight: 400;\n}\n\n@media screen and (min-width: 1000px) {\n  /* hack! to detect non-touch devices */\n  div#links a {\n    line-height: 0.8em;\n  }\n}\n\naudio {\n  max-width: 100%;\n}\n\nbody {\n  font-family: 'Roboto', sans-serif;\n  font-weight: 300;\n  margin: 0;\n  padding: 1em;\n  word-break: break-word;\n}\n\nbutton {\n  background-color: #d84a38;\n  border: none;\n  border-radius: 2px;\n  color: white;\n  font-family: 'Roboto', sans-serif;\n  font-size: 0.8em;\n  margin: 0 0 1em 0;\n  padding: 0.5em 0.7em 0.6em 0.7em;\n}\n\nbutton:active {\n  background-color: #cf402f;\n}\n\nbutton:hover {\n  background-color: #cf402f;\n}\n\nbutton[disabled] {\n  color: #ccc;\n}\n\nbutton[disabled]:hover {\n  background-color: #d84a38;\n}\n\ncanvas {\n  background-color: #ccc;\n  max-width: 100%;\n  width: 100%;\n}\n\ncode {\n  font-family: 'Roboto', sans-serif;\n  font-weight: 400;\n}\n\ndiv#container {\n  margin: 0 auto 0 auto;\n  max-width: 60em;\n  padding: 1em 1.5em 1.3em 1.5em;\n}\n\ndiv#links {\n  padding: 0.5em 0 0 0;\n}\n\nh1 {\n  border-bottom: 1px solid #ccc;\n  font-family: 'Roboto', sans-serif;\n  font-weight: 500;\n  margin: 0 0 0.8em 0;\n  padding: 0 0 0.2em 0;\n}\n\nh2 {\n  color: #444;\n  font-weight: 500;\n}\n\nh3 {\n  border-top: 1px solid #eee;\n  color: #666;\n  font-weight: 500;\n  margin: 10px 0 10px 0;\n  white-space: nowrap;\n}\n\nli {\n  margin: 0 0 0.4em 0;\n}\n\nhtml {\n  /* avoid annoying page width change\n  when moving from the home page */\n  overflow-y: scroll;\n}\n\nimg {\n  border: none;\n  max-width: 100%;\n}\n\ninput[type=radio] {\n  position: relative;\n  top: -1px;\n}\n\np {\n  color: #444;\n  font-weight: 300;\n}\n\np#data {\n  border-top: 1px dotted #666;\n  font-family: Courier New, monospace;\n  line-height: 1.3em;\n  max-height: 1000px;\n  overflow-y: auto;\n  padding: 1em 0 0 0;\n}\n\np.borderBelow {\n  border-bottom: 1px solid #aaa;\n  padding: 0 0 20px 0;\n}\n\nsection p:last-of-type {\n  margin: 0;\n}\n\nsection {\n  border-bottom: 1px solid #eee;\n  margin: 0 0 30px 0;\n  padding: 0 0 20px 0;\n}\n\nsection:last-of-type {\n  border-bottom: none;\n  padding: 0 0 1em 0;\n}\n\nselect {\n  margin: 0 1em 1em 0;\n  position: relative;\n  top: -1px;\n}\n\nh1 span {\n  white-space: nowrap;\n}\n\na {\n  color: #1D6EEE;\n  font-weight: 300;\n  text-decoration: none;\n}\n\nh1 a {\n  font-weight: 300;\n  margin: 0 10px 0 0;\n  white-space: nowrap;\n}\n\na:hover {\n  color: #3d85c6;\n  text-decoration: underline;\n}\n\na#viewSource {\n  display: block;\n  margin: 1.3em 0 0 0;\n  border-top: 1px solid #999;\n  padding: 1em 0 0 0;\n}\n\ndiv#errorMsg p {\n  color: #F00;\n}\n\ndiv#links a {\n  display: block;\n  line-height: 1.3em;\n  margin: 0 0 1.5em 0;\n}\n\ndiv.outputSelector {\n  margin: -1.3em 0 2em 0;\n}\n\np.description {\n  margin: 0 0 0.5em 0;\n}\n\nstrong {\n  font-weight: 500;\n}\n\ntextarea {\n  resize: none;\n  font-family: 'Roboto', sans-serif;\n}\n\nvideo {\n  background: #222;\n  margin: 0 0 20px 0;\n  --width: 100%;\n  width: var(--width);\n  height: calc(var(--width) * 0.75);\n}\n\nul {\n  margin: 0 0 0.5em 0;\n}\n\nfieldset {\n  margin: 0 0 1em 0;\n}\n\nfieldset > select {\n  margin-top: 1em;\n}\n\n@media screen and (max-width: 650px) {\n  .highlight {\n    font-size: 1em;\n    margin: 0 0 20px 0;\n    padding: 0.2em 1em;\n  }\n\n  h1 {\n    font-size: 24px;\n  }\n}\n\n@media screen and (max-width: 550px) {\n  button:active {\n    background-color: darkRed;\n  }\n\n  h1 {\n    font-size: 22px;\n  }\n}\n\n@media screen and (max-width: 450px) {\n  h1 {\n    font-size: 20px;\n  }\n}\n"
  },
  {
    "path": "src/js/lib/ga.js",
    "content": "(function(i, s, o, g, r, a, m) {\ni['GoogleAnalyticsObject']=r; i[r]=i[r]||function() {\n  (i[r].q=i[r].q||[]).push(arguments);\n}, i[r].l=1*new Date(); a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0]; a.async=1; a.src=g; m.parentNode.insertBefore(a, m);\n})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');\n\nga('create', 'UA-48530561-1', 'auto');\nga('send', 'pageview');\n"
  },
  {
    "path": "src/js/third_party/graph.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n// taken from chrome://webrtc-internals with jshint adaptions\n\n'use strict';\n/* exported TimelineDataSeries, TimelineGraphView */\n\n// The maximum number of data points bufferred for each stats. Old data points\n// will be shifted out when the buffer is full.\nconst MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;\n\nconst TimelineDataSeries = (function() {\n  /**\n   * @constructor\n   */\n  function TimelineDataSeries() {\n    // List of DataPoints in chronological order.\n    this.dataPoints_ = [];\n\n    // Default color.  Should always be overridden prior to display.\n    this.color_ = 'red';\n    // Whether or not the data series should be drawn.\n    this.isVisible_ = true;\n\n    this.cacheStartTime_ = null;\n    this.cacheStepSize_ = 0;\n    this.cacheValues_ = [];\n  }\n\n  TimelineDataSeries.prototype = {\n    /**\n     * @override\n     */\n    toJSON: function() {\n      if (this.dataPoints_.length < 1) {\n        return {};\n      }\n\n      let values = [];\n      for (let i = 0; i < this.dataPoints_.length; ++i) {\n        values.push(this.dataPoints_[i].value);\n      }\n      return {\n        startTime: this.dataPoints_[0].time,\n        endTime: this.dataPoints_[this.dataPoints_.length - 1].time,\n        values: JSON.stringify(values),\n      };\n    },\n\n    /**\n     * Adds a DataPoint to |this| with the specified time and value.\n     * DataPoints are assumed to be received in chronological order.\n     */\n    addPoint: function(timeTicks, value) {\n      let time = new Date(timeTicks);\n      this.dataPoints_.push(new DataPoint(time, value));\n\n      if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {\n        this.dataPoints_.shift();\n      }\n    },\n\n    isVisible: function() {\n      return this.isVisible_;\n    },\n\n    show: function(isVisible) {\n      this.isVisible_ = isVisible;\n    },\n\n    getColor: function() {\n      return this.color_;\n    },\n\n    setColor: function(color) {\n      this.color_ = color;\n    },\n\n    getCount: function() {\n      return this.dataPoints_.length;\n    },\n    /**\n     * Returns a list containing the values of the data series at |count|\n     * points, starting at |startTime|, and |stepSize| milliseconds apart.\n     * Caches values, so showing/hiding individual data series is fast.\n     */\n    getValues: function(startTime, stepSize, count) {\n      // Use cached values, if we can.\n      if (this.cacheStartTime_ === startTime &&\n        this.cacheStepSize_ === stepSize &&\n        this.cacheValues_.length === count) {\n        return this.cacheValues_;\n      }\n\n      // Do all the work.\n      this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);\n      this.cacheStartTime_ = startTime;\n      this.cacheStepSize_ = stepSize;\n\n      return this.cacheValues_;\n    },\n\n    /**\n     * Returns the cached |values| in the specified time period.\n     */\n    getValuesInternal_: function(startTime, stepSize, count) {\n      let values = [];\n      let nextPoint = 0;\n      let currentValue = 0;\n      let time = startTime;\n      for (let i = 0; i < count; ++i) {\n        while (nextPoint < this.dataPoints_.length &&\n        this.dataPoints_[nextPoint].time < time) {\n          currentValue = this.dataPoints_[nextPoint].value;\n          ++nextPoint;\n        }\n        values[i] = currentValue;\n        time += stepSize;\n      }\n      return values;\n    }\n  };\n\n  /**\n   * A single point in a data series.  Each point has a time, in the form of\n   * milliseconds since the Unix epoch, and a numeric value.\n   * @constructor\n   */\n  function DataPoint(time, value) {\n    this.time = time;\n    this.value = value;\n  }\n\n  return TimelineDataSeries;\n})();\n\nconst TimelineGraphView = (function() {\n  // Maximum number of labels placed vertically along the sides of the graph.\n  let MAX_VERTICAL_LABELS = 6;\n\n  // Vertical spacing between labels and between the graph and labels.\n  let LABEL_VERTICAL_SPACING = 4;\n  // Horizontal spacing between vertically placed labels and the edges of the\n  // graph.\n  let LABEL_HORIZONTAL_SPACING = 3;\n  // Horizintal spacing between two horitonally placed labels along the bottom\n  // of the graph.\n  // var LABEL_LABEL_HORIZONTAL_SPACING = 25;\n\n  // Length of ticks, in pixels, next to y-axis labels.  The x-axis only has\n  // one set of labels, so it can use lines instead.\n  let Y_AXIS_TICK_LENGTH = 10;\n\n  let GRID_COLOR = '#CCC';\n  let TEXT_COLOR = '#000';\n  let BACKGROUND_COLOR = '#FFF';\n\n  let MAX_DECIMAL_PRECISION = 3;\n\n  /**\n   * @constructor\n   */\n  function TimelineGraphView(divId, canvasId) {\n    this.scrollbar_ = {position_: 0, range_: 0};\n\n    this.graphDiv_ = document.getElementById(divId);\n    this.canvas_ = document.getElementById(canvasId);\n\n    // Set the range and scale of the graph.  Times are in milliseconds since\n    // the Unix epoch.\n\n    // All measurements we have must be after this time.\n    this.startTime_ = 0;\n    // The current rightmost position of the graph is always at most this.\n    this.endTime_ = 1;\n\n    this.graph_ = null;\n\n    // Horizontal scale factor, in terms of milliseconds per pixel.\n    this.scale_ = 1000;\n\n    // Initialize the scrollbar.\n    this.updateScrollbarRange_(true);\n  }\n\n  TimelineGraphView.prototype = {\n    setScale: function(scale) {\n      this.scale_ = scale;\n    },\n\n    // Returns the total length of the graph, in pixels.\n    getLength_: function() {\n      let timeRange = this.endTime_ - this.startTime_;\n      // Math.floor is used to ignore the last partial area, of length less\n      // than this.scale_.\n      return Math.floor(timeRange / this.scale_);\n    },\n\n    /**\n     * Returns true if the graph is scrolled all the way to the right.\n     */\n    graphScrolledToRightEdge_: function() {\n      return this.scrollbar_.position_ === this.scrollbar_.range_;\n    },\n\n    /**\n     * Update the range of the scrollbar.  If |resetPosition| is true, also\n     * sets the slider to point at the rightmost position and triggers a\n     * repaint.\n     */\n    updateScrollbarRange_: function(resetPosition) {\n      let scrollbarRange = this.getLength_() - this.canvas_.width;\n      if (scrollbarRange < 0) {\n        scrollbarRange = 0;\n      }\n\n      // If we've decreased the range to less than the current scroll position,\n      // we need to move the scroll position.\n      if (this.scrollbar_.position_ > scrollbarRange) {\n        resetPosition = true;\n      }\n\n      this.scrollbar_.range_ = scrollbarRange;\n      if (resetPosition) {\n        this.scrollbar_.position_ = scrollbarRange;\n        this.repaint();\n      }\n    },\n\n    /**\n     * Sets the date range displayed on the graph, switches to the default\n     * scale factor, and moves the scrollbar all the way to the right.\n     */\n    setDateRange: function(startDate, endDate) {\n      this.startTime_ = startDate.getTime();\n      this.endTime_ = endDate.getTime();\n\n      // Safety check.\n      if (this.endTime_ <= this.startTime_) {\n        this.startTime_ = this.endTime_ - 1;\n      }\n\n      this.updateScrollbarRange_(true);\n    },\n\n    /**\n     * Updates the end time at the right of the graph to be the current time.\n     * Specifically, updates the scrollbar's range, and if the scrollbar is\n     * all the way to the right, keeps it all the way to the right.  Otherwise,\n     * leaves the view as-is and doesn't redraw anything.\n     */\n    updateEndDate: function(optDate) {\n      this.endTime_ = optDate || (new Date()).getTime();\n      this.updateScrollbarRange_(this.graphScrolledToRightEdge_());\n    },\n\n    getStartDate: function() {\n      return new Date(this.startTime_);\n    },\n\n    /**\n     * Replaces the current TimelineDataSeries with |dataSeries|.\n     */\n    setDataSeries: function(dataSeries) {\n      // Simply recreates the Graph.\n      this.graph_ = new Graph();\n      for (let i = 0; i < dataSeries.length; ++i) {\n        this.graph_.addDataSeries(dataSeries[i]);\n      }\n      this.repaint();\n    },\n\n    /**\n     * Adds |dataSeries| to the current graph.\n     */\n    addDataSeries: function(dataSeries) {\n      if (!this.graph_) {\n        this.graph_ = new Graph();\n      }\n      this.graph_.addDataSeries(dataSeries);\n      this.repaint();\n    },\n\n    /**\n     * Draws the graph on |canvas_|.\n     */\n    repaint: function() {\n      this.repaintTimerRunning_ = false;\n\n      let width = this.canvas_.width;\n      let height = this.canvas_.height;\n      let context = this.canvas_.getContext('2d');\n\n      // Clear the canvas.\n      context.fillStyle = BACKGROUND_COLOR;\n      context.fillRect(0, 0, width, height);\n\n      // Try to get font height in pixels.  Needed for layout.\n      let fontHeightString = context.font.match(/([0-9]+)px/)[1];\n      let fontHeight = parseInt(fontHeightString);\n\n      // Safety check, to avoid drawing anything too ugly.\n      if (fontHeightString.length === 0 || fontHeight <= 0 ||\n        fontHeight * 4 > height || width < 50) {\n        return;\n      }\n\n      // Save current transformation matrix so we can restore it later.\n      context.save();\n\n      // The center of an HTML canvas pixel is technically at (0.5, 0.5).  This\n      // makes near straight lines look bad, due to anti-aliasing.  This\n      // translation reduces the problem a little.\n      context.translate(0.5, 0.5);\n\n      // Figure out what time values to display.\n      let position = this.scrollbar_.position_;\n      // If the entire time range is being displayed, align the right edge of\n      // the graph to the end of the time range.\n      if (this.scrollbar_.range_ === 0) {\n        position = this.getLength_() - this.canvas_.width;\n      }\n      let visibleStartTime = this.startTime_ + position * this.scale_;\n\n      // Make space at the bottom of the graph for the time labels, and then\n      // draw the labels.\n      let textHeight = height;\n      height -= fontHeight + LABEL_VERTICAL_SPACING;\n      this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);\n\n      // Draw outline of the main graph area.\n      context.strokeStyle = GRID_COLOR;\n      context.strokeRect(0, 0, width - 1, height - 1);\n\n      if (this.graph_) {\n        // Layout graph and have them draw their tick marks.\n        this.graph_.layout(\n          width, height, fontHeight, visibleStartTime, this.scale_);\n        this.graph_.drawTicks(context);\n\n        // Draw the lines of all graphs, and then draw their labels.\n        this.graph_.drawLines(context);\n        this.graph_.drawLabels(context);\n      }\n\n      // Restore original transformation matrix.\n      context.restore();\n    },\n\n    /**\n     * Draw time labels below the graph.  Takes in start time as an argument\n     * since it may not be |startTime_|, when we're displaying the entire\n     * time range.\n     */\n    drawTimeLabels: function(context, width, height, textHeight, startTime) {\n      // Draw the labels 1 minute apart.\n      let timeStep = 1000 * 60;\n\n      // Find the time for the first label.  This time is a perfect multiple of\n      // timeStep because of how UTC times work.\n      let time = Math.ceil(startTime / timeStep) * timeStep;\n\n      context.textBaseline = 'bottom';\n      context.textAlign = 'center';\n      context.fillStyle = TEXT_COLOR;\n      context.strokeStyle = GRID_COLOR;\n\n      // Draw labels and vertical grid lines.\n      while (true) {\n        let x = Math.round((time - startTime) / this.scale_);\n        if (x >= width) {\n          break;\n        }\n        let text = (new Date(time)).toLocaleTimeString();\n        context.fillText(text, x, textHeight);\n        context.beginPath();\n        context.lineTo(x, 0);\n        context.lineTo(x, height);\n        context.stroke();\n        time += timeStep;\n      }\n    },\n\n    getDataSeriesCount: function() {\n      if (this.graph_) {\n        return this.graph_.dataSeries_.length;\n      }\n      return 0;\n    },\n\n    hasDataSeries: function(dataSeries) {\n      if (this.graph_) {\n        return this.graph_.hasDataSeries(dataSeries);\n      }\n      return false;\n    },\n\n  };\n\n  /**\n   * A Graph is responsible for drawing all the TimelineDataSeries that have\n   * the same data type.  Graphs are responsible for scaling the values, laying\n   * out labels, and drawing both labels and lines for its data series.\n   */\n  const Graph = (function() {\n    /**\n     * @constructor\n     */\n    function Graph() {\n      this.dataSeries_ = [];\n\n      // Cached properties of the graph, set in layout.\n      this.width_ = 0;\n      this.height_ = 0;\n      this.fontHeight_ = 0;\n      this.startTime_ = 0;\n      this.scale_ = 0;\n\n      // The lowest/highest values adjusted by the vertical label step size\n      // in the displayed range of the graph. Used for scaling and setting\n      // labels.  Set in layoutLabels.\n      this.min_ = 0;\n      this.max_ = 0;\n\n      // Cached text of equally spaced labels.  Set in layoutLabels.\n      this.labels_ = [];\n    }\n\n    /**\n     * A Label is the label at a particular position along the y-axis.\n     * @constructor\n     */\n    /*\n    function Label(height, text) {\n      this.height = height;\n      this.text = text;\n    }\n    */\n\n    Graph.prototype = {\n      addDataSeries: function(dataSeries) {\n        this.dataSeries_.push(dataSeries);\n      },\n\n      hasDataSeries: function(dataSeries) {\n        for (let i = 0; i < this.dataSeries_.length; ++i) {\n          if (this.dataSeries_[i] === dataSeries) {\n            return true;\n          }\n        }\n        return false;\n      },\n\n      /**\n       * Returns a list of all the values that should be displayed for a given\n       * data series, using the current graph layout.\n       */\n      getValues: function(dataSeries) {\n        if (!dataSeries.isVisible()) {\n          return null;\n        }\n        return dataSeries.getValues(this.startTime_, this.scale_, this.width_);\n      },\n\n      /**\n       * Updates the graph's layout.  In particular, both the max value and\n       * label positions are updated.  Must be called before calling any of the\n       * drawing functions.\n       */\n      layout: function(width, height, fontHeight, startTime, scale) {\n        this.width_ = width;\n        this.height_ = height;\n        this.fontHeight_ = fontHeight;\n        this.startTime_ = startTime;\n        this.scale_ = scale;\n\n        // Find largest value.\n        let max = 0;\n        let min = 0;\n        for (let i = 0; i < this.dataSeries_.length; ++i) {\n          let values = this.getValues(this.dataSeries_[i]);\n          if (!values) {\n            continue;\n          }\n          for (let j = 0; j < values.length; ++j) {\n            if (values[j] > max) {\n              max = values[j];\n            } else if (values[j] < min) {\n              min = values[j];\n            }\n          }\n        }\n\n        this.layoutLabels_(min, max);\n      },\n\n      /**\n       * Lays out labels and sets |max_|/|min_|, taking the time units into\n       * consideration.  |maxValue| is the actual maximum value, and\n       * |max_| will be set to the value of the largest label, which\n       * will be at least |maxValue|. Similar for |min_|.\n       */\n      layoutLabels_: function(minValue, maxValue) {\n        if (maxValue - minValue < 1024) {\n          this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);\n          return;\n        }\n\n        // Find appropriate units to use.\n        let units = ['', 'k', 'M', 'G', 'T', 'P'];\n        // Units to use for labels.  0 is '1', 1 is K, etc.\n        // We start with 1, and work our way up.\n        let unit = 1;\n        minValue /= 1024;\n        maxValue /= 1024;\n        while (units[unit + 1] && maxValue - minValue >= 1024) {\n          minValue /= 1024;\n          maxValue /= 1024;\n          ++unit;\n        }\n\n        // Calculate labels.\n        this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);\n\n        // Append units to labels.\n        for (let i = 0; i < this.labels_.length; ++i) {\n          this.labels_[i] += ' ' + units[unit];\n        }\n\n        // Convert |min_|/|max_| back to unit '1'.\n        this.min_ *= Math.pow(1024, unit);\n        this.max_ *= Math.pow(1024, unit);\n      },\n\n      /**\n       * Same as layoutLabels_, but ignores units.  |maxDecimalDigits| is the\n       * maximum number of decimal digits allowed.  The minimum allowed\n       * difference between two adjacent labels is 10^-|maxDecimalDigits|.\n       */\n      layoutLabelsBasic_: function(minValue, maxValue, maxDecimalDigits) {\n        this.labels_ = [];\n        let range = maxValue - minValue;\n        // No labels if the range is 0.\n        if (range === 0) {\n          this.min_ = this.max_ = maxValue;\n          return;\n        }\n\n        // The maximum number of equally spaced labels allowed.  |fontHeight_|\n        // is doubled because the top two labels are both drawn in the same\n        // gap.\n        let minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;\n\n        // The + 1 is for the top label.\n        let maxLabels = 1 + this.height_ / minLabelSpacing;\n        if (maxLabels < 2) {\n          maxLabels = 2;\n        } else if (maxLabels > MAX_VERTICAL_LABELS) {\n          maxLabels = MAX_VERTICAL_LABELS;\n        }\n\n        // Initial try for step size between conecutive labels.\n        let stepSize = Math.pow(10, -maxDecimalDigits);\n        // Number of digits to the right of the decimal of |stepSize|.\n        // Used for formating label strings.\n        let stepSizeDecimalDigits = maxDecimalDigits;\n\n        // Pick a reasonable step size.\n        while (true) {\n          // If we use a step size of |stepSize| between labels, we'll need:\n          //\n          // Math.ceil(range / stepSize) + 1\n          //\n          // labels.  The + 1 is because we need labels at both at 0 and at\n          // the top of the graph.\n\n          // Check if we can use steps of size |stepSize|.\n          if (Math.ceil(range / stepSize) + 1 <= maxLabels) {\n            break;\n          }\n          // Check |stepSize| * 2.\n          if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {\n            stepSize *= 2;\n            break;\n          }\n          // Check |stepSize| * 5.\n          if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {\n            stepSize *= 5;\n            break;\n          }\n          stepSize *= 10;\n          if (stepSizeDecimalDigits > 0) {\n            --stepSizeDecimalDigits;\n          }\n        }\n\n        // Set the min/max so it's an exact multiple of the chosen step size.\n        this.max_ = Math.ceil(maxValue / stepSize) * stepSize;\n        this.min_ = Math.floor(minValue / stepSize) * stepSize;\n\n        // Create labels.\n        for (let label = this.max_; label >= this.min_; label -= stepSize) {\n          this.labels_.push(label.toFixed(stepSizeDecimalDigits));\n        }\n      },\n\n      /**\n       * Draws tick marks for each of the labels in |labels_|.\n       */\n      drawTicks: function(context) {\n        let x1;\n        let x2;\n        x1 = this.width_ - 1;\n        x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;\n\n        context.fillStyle = GRID_COLOR;\n        context.beginPath();\n        for (let i = 1; i < this.labels_.length - 1; ++i) {\n          // The rounding is needed to avoid ugly 2-pixel wide anti-aliased\n          // lines.\n          let y = Math.round(this.height_ * i / (this.labels_.length - 1));\n          context.moveTo(x1, y);\n          context.lineTo(x2, y);\n        }\n        context.stroke();\n      },\n\n      /**\n       * Draws a graph line for each of the data series.\n       */\n      drawLines: function(context) {\n        // Factor by which to scale all values to convert them to a number from\n        // 0 to height - 1.\n        let scale = 0;\n        let bottom = this.height_ - 1;\n        if (this.max_) {\n          scale = bottom / (this.max_ - this.min_);\n        }\n\n        // Draw in reverse order, so earlier data series are drawn on top of\n        // subsequent ones.\n        for (let i = this.dataSeries_.length - 1; i >= 0; --i) {\n          let values = this.getValues(this.dataSeries_[i]);\n          if (!values) {\n            continue;\n          }\n          context.strokeStyle = this.dataSeries_[i].getColor();\n          context.beginPath();\n          for (let x = 0; x < values.length; ++x) {\n            // The rounding is needed to avoid ugly 2-pixel wide anti-aliased\n            // horizontal lines.\n            context.lineTo(\n              x, bottom - Math.round((values[x] - this.min_) * scale));\n          }\n          context.stroke();\n        }\n      },\n\n      /**\n       * Draw labels in |labels_|.\n       */\n      drawLabels: function(context) {\n        if (this.labels_.length === 0) {\n          return;\n        }\n        let x = this.width_ - LABEL_HORIZONTAL_SPACING;\n\n        // Set up the context.\n        context.fillStyle = TEXT_COLOR;\n        context.textAlign = 'right';\n\n        // Draw top label, which is the only one that appears below its tick\n        // mark.\n        context.textBaseline = 'top';\n        context.fillText(this.labels_[0], x, 0);\n\n        // Draw all the other labels.\n        context.textBaseline = 'bottom';\n        let step = (this.height_ - 1) / (this.labels_.length - 1);\n        for (let i = 1; i < this.labels_.length; ++i) {\n          context.fillText(this.labels_[i], x, step * i);\n        }\n      }\n    };\n\n    return Graph;\n  })();\n\n  return TimelineGraphView;\n})();\n"
  },
  {
    "path": "src/js/third_party/streamvisualizer.js",
    "content": "/*\n * Copyright 2016 Boris Smus. All Rights Reserved.\n\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Adapted from Boris Smus's demo at http://webaudioapi.com/samples/visualizer\n\n/* globals AudioContext, webkitAudioContext */\n\nconst WIDTH = 308;\nconst HEIGHT = 231;\n\n// Interesting parameters to tweak!\nconst SMOOTHING = 0.8;\nconst FFT_SIZE = 2048;\n\nfunction StreamVisualizer(remoteStream, canvas) {\n  console.log('Creating StreamVisualizer with remoteStream and canvas: ',\n    remoteStream, canvas);\n  this.canvas = canvas;\n  this.drawContext = this.canvas.getContext('2d');\n\n  // cope with browser differences\n  if (typeof AudioContext === 'function') {\n    this.context = new AudioContext();\n  } else if (typeof webkitAudioContext === 'function') {\n    this.context = new webkitAudioContext(); // eslint-disable-line new-cap\n  } else {\n    alert('Sorry! Web Audio is not supported by this browser');\n  }\n\n  // Create a MediaStreamAudioSourceNode from the remoteStream\n  this.source = this.context.createMediaStreamSource(remoteStream);\n  console.log('Created Web Audio source from remote stream: ', this.source);\n\n  this.analyser = this.context.createAnalyser();\n//  this.analyser.connect(this.context.destination);\n  this.analyser.minDecibels = -140;\n  this.analyser.maxDecibels = 0;\n  this.freqs = new Uint8Array(this.analyser.frequencyBinCount);\n  this.times = new Uint8Array(this.analyser.frequencyBinCount);\n\n  this.source.connect(this.analyser);\n\n  this.startTime = 0;\n  this.startOffset = 0;\n}\n\nStreamVisualizer.prototype.start = function() {\n  requestAnimationFrame(this.draw.bind(this));\n};\n\nStreamVisualizer.prototype.draw = function() {\n  let barWidth;\n  let offset;\n  let height;\n  let percent;\n  let value;\n  this.analyser.smoothingTimeConstant = SMOOTHING;\n  this.analyser.fftSize = FFT_SIZE;\n\n  // Get the frequency data from the currently playing music\n  this.analyser.getByteFrequencyData(this.freqs);\n  this.analyser.getByteTimeDomainData(this.times);\n\n\n  this.canvas.width = WIDTH;\n  this.canvas.height = HEIGHT;\n  // Draw the frequency domain chart.\n  for (let i = 0; i < this.analyser.frequencyBinCount; i++) {\n    value = this.freqs[i];\n    percent = value / 256;\n    height = HEIGHT * percent;\n    offset = HEIGHT - height - 1;\n    barWidth = WIDTH / this.analyser.frequencyBinCount;\n    let hue = i/this.analyser.frequencyBinCount * 360;\n    this.drawContext.fillStyle = 'hsl(' + hue + ', 100%, 50%)';\n    this.drawContext.fillRect(i * barWidth, offset, barWidth, height);\n  }\n\n  // Draw the time domain chart.\n  for (let i = 0; i < this.analyser.frequencyBinCount; i++) {\n    value = this.times[i];\n    percent = value / 256;\n    height = HEIGHT * percent;\n    offset = HEIGHT - height - 1;\n    barWidth = WIDTH/this.analyser.frequencyBinCount;\n    this.drawContext.fillStyle = 'white';\n    this.drawContext.fillRect(i * barWidth, offset, 1, 2);\n  }\n\n  requestAnimationFrame(this.draw.bind(this));\n};\n\nStreamVisualizer.prototype.getFrequencyValue = function(freq) {\n  let nyquist = this.context.sampleRate/2;\n  let index = Math.round(freq/nyquist * this.freqs.length);\n  return this.freqs[index];\n};\n"
  },
  {
    "path": "src/js/third_party/webgl_teapot/cameracontroller.js",
    "content": "/*\n * Copyright (c) 2009 The Chromium Authors. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *    * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *    * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *    * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n// A simple camera controller which uses an HTML element as the event\n// source for constructing a view matrix. Assign an \"onchange\"\n// function to the controller as follows to receive the updated X and\n// Y angles for the camera:\n//\n//   var controller = new CameraController(canvas);\n//   controller.onchange = function(xRot, yRot) { ... };\n//\n// The view matrix is computed elsewhere.\n//\n// opt_canvas (an HTMLCanvasElement) and opt_context (a\n// WebGLRenderingContext) can be passed in to make the hit detection\n// more precise -- only opaque pixels will be considered as the start\n// of a drag action.\nfunction CameraController(element, opt_canvas, opt_context) {\n    var controller = this;\n    this.onchange = null;\n    this.xRot = 0;\n    this.yRot = 0;\n    this.scaleFactor = 3.0;\n    this.dragging = false;\n    this.curX = 0;\n    this.curY = 0;\n\n    if (opt_canvas)\n        this.canvas_ = opt_canvas;\n\n    if (opt_context)\n        this.context_ = opt_context;\n\n    function mouseDown(ev) {\n        controller.curX = ev.clientX;\n        controller.curY = ev.clientY;\n        var dragging = false;\n        if (controller.canvas_ && controller.context_) {\n            var rect = controller.canvas_.getBoundingClientRect();\n            // Transform the event's x and y coordinates into the coordinate\n            // space of the canvas\n            var canvasRelativeX = ev.pageX - rect.left;\n            var canvasRelativeY = ev.pageY - rect.top;\n            var canvasWidth = controller.canvas_.width;\n            var canvasHeight = controller.canvas_.height;\n\n            // Read back a small portion of the frame buffer around this point\n            if (canvasRelativeX > 0 && canvasRelativeX < canvasWidth &&\n                canvasRelativeY > 0 && canvasRelativeY < canvasHeight) {\n                var pixels = new Uint8Array(1);\n                controller.context_.readPixels(canvasRelativeX,\n                                               canvasHeight - canvasRelativeY,\n                                               1,\n                                               1,\n                                               controller.context_.RGBA,\n                                               controller.context_.UNSIGNED_BYTE,\n                                               pixels);\n                // See whether this pixel has an alpha value of >= about 10%\n                if (pixels[3] > (255.0 / 10.0)) {\n                    dragging = true;\n                }\n            }\n        } else {\n            dragging = true;\n        }\n\n        controller.dragging = dragging;\n    }\n\n    function mouseMove(ev) {\n        if (controller.dragging) {\n            // Determine how far we have moved since the last mouse move\n            // event.\n            var curX = ev.clientX;\n            var curY = ev.clientY;\n            var deltaX = (controller.curX - curX) / controller.scaleFactor;\n            var deltaY = (controller.curY - curY) / controller.scaleFactor;\n            controller.curX = curX;\n            controller.curY = curY;\n            // Update the X and Y rotation angles based on the mouse motion.\n            controller.yRot = (controller.yRot + deltaX) % 360;\n            controller.xRot = (controller.xRot + deltaY);\n            // Clamp the X rotation to prevent the camera from going upside\n            // down.\n            if (controller.xRot < -90) {\n                controller.xRot = -90;\n            } else if (controller.xRot > 90) {\n                controller.xRot = 90;\n            }\n            // Send the onchange event to any listener.\n            if (controller.onchange != null) {\n                controller.onchange(controller.xRot, controller.yRot);\n            }\n        }\n    }\n\n    function mouseUp(ev) {\n        controller.dragging = false;\n    }\n\n    element.addEventListener(\"mousedown\", mouseDown, false);\n    element.addEventListener(\"mousemove\", mouseMove, false);\n    element.addEventListener(\"mouseup\", mouseUp, false);\n\n    var activeTouchIdentifier;\n\n    function findActiveTouch(touches) {\n        for (var ii = 0; ii < touches.length; ++ii) {\n            if (touches.item(ii).identifier == activeTouchIdentifier) {\n                return touches.item(ii);\n            }\n        }\n        return null;\n    }\n\n    function touchStart(ev) {\n        if (controller.dragging || ev.targetTouches.length == 0) {\n            return;\n        }\n        var touch = ev.targetTouches.item(0);\n        mouseDown(touch);\n        if (controller.dragging) {\n            activeTouchIdentifier = touch.identifier;\n        }\n        ev.preventDefault();\n    }\n\n    function touchMove(ev) {\n        if (!controller.dragging) {\n            return;\n        }\n        var touch = findActiveTouch(ev.changedTouches);\n        if (touch) {\n            mouseMove(touch);\n        }\n        ev.preventDefault();\n    }\n\n    function touchEnd(ev) {\n        var touch = findActiveTouch(ev.changedTouches);\n        if (touch) {\n            mouseUp(touch);\n        }\n        ev.preventDefault();\n    }\n\n    element.addEventListener(\"touchstart\", touchStart, false);\n    element.addEventListener(\"touchmove\", touchMove, false);\n    element.addEventListener(\"touchend\", touchEnd, false);\n    element.addEventListener(\"touchcancel\", touchEnd, false);\n}\n"
  },
  {
    "path": "src/js/third_party/webgl_teapot/demo.js",
    "content": "/*\n * Copyright (c) 2009 The Chromium Authors. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *    * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *    * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *    * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nvar gl = null;\nvar g_width = 0;\nvar g_height = 0;\nvar g_bumpTexture = null;\nvar g_envTexture = null;\nvar g_programObject = null;\nvar g_vbo = null;\nvar g_elementVbo = null;\nvar g_normalsOffset = 0;\nvar g_tangentsOffset = 0;\nvar g_binormalsOffset = 0;\nvar g_texCoordsOffset = 0;\nvar g_numElements = 0;\n\n// Uniform variables\nvar g_worldLoc = 0;\nvar g_worldInverseTransposeLoc = 0;\nvar g_worldViewProjLoc = 0;\nvar g_viewInverseLoc = 0;\nvar g_normalSamplerLoc = 0;\nvar g_envSamplerLoc = 0;\n\nvar g_pendingTextureLoads = 0;\n\n// The \"model\" matrix is the \"world\" matrix in Standard Annotations\n// and Semantics\nvar model = new Matrix4x4();\nvar view = new Matrix4x4();\nvar projection = new Matrix4x4();\n\nvar controller = null;\n\nfunction main() {\n    var c = document.querySelector(\"canvas\");\n\n    //c = WebGLDebugUtils.makeLostContextSimulatingCanvas(c);\n    // tell the simulator when to lose context.\n    //c.loseContextInNCalls(15);\n\n    c.addEventListener('webglcontextlost', handleContextLost, false);\n    c.addEventListener('webglcontextrestored', handleContextRestored, false);\n\n\tvar ratio = window.devicePixelRatio ? window.devicePixelRatio : 1;\n    // original is 480 x 270\n\tc.width = 240 * ratio;\n\tc.height = 180 * ratio;\n    gl = WebGLUtils.setupWebGL(c);\n    if (!gl)\n        return;\n    g_width = c.width;\n    g_height = c.height;\n    controller = new CameraController(c);\n    // Try the following (and uncomment the \"pointer-events: none;\" in\n    // the index.html) to try the more precise hit detection\n    //  controller = new CameraController(document.getElementById(\"body\"), c, gl);\n    controller.onchange = function(xRot, yRot) {\n        draw();\n    };\n    init();\n}\n\nfunction log(msg) {\n    if (window.console && window.console.log) {\n        console.log(msg);\n    }\n}\n\nfunction handleContextLost(e) {\n    log(\"handle context lost\");\n    e.preventDefault();\n    clearLoadingImages();\n}\n\nfunction handleContextRestored() {\n    log(\"handle context restored\");\n    init();\n}\n\n\nfunction output(str) {\n    document.body.appendChild(document.createTextNode(str));\n    document.body.appendChild(document.createElement(\"br\"));\n}\n\nfunction checkGLError() {\n    var error = gl.getError();\n    if (error != gl.NO_ERROR && error != gl.CONTEXT_LOST_WEBGL) {\n        var str = \"GL Error: \" + error;\n        output(str);\n        throw str;\n    }\n}\n\nfunction init() {\n    gl.enable(gl.DEPTH_TEST);\n    // Can use this to make the background opaque\n    // gl.clearColor(0.3, 0.2, 0.2, 1.);\n    gl.clearColor(0.0, 0.0, 0.0, 0.0);\n    initTeapot();\n    initShaders();\n    g_bumpTexture = loadTexture(\"../../../js/third_party/webgl_teapot/images/bump.jpg\");\n    g_envTexture = loadCubeMap(\"../../../js/third_party/webgl_teapot/images/skybox\", \"jpg\");\n    draw();\n}\n\nfunction initTeapot() {\n    g_vbo = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, g_vbo);\n    gl.bufferData(gl.ARRAY_BUFFER,\n                  teapotPositions.byteLength +\n                  teapotNormals.byteLength +\n                  teapotTangents.byteLength +\n                  teapotBinormals.byteLength +\n                  teapotTexCoords.byteLength,\n                  gl.STATIC_DRAW);\n    g_normalsOffset = teapotPositions.byteLength;\n    g_tangentsOffset = g_normalsOffset + teapotNormals.byteLength;\n    g_binormalsOffset = g_tangentsOffset + teapotTangents.byteLength;\n    g_texCoordsOffset = g_binormalsOffset + teapotBinormals.byteLength;\n    gl.bufferSubData(gl.ARRAY_BUFFER, 0, teapotPositions);\n    gl.bufferSubData(gl.ARRAY_BUFFER, g_normalsOffset, teapotNormals);\n    gl.bufferSubData(gl.ARRAY_BUFFER, g_tangentsOffset, teapotTangents);\n    gl.bufferSubData(gl.ARRAY_BUFFER, g_binormalsOffset, teapotBinormals);\n    gl.bufferSubData(gl.ARRAY_BUFFER, g_texCoordsOffset, teapotTexCoords);\n\n    g_elementVbo = gl.createBuffer();\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_elementVbo);\n    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, teapotIndices, gl.STATIC_DRAW);\n    g_numElements = teapotIndices.length;\n}\n\nvar bumpReflectVertexSource = [\n    \"attribute vec3 g_Position;\",\n    \"attribute vec3 g_TexCoord0;\",\n    \"attribute vec3 g_Tangent;\",\n    \"attribute vec3 g_Binormal;\",\n    \"attribute vec3 g_Normal;\",\n    \"\",\n    \"uniform mat4 world;\",\n    \"uniform mat4 worldInverseTranspose;\",\n    \"uniform mat4 worldViewProj;\",\n    \"uniform mat4 viewInverse;\",\n    \"\",\n    \"varying vec2 texCoord;\",\n    \"varying vec3 worldEyeVec;\",\n    \"varying vec3 worldNormal;\",\n    \"varying vec3 worldTangent;\",\n    \"varying vec3 worldBinorm;\",\n    \"\",\n    \"void main() {\",\n    \"  gl_Position = worldViewProj * vec4(g_Position.xyz, 1.);\",\n    \"  texCoord.xy = g_TexCoord0.xy;\",\n    \"  worldNormal = (worldInverseTranspose * vec4(g_Normal, 1.)).xyz;\",\n    \"  worldTangent = (worldInverseTranspose * vec4(g_Tangent, 1.)).xyz;\",\n    \"  worldBinorm = (worldInverseTranspose * vec4(g_Binormal, 1.)).xyz;\",\n    \"  vec3 worldPos = (world * vec4(g_Position, 1.)).xyz;\",\n    \"  worldEyeVec = normalize(worldPos - viewInverse[3].xyz);\",\n    \"}\"\n    ].join(\"\\n\");\n\nvar bumpReflectFragmentSource = [\n    \"precision mediump float;\\n\",\n    \"const float bumpHeight = 0.2;\",\n    \"\",\n    \"uniform sampler2D normalSampler;\",\n    \"uniform samplerCube envSampler;\",\n    \"\",\n    \"varying vec2 texCoord;\",\n    \"varying vec3 worldEyeVec;\",\n    \"varying vec3 worldNormal;\",\n    \"varying vec3 worldTangent;\",\n    \"varying vec3 worldBinorm;\",\n    \"\",\n    \"void main() {\",\n    \"  vec2 bump = (texture2D(normalSampler, texCoord.xy).xy * 2.0 - 1.0) * bumpHeight;\",\n    \"  vec3 normal = normalize(worldNormal);\",\n    \"  vec3 tangent = normalize(worldTangent);\",\n    \"  vec3 binormal = normalize(worldBinorm);\",\n    \"  vec3 nb = normal + bump.x * tangent + bump.y * binormal;\",\n    \"  nb = normalize(nb);\",\n    \"  vec3 worldEye = normalize(worldEyeVec);\",\n    \"  vec3 lookup = reflect(worldEye, nb);\",\n    \"  vec4 color = textureCube(envSampler, lookup);\",\n    \"  gl_FragColor = color;\",\n    \"}\"\n    ].join(\"\\n\");\n\nfunction loadShader(type, shaderSrc) {\n    var shader = gl.createShader(type);\n    // Load the shader source\n    gl.shaderSource(shader, shaderSrc);\n    // Compile the shader\n    gl.compileShader(shader);\n    // Check the compile status\n    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS) &&\n        !gl.isContextLost()) {\n        var infoLog = gl.getShaderInfoLog(shader);\n        output(\"Error compiling shader:\\n\" + infoLog);\n        gl.deleteShader(shader);\n        return null;\n    }\n    return shader;\n}\n\nfunction initShaders() {\n    var vertexShader = loadShader(gl.VERTEX_SHADER, bumpReflectVertexSource);\n    var fragmentShader = loadShader(gl.FRAGMENT_SHADER, bumpReflectFragmentSource);\n    // Create the program object\n    var programObject = gl.createProgram();\n    gl.attachShader(programObject, vertexShader);\n    gl.attachShader(programObject, fragmentShader);\n    // Bind attributes\n    gl.bindAttribLocation(programObject, 0, \"g_Position\");\n    gl.bindAttribLocation(programObject, 1, \"g_TexCoord0\");\n    gl.bindAttribLocation(programObject, 2, \"g_Tangent\");\n    gl.bindAttribLocation(programObject, 3, \"g_Binormal\");\n    gl.bindAttribLocation(programObject, 4, \"g_Normal\");\n    // Link the program\n    gl.linkProgram(programObject);\n    // Check the link status\n    var linked = gl.getProgramParameter(programObject, gl.LINK_STATUS);\n    if (!linked && !gl.isContextLost()) {\n        var infoLog = gl.getProgramInfoLog(programObject);\n        output(\"Error linking program:\\n\" + infoLog);\n        gl.deleteProgram(programObject);\n        return;\n    }\n    g_programObject = programObject;\n    // Look up uniform locations\n    g_worldLoc = gl.getUniformLocation(g_programObject, \"world\");\n    g_worldInverseTransposeLoc = gl.getUniformLocation(g_programObject, \"worldInverseTranspose\");\n    g_worldViewProjLoc = gl.getUniformLocation(g_programObject, \"worldViewProj\");\n    g_viewInverseLoc = gl.getUniformLocation(g_programObject, \"viewInverse\");\n    g_normalSamplerLoc = gl.getUniformLocation(g_programObject, \"normalSampler\");\n    g_envSamplerLoc = gl.getUniformLocation(g_programObject, \"envSampler\");\n    checkGLError();\n}\n\nfunction draw() {\n    // Note: the viewport is automatically set up to cover the entire Canvas.\n    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n    checkGLError();\n\n    // For now, don't render if we have incomplete textures, just to\n    // avoid accidentally incurring OpenGL errors -- although we should\n    // be fully able to load textures in in the background\n    if (g_pendingTextureLoads > 0) {\n        return;\n    }\n\n    // Set up the model, view and projection matrices\n    projection.loadIdentity();\n    projection.perspective(45, g_width / g_height, 10, 500);\n    view.loadIdentity();\n    view.translate(0, -10, -100.0);\n\n    // Add in camera controller's rotation\n    model.loadIdentity();\n    model.rotate(controller.xRot, 1, 0, 0);\n    model.rotate(controller.yRot, 0, 1, 0);\n\n    // Correct for initial placement and orientation of model\n    model.translate(0, -10, 0);\n    model.rotate(90, 1, 0, 0);\n\n    gl.useProgram(g_programObject);\n\n    // Compute necessary matrices\n    var mvp = new Matrix4x4();\n    mvp.multiply(model);\n    mvp.multiply(view);\n    mvp.multiply(projection);\n    var worldInverseTranspose = model.inverse();\n    worldInverseTranspose.transpose();\n    var viewInverse = view.inverse();\n\n    // Set up uniforms\n    gl.uniformMatrix4fv(g_worldLoc, gl.FALSE, new Float32Array(model.elements));\n    gl.uniformMatrix4fv(g_worldInverseTransposeLoc, gl.FALSE, new Float32Array(worldInverseTranspose.elements));\n    gl.uniformMatrix4fv(g_worldViewProjLoc, gl.FALSE, new Float32Array(mvp.elements));\n    gl.uniformMatrix4fv(g_viewInverseLoc, gl.FALSE, new Float32Array(viewInverse.elements));\n    gl.activeTexture(gl.TEXTURE0);\n    gl.bindTexture(gl.TEXTURE_2D, g_bumpTexture);\n    gl.uniform1i(g_normalSamplerLoc, 0);\n    gl.activeTexture(gl.TEXTURE1);\n    gl.bindTexture(gl.TEXTURE_CUBE_MAP, g_envTexture);\n    gl.uniform1i(g_envSamplerLoc, 1);\n    checkGLError();\n\n    // Bind and set up vertex streams\n    gl.bindBuffer(gl.ARRAY_BUFFER, g_vbo);\n    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);\n    gl.enableVertexAttribArray(0);\n    gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, g_texCoordsOffset);\n    gl.enableVertexAttribArray(1);\n    gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, g_tangentsOffset);\n    gl.enableVertexAttribArray(2);\n    gl.vertexAttribPointer(3, 3, gl.FLOAT, false, 0, g_binormalsOffset);\n    gl.enableVertexAttribArray(3);\n    gl.vertexAttribPointer(4, 3, gl.FLOAT, false, 0, g_normalsOffset);\n    gl.enableVertexAttribArray(4);\n    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_elementVbo);\n    checkGLError();\n    gl.drawElements(gl.TRIANGLES, g_numElements, gl.UNSIGNED_SHORT, 0);\n}\n\n// Array of images curently loading\nvar g_loadingImages = [];\n\n// Clears all the images currently loading.\n// This is used to handle context lost events.\nfunction clearLoadingImages() {\n    for (var ii = 0; ii < g_loadingImages.length; ++ii) {\n        g_loadingImages[ii].onload = undefined;\n    }\n    g_loadingImages = [];\n}\n\nfunction loadTexture(src) {\n    var texture = gl.createTexture();\n    gl.bindTexture(gl.TEXTURE_2D, texture);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);\n    ++g_pendingTextureLoads;\n    var image = new Image();\n    g_loadingImages.push(image);\n    image.onload = function() {\n        g_loadingImages.splice(g_loadingImages.indexOf(image), 1);\n        --g_pendingTextureLoads;\n        gl.bindTexture(gl.TEXTURE_2D, texture);\n        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n        gl.texImage2D(\n            gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);\n        checkGLError();\n        draw();\n    };\n    image.src = src;\n    return texture;\n}\n\nfunction loadCubeMap(base, suffix) {\n    var texture = gl.createTexture();\n    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);\n    checkGLError();\n    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    checkGLError();\n    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    checkGLError();\n    // FIXME: TEXTURE_WRAP_R doesn't exist in OpenGL ES?!\n    //  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);\n    //  checkGLError();\n    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n    checkGLError();\n    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    checkGLError();\n    var faces = [[\"posx\", gl.TEXTURE_CUBE_MAP_POSITIVE_X],\n                 [\"negx\", gl.TEXTURE_CUBE_MAP_NEGATIVE_X],\n                 [\"posy\", gl.TEXTURE_CUBE_MAP_POSITIVE_Y],\n                 [\"negy\", gl.TEXTURE_CUBE_MAP_NEGATIVE_Y],\n                 [\"posz\", gl.TEXTURE_CUBE_MAP_POSITIVE_Z],\n                 [\"negz\", gl.TEXTURE_CUBE_MAP_NEGATIVE_Z]];\n    for (var i = 0; i < faces.length; i++) {\n        var url = base + \"-\" + faces[i][0] + \".\" + suffix;\n        var face = faces[i][1];\n        ++g_pendingTextureLoads;\n        var image = new Image();\n        g_loadingImages.push(image);\n        // Javascript has function, not block, scope.\n        // See \"JavaScript: The Good Parts\", Chapter 4, \"Functions\",\n        // section \"Scope\".\n        image.onload = function(texture, face, image, url) {\n            return function() {\n                g_loadingImages.splice(g_loadingImages.indexOf(image), 1);\n                --g_pendingTextureLoads;\n                gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);\n                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n                gl.texImage2D(\n                   face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);\n                checkGLError();\n                draw();\n            }\n        }(texture, face, image, url);\n        console.log(url);\n        image.src = url;\n    }\n    return texture;\n}\n"
  },
  {
    "path": "src/js/third_party/webgl_teapot/matrix4x4.js",
    "content": "/*\n * Copyright (c) 2009, Mozilla Corp\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *     * Redistributions of source code must retain the above copyright\n *       notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above copyright\n *       notice, this list of conditions and the following disclaimer in the\n *       documentation and/or other materials provided with the distribution.\n *     * Neither the name of the <organization> nor the\n *       names of its contributors may be used to endorse or promote products\n *       derived from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY\n * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/*\n * Based on sample code from the OpenGL(R) ES 2.0 Programming Guide, which carriers\n * the following header:\n *\n * Book:      OpenGL(R) ES 2.0 Programming Guide\n * Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner\n * ISBN-10:   0321502795\n * ISBN-13:   9780321502797\n * Publisher: Addison-Wesley Professional\n * URLs:      http://safari.informit.com/9780321563835\n *            http://www.opengles-book.com\n */\n\n//\n// A simple 4x4 Matrix utility class\n//\n\nfunction Matrix4x4() {\n  this.elements = Array(16);\n  this.loadIdentity();\n}\n\nMatrix4x4.prototype = {\n  scale: function (sx, sy, sz) {\n    this.elements[0*4+0] *= sx;\n    this.elements[0*4+1] *= sx;\n    this.elements[0*4+2] *= sx;\n    this.elements[0*4+3] *= sx;\n\n    this.elements[1*4+0] *= sy;\n    this.elements[1*4+1] *= sy;\n    this.elements[1*4+2] *= sy;\n    this.elements[1*4+3] *= sy;\n\n    this.elements[2*4+0] *= sz;\n    this.elements[2*4+1] *= sz;\n    this.elements[2*4+2] *= sz;\n    this.elements[2*4+3] *= sz;\n\n    return this;\n  },\n\n  translate: function (tx, ty, tz) {\n    this.elements[3*4+0] += this.elements[0*4+0] * tx + this.elements[1*4+0] * ty + this.elements[2*4+0] * tz;\n    this.elements[3*4+1] += this.elements[0*4+1] * tx + this.elements[1*4+1] * ty + this.elements[2*4+1] * tz;\n    this.elements[3*4+2] += this.elements[0*4+2] * tx + this.elements[1*4+2] * ty + this.elements[2*4+2] * tz;\n    this.elements[3*4+3] += this.elements[0*4+3] * tx + this.elements[1*4+3] * ty + this.elements[2*4+3] * tz;\n\n    return this;\n  },\n\n  rotate: function (angle, x, y, z) {\n    var mag = Math.sqrt(x*x + y*y + z*z);\n    var sinAngle = Math.sin(angle * Math.PI / 180.0);\n    var cosAngle = Math.cos(angle * Math.PI / 180.0);\n\n    if (mag > 0) {\n      var xx, yy, zz, xy, yz, zx, xs, ys, zs;\n      var oneMinusCos;\n      var rotMat;\n\n      x /= mag;\n      y /= mag;\n      z /= mag;\n\n      xx = x * x;\n      yy = y * y;\n      zz = z * z;\n      xy = x * y;\n      yz = y * z;\n      zx = z * x;\n      xs = x * sinAngle;\n      ys = y * sinAngle;\n      zs = z * sinAngle;\n      oneMinusCos = 1.0 - cosAngle;\n\n      rotMat = new Matrix4x4();\n\n      rotMat.elements[0*4+0] = (oneMinusCos * xx) + cosAngle;\n      rotMat.elements[0*4+1] = (oneMinusCos * xy) - zs;\n      rotMat.elements[0*4+2] = (oneMinusCos * zx) + ys;\n      rotMat.elements[0*4+3] = 0.0;\n\n      rotMat.elements[1*4+0] = (oneMinusCos * xy) + zs;\n      rotMat.elements[1*4+1] = (oneMinusCos * yy) + cosAngle;\n      rotMat.elements[1*4+2] = (oneMinusCos * yz) - xs;\n      rotMat.elements[1*4+3] = 0.0;\n\n      rotMat.elements[2*4+0] = (oneMinusCos * zx) - ys;\n      rotMat.elements[2*4+1] = (oneMinusCos * yz) + xs;\n      rotMat.elements[2*4+2] = (oneMinusCos * zz) + cosAngle;\n      rotMat.elements[2*4+3] = 0.0;\n\n      rotMat.elements[3*4+0] = 0.0;\n      rotMat.elements[3*4+1] = 0.0;\n      rotMat.elements[3*4+2] = 0.0;\n      rotMat.elements[3*4+3] = 1.0;\n\n      rotMat = rotMat.multiply(this);\n      this.elements = rotMat.elements;\n    }\n\n    return this;\n  },\n\n  frustum: function (left, right, bottom, top, nearZ, farZ) {\n    var deltaX = right - left;\n    var deltaY = top - bottom;\n    var deltaZ = farZ - nearZ;\n    var frust;\n\n    if ( (nearZ <= 0.0) || (farZ <= 0.0) ||\n         (deltaX <= 0.0) || (deltaY <= 0.0) || (deltaZ <= 0.0) )\n         return this;\n\n    frust = new Matrix4x4();\n\n    frust.elements[0*4+0] = 2.0 * nearZ / deltaX;\n    frust.elements[0*4+1] = frust.elements[0*4+2] = frust.elements[0*4+3] = 0.0;\n\n    frust.elements[1*4+1] = 2.0 * nearZ / deltaY;\n    frust.elements[1*4+0] = frust.elements[1*4+2] = frust.elements[1*4+3] = 0.0;\n\n    frust.elements[2*4+0] = (right + left) / deltaX;\n    frust.elements[2*4+1] = (top + bottom) / deltaY;\n    frust.elements[2*4+2] = -(nearZ + farZ) / deltaZ;\n    frust.elements[2*4+3] = -1.0;\n\n    frust.elements[3*4+2] = -2.0 * nearZ * farZ / deltaZ;\n    frust.elements[3*4+0] = frust.elements[3*4+1] = frust.elements[3*4+3] = 0.0;\n\n    frust = frust.multiply(this);\n    this.elements = frust.elements;\n\n    return this;\n  },\n\n  perspective: function (fovy, aspect, nearZ, farZ) {\n    var frustumH = Math.tan(fovy / 360.0 * Math.PI) * nearZ;\n    var frustumW = frustumH * aspect;\n\n    return this.frustum(-frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);\n  },\n\n  ortho: function (left, right, bottom, top, nearZ, farZ) {\n    var deltaX = right - left;\n    var deltaY = top - bottom;\n    var deltaZ = farZ - nearZ;\n\n    var ortho = new Matrix4x4();\n\n    if ( (deltaX == 0.0) || (deltaY == 0.0) || (deltaZ == 0.0) )\n        return this;\n\n    ortho.elements[0*4+0] = 2.0 / deltaX;\n    ortho.elements[3*4+0] = -(right + left) / deltaX;\n    ortho.elements[1*4+1] = 2.0 / deltaY;\n    ortho.elements[3*4+1] = -(top + bottom) / deltaY;\n    ortho.elements[2*4+2] = -2.0 / deltaZ;\n    ortho.elements[3*4+2] = -(nearZ + farZ) / deltaZ;\n\n    ortho = ortho.multiply(this);\n    this.elements = ortho.elements;\n\n    return this;\n  },\n\n  multiply: function (right) {\n    var tmp = new Matrix4x4();\n\n    for (var i = 0; i < 4; i++) {\n      tmp.elements[i*4+0] =\n\t(this.elements[i*4+0] * right.elements[0*4+0]) +\n\t(this.elements[i*4+1] * right.elements[1*4+0]) +\n\t(this.elements[i*4+2] * right.elements[2*4+0]) +\n\t(this.elements[i*4+3] * right.elements[3*4+0]) ;\n\n      tmp.elements[i*4+1] =\n\t(this.elements[i*4+0] * right.elements[0*4+1]) +\n\t(this.elements[i*4+1] * right.elements[1*4+1]) +\n\t(this.elements[i*4+2] * right.elements[2*4+1]) +\n\t(this.elements[i*4+3] * right.elements[3*4+1]) ;\n\n      tmp.elements[i*4+2] =\n\t(this.elements[i*4+0] * right.elements[0*4+2]) +\n\t(this.elements[i*4+1] * right.elements[1*4+2]) +\n\t(this.elements[i*4+2] * right.elements[2*4+2]) +\n\t(this.elements[i*4+3] * right.elements[3*4+2]) ;\n\n      tmp.elements[i*4+3] =\n\t(this.elements[i*4+0] * right.elements[0*4+3]) +\n\t(this.elements[i*4+1] * right.elements[1*4+3]) +\n\t(this.elements[i*4+2] * right.elements[2*4+3]) +\n\t(this.elements[i*4+3] * right.elements[3*4+3]) ;\n    }\n\n    this.elements = tmp.elements;\n    return this;\n  },\n\n  copy: function () {\n    var tmp = new Matrix4x4();\n    for (var i = 0; i < 16; i++) {\n      tmp.elements[i] = this.elements[i];\n    }\n    return tmp;\n  },\n\n  get: function (row, col) {\n    return this.elements[4*row+col];\n  },\n\n  // In-place inversion\n  invert: function () {\n    var tmp_0 = this.get(2,2) * this.get(3,3);\n    var tmp_1 = this.get(3,2) * this.get(2,3);\n    var tmp_2 = this.get(1,2) * this.get(3,3);\n    var tmp_3 = this.get(3,2) * this.get(1,3);\n    var tmp_4 = this.get(1,2) * this.get(2,3);\n    var tmp_5 = this.get(2,2) * this.get(1,3);\n    var tmp_6 = this.get(0,2) * this.get(3,3);\n    var tmp_7 = this.get(3,2) * this.get(0,3);\n    var tmp_8 = this.get(0,2) * this.get(2,3);\n    var tmp_9 = this.get(2,2) * this.get(0,3);\n    var tmp_10 = this.get(0,2) * this.get(1,3);\n    var tmp_11 = this.get(1,2) * this.get(0,3);\n    var tmp_12 = this.get(2,0) * this.get(3,1);\n    var tmp_13 = this.get(3,0) * this.get(2,1);\n    var tmp_14 = this.get(1,0) * this.get(3,1);\n    var tmp_15 = this.get(3,0) * this.get(1,1);\n    var tmp_16 = this.get(1,0) * this.get(2,1);\n    var tmp_17 = this.get(2,0) * this.get(1,1);\n    var tmp_18 = this.get(0,0) * this.get(3,1);\n    var tmp_19 = this.get(3,0) * this.get(0,1);\n    var tmp_20 = this.get(0,0) * this.get(2,1);\n    var tmp_21 = this.get(2,0) * this.get(0,1);\n    var tmp_22 = this.get(0,0) * this.get(1,1);\n    var tmp_23 = this.get(1,0) * this.get(0,1);\n\n    var t0 = ((tmp_0 * this.get(1,1) + tmp_3 * this.get(2,1) + tmp_4 * this.get(3,1)) -\n              (tmp_1 * this.get(1,1) + tmp_2 * this.get(2,1) + tmp_5 * this.get(3,1)));\n    var t1 = ((tmp_1 * this.get(0,1) + tmp_6 * this.get(2,1) + tmp_9 * this.get(3,1)) -\n              (tmp_0 * this.get(0,1) + tmp_7 * this.get(2,1) + tmp_8 * this.get(3,1)));\n    var t2 = ((tmp_2 * this.get(0,1) + tmp_7 * this.get(1,1) + tmp_10 * this.get(3,1)) -\n              (tmp_3 * this.get(0,1) + tmp_6 * this.get(1,1) + tmp_11 * this.get(3,1)));\n    var t3 = ((tmp_5 * this.get(0,1) + tmp_8 * this.get(1,1) + tmp_11 * this.get(2,1)) -\n              (tmp_4 * this.get(0,1) + tmp_9 * this.get(1,1) + tmp_10 * this.get(2,1)));\n\n    var d = 1.0 / (this.get(0,0) * t0 + this.get(1,0) * t1 + this.get(2,0) * t2 + this.get(3,0) * t3);\n\n    var out_00 = d * t0;\n    var out_01 = d * t1;\n    var out_02 = d * t2;\n    var out_03 = d * t3;\n\n    var out_10 = d * ((tmp_1 * this.get(1,0) + tmp_2 * this.get(2,0) + tmp_5 * this.get(3,0)) -\n                      (tmp_0 * this.get(1,0) + tmp_3 * this.get(2,0) + tmp_4 * this.get(3,0)));\n    var out_11 = d * ((tmp_0 * this.get(0,0) + tmp_7 * this.get(2,0) + tmp_8 * this.get(3,0)) -\n                      (tmp_1 * this.get(0,0) + tmp_6 * this.get(2,0) + tmp_9 * this.get(3,0)));\n    var out_12 = d * ((tmp_3 * this.get(0,0) + tmp_6 * this.get(1,0) + tmp_11 * this.get(3,0)) -\n                      (tmp_2 * this.get(0,0) + tmp_7 * this.get(1,0) + tmp_10 * this.get(3,0)));\n    var out_13 = d * ((tmp_4 * this.get(0,0) + tmp_9 * this.get(1,0) + tmp_10 * this.get(2,0)) -\n                      (tmp_5 * this.get(0,0) + tmp_8 * this.get(1,0) + tmp_11 * this.get(2,0)));\n\n    var out_20 = d * ((tmp_12 * this.get(1,3) + tmp_15 * this.get(2,3) + tmp_16 * this.get(3,3)) -\n                      (tmp_13 * this.get(1,3) + tmp_14 * this.get(2,3) + tmp_17 * this.get(3,3)));\n    var out_21 = d * ((tmp_13 * this.get(0,3) + tmp_18 * this.get(2,3) + tmp_21 * this.get(3,3)) -\n                      (tmp_12 * this.get(0,3) + tmp_19 * this.get(2,3) + tmp_20 * this.get(3,3)));\n    var out_22 = d * ((tmp_14 * this.get(0,3) + tmp_19 * this.get(1,3) + tmp_22 * this.get(3,3)) -\n                      (tmp_15 * this.get(0,3) + tmp_18 * this.get(1,3) + tmp_23 * this.get(3,3)));\n    var out_23 = d * ((tmp_17 * this.get(0,3) + tmp_20 * this.get(1,3) + tmp_23 * this.get(2,3)) -\n                      (tmp_16 * this.get(0,3) + tmp_21 * this.get(1,3) + tmp_22 * this.get(2,3)));\n    \n    var out_30 = d * ((tmp_14 * this.get(2,2) + tmp_17 * this.get(3,2) + tmp_13 * this.get(1,2)) -\n                      (tmp_16 * this.get(3,2) + tmp_12 * this.get(1,2) + tmp_15 * this.get(2,2)));\n    var out_31 = d * ((tmp_20 * this.get(3,2) + tmp_12 * this.get(0,2) + tmp_19 * this.get(2,2)) -\n                      (tmp_18 * this.get(2,2) + tmp_21 * this.get(3,2) + tmp_13 * this.get(0,2)));\n    var out_32 = d * ((tmp_18 * this.get(1,2) + tmp_23 * this.get(3,2) + tmp_15 * this.get(0,2)) -\n                      (tmp_22 * this.get(3,2) + tmp_14 * this.get(0,2) + tmp_19 * this.get(1,2)));\n    var out_33 = d * ((tmp_22 * this.get(2,2) + tmp_16 * this.get(0,2) + tmp_21 * this.get(1,2)) -\n                      (tmp_20 * this.get(1,2) + tmp_23 * this.get(2,2) + tmp_17 * this.get(0,2)));\n\n    this.elements[0*4+0] = out_00;\n    this.elements[0*4+1] = out_01;\n    this.elements[0*4+2] = out_02;\n    this.elements[0*4+3] = out_03;\n    this.elements[1*4+0] = out_10;\n    this.elements[1*4+1] = out_11;\n    this.elements[1*4+2] = out_12;\n    this.elements[1*4+3] = out_13;\n    this.elements[2*4+0] = out_20;\n    this.elements[2*4+1] = out_21;\n    this.elements[2*4+2] = out_22;\n    this.elements[2*4+3] = out_23;\n    this.elements[3*4+0] = out_30;\n    this.elements[3*4+1] = out_31;\n    this.elements[3*4+2] = out_32;\n    this.elements[3*4+3] = out_33;\n    return this;\n  },\n\n  // Returns new matrix which is the inverse of this\n  inverse: function () {\n    var tmp = this.copy();\n    return tmp.invert();\n  },\n  \n  // In-place transpose\n  transpose: function () {\n    var tmp = this.elements[0*4+1];\n    this.elements[0*4+1] = this.elements[1*4+0];\n    this.elements[1*4+0] = tmp;\n\n    tmp = this.elements[0*4+2];\n    this.elements[0*4+2] = this.elements[2*4+0];\n    this.elements[2*4+0] = tmp;\n\n    tmp = this.elements[0*4+3];\n    this.elements[0*4+3] = this.elements[3*4+0];\n    this.elements[3*4+0] = tmp;\n\n    tmp = this.elements[1*4+2];\n    this.elements[1*4+2] = this.elements[2*4+1];\n    this.elements[2*4+1] = tmp;\n\n    tmp = this.elements[1*4+3];\n    this.elements[1*4+3] = this.elements[3*4+1];\n    this.elements[3*4+1] = tmp;\n\n    tmp = this.elements[2*4+3];\n    this.elements[2*4+3] = this.elements[3*4+2];\n    this.elements[3*4+2] = tmp;\n\n    return this;\n  },\n\n  loadIdentity: function () {\n    for (var i = 0; i < 16; i++)\n      this.elements[i] = 0;\n    this.elements[0*4+0] = 1.0;\n    this.elements[1*4+1] = 1.0;\n    this.elements[2*4+2] = 1.0;\n    this.elements[3*4+3] = 1.0;\n    return this;\n  }\n};\n"
  },
  {
    "path": "src/js/third_party/webgl_teapot/teapot-streams.js",
    "content": "/*\n * Copyright (c) 2009 The Chromium Authors. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *    * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *    * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *    * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\nvar teapotPositions = new Float32Array([ 17.83489990234375, 0, 30.573999404907227, 16.452699661254883, -7.000179767608643, 30.573999404907227, 16.223100662231445, -6.902520179748535, 31.51460075378418, 17.586000442504883, 0, 31.51460075378418, 16.48940086364746, -7.015810012817383, 31.828100204467773, 17.87470054626465, 0, 31.828100204467773, 17.031099319458008, -7.246280193328857, 31.51460075378418, 18.46190071105957, 0, 31.51460075378418, 17.62779998779297, -7.500199794769287, 30.573999404907227, 19.108800888061523, 0, 30.573999404907227, 12.662699699401855, -12.662699699401855, 30.573999404907227, 12.486100196838379, -12.486100196838379, 31.51460075378418, 12.690999984741211, -12.690999984741211, 31.828100204467773, 13.10789966583252, -13.10789966583252, 31.51460075378418, 13.56719970703125, -13.56719970703125, 30.573999404907227, 7.000179767608643, -16.452699661254883, 30.573999404907227, 6.902520179748535, -16.223100662231445, 31.51460075378418, 7.015810012817383, -16.48940086364746, 31.828100204467773, 7.246280193328857, -17.031099319458008, 31.51460075378418, 7.500199794769287, -17.62779998779297, 30.573999404907227, 0, -17.83489990234375, 30.573999404907227, 0, -17.586000442504883, 31.51460075378418, 0, -17.87470054626465, 31.828100204467773, 0, -18.46190071105957, 31.51460075378418, 0, -19.108800888061523, 30.573999404907227, 0, -17.83489990234375, 30.573999404907227, -7.483870029449463, -16.452699661254883, 30.573999404907227, -7.106579780578613, -16.223100662231445, 31.51460075378418, 0, -17.586000442504883, 31.51460075378418, -7.07627010345459, -16.48940086364746, 31.828100204467773, 0, -17.87470054626465, 31.828100204467773, -7.25383996963501, -17.031099319458008, 31.51460075378418, 0, -18.46190071105957, 31.51460075378418, -7.500199794769287, -17.62779998779297, 30.573999404907227, 0, -19.108800888061523, 30.573999404907227, -13.092700004577637, -12.662699699401855, 30.573999404907227, -12.667499542236328, -12.486100196838379, 31.51460075378418, -12.744799613952637, -12.690999984741211, 31.828100204467773, -13.11460018157959, -13.10789966583252, 31.51460075378418, -13.56719970703125, -13.56719970703125, 30.573999404907227, -16.61389923095703, -7.000179767608643, 30.573999404907227, -16.291099548339844, -6.902520179748535, 31.51460075378418, -16.50950050354004, -7.015810012817383, 31.828100204467773, -17.033599853515625, -7.246280193328857, 31.51460075378418, -17.62779998779297, -7.500199794769287, 30.573999404907227, -17.83489990234375, 0, 30.573999404907227, -17.586000442504883, 0, 31.51460075378418, -17.87470054626465, 0, 31.828100204467773, -18.46190071105957, 0, 31.51460075378418, -19.108800888061523, 0, 30.573999404907227, -17.83489990234375, 0, 30.573999404907227, -16.452699661254883, 7.000179767608643, 30.573999404907227, -16.223100662231445, 6.902520179748535, 31.51460075378418, -17.586000442504883, 0, 31.51460075378418, -16.48940086364746, 7.015810012817383, 31.828100204467773, -17.87470054626465, 0, 31.828100204467773, -17.031099319458008, 7.246280193328857, 31.51460075378418, -18.46190071105957, 0, 31.51460075378418, -17.62779998779297, 7.500199794769287, 30.573999404907227, -19.108800888061523, 0, 30.573999404907227, -12.662699699401855, 12.662699699401855, 30.573999404907227, -12.486100196838379, 12.486100196838379, 31.51460075378418, -12.690999984741211, 12.690999984741211, 31.828100204467773, -13.10789966583252, 13.10789966583252, 31.51460075378418, -13.56719970703125, 13.56719970703125, 30.573999404907227, -7.000179767608643, 16.452699661254883, 30.573999404907227, -6.902520179748535, 16.223100662231445, 31.51460075378418, -7.015810012817383, 16.48940086364746, 31.828100204467773, -7.246280193328857, 17.031099319458008, 31.51460075378418, -7.500199794769287, 17.62779998779297, 30.573999404907227, 0, 17.83489990234375, 30.573999404907227, 0, 17.586000442504883, 31.51460075378418, 0, 17.87470054626465, 31.828100204467773, 0, 18.46190071105957, 31.51460075378418, 0, 19.108800888061523, 30.573999404907227, 0, 17.83489990234375, 30.573999404907227, 7.000179767608643, 16.452699661254883, 30.573999404907227, 6.902520179748535, 16.223100662231445, 31.51460075378418, 0, 17.586000442504883, 31.51460075378418, 7.015810012817383, 16.48940086364746, 31.828100204467773, 0, 17.87470054626465, 31.828100204467773, 7.246280193328857, 17.031099319458008, 31.51460075378418, 0, 18.46190071105957, 31.51460075378418, 7.500199794769287, 17.62779998779297, 30.573999404907227, 0, 19.108800888061523, 30.573999404907227, 12.662699699401855, 12.662699699401855, 30.573999404907227, 12.486100196838379, 12.486100196838379, 31.51460075378418, 12.690999984741211, 12.690999984741211, 31.828100204467773, 13.10789966583252, 13.10789966583252, 31.51460075378418, 13.56719970703125, 13.56719970703125, 30.573999404907227, 16.452699661254883, 7.000179767608643, 30.573999404907227, 16.223100662231445, 6.902520179748535, 31.51460075378418, 16.48940086364746, 7.015810012817383, 31.828100204467773, 17.031099319458008, 7.246280193328857, 31.51460075378418, 17.62779998779297, 7.500199794769287, 30.573999404907227, 17.83489990234375, 0, 30.573999404907227, 17.586000442504883, 0, 31.51460075378418, 17.87470054626465, 0, 31.828100204467773, 18.46190071105957, 0, 31.51460075378418, 19.108800888061523, 0, 30.573999404907227, 19.108800888061523, 0, 30.573999404907227, 17.62779998779297, -7.500199794769287, 30.573999404907227, 19.785400390625, -8.418190002441406, 25.572900772094727, 21.447599411010742, 0, 25.572900772094727, 21.667600631713867, -9.218990325927734, 20.661399841308594, 23.487899780273438, 0, 20.661399841308594, 22.99880027770996, -9.785409927368164, 15.928999900817871, 24.930999755859375, 0, 15.928999900817871, 23.503799438476562, -10.000300407409668, 11.465299606323242, 25.4783992767334, 0, 11.465299606323242, 13.56719970703125, -13.56719970703125, 30.573999404907227, 15.227800369262695, -15.227800369262695, 25.572900772094727, 16.67639923095703, -16.67639923095703, 20.661399841308594, 17.701000213623047, -17.701000213623047, 15.928999900817871, 18.089599609375, -18.089599609375, 11.465299606323242, 7.500199794769287, -17.62779998779297, 30.573999404907227, 8.418190002441406, -19.785400390625, 25.572900772094727, 9.218990325927734, -21.667600631713867, 20.661399841308594, 9.785409927368164, -22.99880027770996, 15.928999900817871, 10.000300407409668, -23.503799438476562, 11.465299606323242, 0, -19.108800888061523, 30.573999404907227, 0, -21.447599411010742, 25.572900772094727, 0, -23.487899780273438, 20.661399841308594, 0, -24.930999755859375, 15.928999900817871, 0, -25.4783992767334, 11.465299606323242, 0, -19.108800888061523, 30.573999404907227, -7.500199794769287, -17.62779998779297, 30.573999404907227, -8.418190002441406, -19.785400390625, 25.572900772094727, 0, -21.447599411010742, 25.572900772094727, -9.218990325927734, -21.667600631713867, 20.661399841308594, 0, -23.487899780273438, 20.661399841308594, -9.785409927368164, -22.99880027770996, 15.928999900817871, 0, -24.930999755859375, 15.928999900817871, -10.000300407409668, -23.503799438476562, 11.465299606323242, 0, -25.4783992767334, 11.465299606323242, -13.56719970703125, -13.56719970703125, 30.573999404907227, -15.227800369262695, -15.227800369262695, 25.572900772094727, -16.67639923095703, -16.67639923095703, 20.661399841308594, -17.701000213623047, -17.701000213623047, 15.928999900817871, -18.089599609375, -18.089599609375, 11.465299606323242, -17.62779998779297, -7.500199794769287, 30.573999404907227, -19.785400390625, -8.418190002441406, 25.572900772094727, -21.667600631713867, -9.218990325927734, 20.661399841308594, -22.99880027770996, -9.785409927368164, 15.928999900817871, -23.503799438476562, -10.000300407409668, 11.465299606323242, -19.108800888061523, 0, 30.573999404907227, -21.447599411010742, 0, 25.572900772094727, -23.487899780273438, 0, 20.661399841308594, -24.930999755859375, 0, 15.928999900817871, -25.4783992767334, 0, 11.465299606323242, -19.108800888061523, 0, 30.573999404907227, -17.62779998779297, 7.500199794769287, 30.573999404907227, -19.785400390625, 8.418190002441406, 25.572900772094727, -21.447599411010742, 0, 25.572900772094727, -21.667600631713867, 9.218990325927734, 20.661399841308594, -23.487899780273438, 0, 20.661399841308594, -22.99880027770996, 9.785409927368164, 15.928999900817871, -24.930999755859375, 0, 15.928999900817871, -23.503799438476562, 10.000300407409668, 11.465299606323242, -25.4783992767334, 0, 11.465299606323242, -13.56719970703125, 13.56719970703125, 30.573999404907227, -15.227800369262695, 15.227800369262695, 25.572900772094727, -16.67639923095703, 16.67639923095703, 20.661399841308594, -17.701000213623047, 17.701000213623047, 15.928999900817871, -18.089599609375, 18.089599609375, 11.465299606323242, -7.500199794769287, 17.62779998779297, 30.573999404907227, -8.418190002441406, 19.785400390625, 25.572900772094727, -9.218990325927734, 21.667600631713867, 20.661399841308594, -9.785409927368164, 22.99880027770996, 15.928999900817871, -10.000300407409668, 23.503799438476562, 11.465299606323242, 0, 19.108800888061523, 30.573999404907227, 0, 21.447599411010742, 25.572900772094727, 0, 23.487899780273438, 20.661399841308594, 0, 24.930999755859375, 15.928999900817871, 0, 25.4783992767334, 11.465299606323242, 0, 19.108800888061523, 30.573999404907227, 7.500199794769287, 17.62779998779297, 30.573999404907227, 8.418190002441406, 19.785400390625, 25.572900772094727, 0, 21.447599411010742, 25.572900772094727, 9.218990325927734, 21.667600631713867, 20.661399841308594, 0, 23.487899780273438, 20.661399841308594, 9.785409927368164, 22.99880027770996, 15.928999900817871, 0, 24.930999755859375, 15.928999900817871, 10.000300407409668, 23.503799438476562, 11.465299606323242, 0, 25.4783992767334, 11.465299606323242, 13.56719970703125, 13.56719970703125, 30.573999404907227, 15.227800369262695, 15.227800369262695, 25.572900772094727, 16.67639923095703, 16.67639923095703, 20.661399841308594, 17.701000213623047, 17.701000213623047, 15.928999900817871, 18.089599609375, 18.089599609375, 11.465299606323242, 17.62779998779297, 7.500199794769287, 30.573999404907227, 19.785400390625, 8.418190002441406, 25.572900772094727, 21.667600631713867, 9.218990325927734, 20.661399841308594, 22.99880027770996, 9.785409927368164, 15.928999900817871, 23.503799438476562, 10.000300407409668, 11.465299606323242, 19.108800888061523, 0, 30.573999404907227, 21.447599411010742, 0, 25.572900772094727, 23.487899780273438, 0, 20.661399841308594, 24.930999755859375, 0, 15.928999900817871, 25.4783992767334, 0, 11.465299606323242, 25.4783992767334, 0, 11.465299606323242, 23.503799438476562, -10.000300407409668, 11.465299606323242, 22.5856990814209, -9.609620094299316, 7.688300132751465, 24.48310089111328, 0, 7.688300132751465, 20.565799713134766, -8.750229835510254, 4.89661979675293, 22.29360008239746, 0, 4.89661979675293, 18.54599952697754, -7.890830039978027, 3.0006699562072754, 20.104000091552734, 0, 3.0006699562072754, 17.62779998779297, -7.500199794769287, 1.9108799695968628, 19.108800888061523, 0, 1.9108799695968628, 18.089599609375, -18.089599609375, 11.465299606323242, 17.382999420166016, -17.382999420166016, 7.688300132751465, 15.828399658203125, -15.828399658203125, 4.89661979675293, 14.273900032043457, -14.273900032043457, 3.0006699562072754, 13.56719970703125, -13.56719970703125, 1.9108799695968628, 10.000300407409668, -23.503799438476562, 11.465299606323242, 9.609620094299316, -22.5856990814209, 7.688300132751465, 8.750229835510254, -20.565799713134766, 4.89661979675293, 7.890830039978027, -18.54599952697754, 3.0006699562072754, 7.500199794769287, -17.62779998779297, 1.9108799695968628, 0, -25.4783992767334, 11.465299606323242, 0, -24.48310089111328, 7.688300132751465, 0, -22.29360008239746, 4.89661979675293, 0, -20.104000091552734, 3.0006699562072754, 0, -19.108800888061523, 1.9108799695968628, 0, -25.4783992767334, 11.465299606323242, -10.000300407409668, -23.503799438476562, 11.465299606323242, -9.609620094299316, -22.5856990814209, 7.688300132751465, 0, -24.48310089111328, 7.688300132751465, -8.750229835510254, -20.565799713134766, 4.89661979675293, 0, -22.29360008239746, 4.89661979675293, -7.890830039978027, -18.54599952697754, 3.0006699562072754, 0, -20.104000091552734, 3.0006699562072754, -7.500199794769287, -17.62779998779297, 1.9108799695968628, 0, -19.108800888061523, 1.9108799695968628, -18.089599609375, -18.089599609375, 11.465299606323242, -17.382999420166016, -17.382999420166016, 7.688300132751465, -15.828399658203125, -15.828399658203125, 4.89661979675293, -14.273900032043457, -14.273900032043457, 3.0006699562072754, -13.56719970703125, -13.56719970703125, 1.9108799695968628, -23.503799438476562, -10.000300407409668, 11.465299606323242, -22.5856990814209, -9.609620094299316, 7.688300132751465, -20.565799713134766, -8.750229835510254, 4.89661979675293, -18.54599952697754, -7.890830039978027, 3.0006699562072754, -17.62779998779297, -7.500199794769287, 1.9108799695968628, -25.4783992767334, 0, 11.465299606323242, -24.48310089111328, 0, 7.688300132751465, -22.29360008239746, 0, 4.89661979675293, -20.104000091552734, 0, 3.0006699562072754, -19.108800888061523, 0, 1.9108799695968628, -25.4783992767334, 0, 11.465299606323242, -23.503799438476562, 10.000300407409668, 11.465299606323242, -22.5856990814209, 9.609620094299316, 7.688300132751465, -24.48310089111328, 0, 7.688300132751465, -20.565799713134766, 8.750229835510254, 4.89661979675293, -22.29360008239746, 0, 4.89661979675293, -18.54599952697754, 7.890830039978027, 3.0006699562072754, -20.104000091552734, 0, 3.0006699562072754, -17.62779998779297, 7.500199794769287, 1.9108799695968628, -19.108800888061523, 0, 1.9108799695968628, -18.089599609375, 18.089599609375, 11.465299606323242, -17.382999420166016, 17.382999420166016, 7.688300132751465, -15.828399658203125, 15.828399658203125, 4.89661979675293, -14.273900032043457, 14.273900032043457, 3.0006699562072754, -13.56719970703125, 13.56719970703125, 1.9108799695968628, -10.000300407409668, 23.503799438476562, 11.465299606323242, -9.609620094299316, 22.5856990814209, 7.688300132751465, -8.750229835510254, 20.565799713134766, 4.89661979675293, -7.890830039978027, 18.54599952697754, 3.0006699562072754, -7.500199794769287, 17.62779998779297, 1.9108799695968628, 0, 25.4783992767334, 11.465299606323242, 0, 24.48310089111328, 7.688300132751465, 0, 22.29360008239746, 4.89661979675293, 0, 20.104000091552734, 3.0006699562072754, 0, 19.108800888061523, 1.9108799695968628, 0, 25.4783992767334, 11.465299606323242, 10.000300407409668, 23.503799438476562, 11.465299606323242, 9.609620094299316, 22.5856990814209, 7.688300132751465, 0, 24.48310089111328, 7.688300132751465, 8.750229835510254, 20.565799713134766, 4.89661979675293, 0, 22.29360008239746, 4.89661979675293, 7.890830039978027, 18.54599952697754, 3.0006699562072754, 0, 20.104000091552734, 3.0006699562072754, 7.500199794769287, 17.62779998779297, 1.9108799695968628, 0, 19.108800888061523, 1.9108799695968628, 18.089599609375, 18.089599609375, 11.465299606323242, 17.382999420166016, 17.382999420166016, 7.688300132751465, 15.828399658203125, 15.828399658203125, 4.89661979675293, 14.273900032043457, 14.273900032043457, 3.0006699562072754, 13.56719970703125, 13.56719970703125, 1.9108799695968628, 23.503799438476562, 10.000300407409668, 11.465299606323242, 22.5856990814209, 9.609620094299316, 7.688300132751465, 20.565799713134766, 8.750229835510254, 4.89661979675293, 18.54599952697754, 7.890830039978027, 3.0006699562072754, 17.62779998779297, 7.500199794769287, 1.9108799695968628, 25.4783992767334, 0, 11.465299606323242, 24.48310089111328, 0, 7.688300132751465, 22.29360008239746, 0, 4.89661979675293, 20.104000091552734, 0, 3.0006699562072754, 19.108800888061523, 0, 1.9108799695968628, 19.108800888061523, 0, 1.9108799695968628, 17.62779998779297, -7.500199794769287, 1.9108799695968628, 17.228500366210938, -7.330269813537598, 1.2092299461364746, 18.675800323486328, 0, 1.2092299461364746, 15.093799591064453, -6.422039985656738, 0.5971490144729614, 16.361900329589844, 0, 0.5971490144729614, 9.819259643554688, -4.177840232849121, 0.16421599686145782, 10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, 0, 0, 0, 13.56719970703125, -13.56719970703125, 1.9108799695968628, 13.25979995727539, -13.25979995727539, 1.2092299461364746, 11.616900444030762, -11.616900444030762, 0.5971490144729614, 7.557370185852051, -7.557370185852051, 0.16421599686145782, 0, 0, 0, 7.500199794769287, -17.62779998779297, 1.9108799695968628, 7.330269813537598, -17.228500366210938, 1.2092299461364746, 6.422039985656738, -15.093799591064453, 0.5971490144729614, 4.177840232849121, -9.819259643554688, 0.16421599686145782, 0, 0, 0, 0, -19.108800888061523, 1.9108799695968628, 0, -18.675800323486328, 1.2092299461364746, 0, -16.361900329589844, 0.5971490144729614, 0, -10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, -19.108800888061523, 1.9108799695968628, -7.500199794769287, -17.62779998779297, 1.9108799695968628, -7.330269813537598, -17.228500366210938, 1.2092299461364746, 0, -18.675800323486328, 1.2092299461364746, -6.422039985656738, -15.093799591064453, 0.5971490144729614, 0, -16.361900329589844, 0.5971490144729614, -4.177840232849121, -9.819259643554688, 0.16421599686145782, 0, -10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, 0, 0, -13.56719970703125, -13.56719970703125, 1.9108799695968628, -13.25979995727539, -13.25979995727539, 1.2092299461364746, -11.616900444030762, -11.616900444030762, 0.5971490144729614, -7.557370185852051, -7.557370185852051, 0.16421599686145782, 0, 0, 0, -17.62779998779297, -7.500199794769287, 1.9108799695968628, -17.228500366210938, -7.330269813537598, 1.2092299461364746, -15.093799591064453, -6.422039985656738, 0.5971490144729614, -9.819259643554688, -4.177840232849121, 0.16421599686145782, 0, 0, 0, -19.108800888061523, 0, 1.9108799695968628, -18.675800323486328, 0, 1.2092299461364746, -16.361900329589844, 0, 0.5971490144729614, -10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, -19.108800888061523, 0, 1.9108799695968628, -17.62779998779297, 7.500199794769287, 1.9108799695968628, -17.228500366210938, 7.330269813537598, 1.2092299461364746, -18.675800323486328, 0, 1.2092299461364746, -15.093799591064453, 6.422039985656738, 0.5971490144729614, -16.361900329589844, 0, 0.5971490144729614, -9.819259643554688, 4.177840232849121, 0.16421599686145782, -10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, 0, 0, 0, -13.56719970703125, 13.56719970703125, 1.9108799695968628, -13.25979995727539, 13.25979995727539, 1.2092299461364746, -11.616900444030762, 11.616900444030762, 0.5971490144729614, -7.557370185852051, 7.557370185852051, 0.16421599686145782, 0, 0, 0, -7.500199794769287, 17.62779998779297, 1.9108799695968628, -7.330269813537598, 17.228500366210938, 1.2092299461364746, -6.422039985656738, 15.093799591064453, 0.5971490144729614, -4.177840232849121, 9.819259643554688, 0.16421599686145782, 0, 0, 0, 0, 19.108800888061523, 1.9108799695968628, 0, 18.675800323486328, 1.2092299461364746, 0, 16.361900329589844, 0.5971490144729614, 0, 10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, 19.108800888061523, 1.9108799695968628, 7.500199794769287, 17.62779998779297, 1.9108799695968628, 7.330269813537598, 17.228500366210938, 1.2092299461364746, 0, 18.675800323486328, 1.2092299461364746, 6.422039985656738, 15.093799591064453, 0.5971490144729614, 0, 16.361900329589844, 0.5971490144729614, 4.177840232849121, 9.819259643554688, 0.16421599686145782, 0, 10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, 0, 0, 13.56719970703125, 13.56719970703125, 1.9108799695968628, 13.25979995727539, 13.25979995727539, 1.2092299461364746, 11.616900444030762, 11.616900444030762, 0.5971490144729614, 7.557370185852051, 7.557370185852051, 0.16421599686145782, 0, 0, 0, 17.62779998779297, 7.500199794769287, 1.9108799695968628, 17.228500366210938, 7.330269813537598, 1.2092299461364746, 15.093799591064453, 6.422039985656738, 0.5971490144729614, 9.819259643554688, 4.177840232849121, 0.16421599686145782, 0, 0, 0, 19.108800888061523, 0, 1.9108799695968628, 18.675800323486328, 0, 1.2092299461364746, 16.361900329589844, 0, 0.5971490144729614, 10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, -20.382699966430664, 0, 25.796899795532227, -20.1835994720459, -2.149739980697632, 26.244699478149414, -26.511600494384766, -2.149739980697632, 26.192899703979492, -26.334299087524414, 0, 25.752099990844727, -31.156299591064453, -2.149739980697632, 25.830400466918945, -30.733299255371094, 0, 25.438600540161133, -34.016998291015625, -2.149739980697632, 24.846500396728516, -33.46030044555664, 0, 24.587600708007812, -34.99290084838867, -2.149739980697632, 22.930500030517578, -34.39580154418945, 0, 22.930500030517578, -19.74570083618164, -2.8663198947906494, 27.229999542236328, -26.901599884033203, -2.8663198947906494, 27.162799835205078, -32.08679962158203, -2.8663198947906494, 26.69260025024414, -35.241798400878906, -2.8663198947906494, 25.416200637817383, -36.30670166015625, -2.8663198947906494, 22.930500030517578, -19.30780029296875, -2.149739980697632, 28.215299606323242, -27.29159927368164, -2.149739980697632, 28.132699966430664, -33.017398834228516, -2.149739980697632, 27.55470085144043, -36.46649932861328, -2.149739980697632, 25.98579978942871, -37.620399475097656, -2.149739980697632, 22.930500030517578, -19.108800888061523, 0, 28.66320037841797, -27.468900680541992, 0, 28.57360076904297, -33.440399169921875, 0, 27.94659996032715, -37.02330017089844, 0, 26.244699478149414, -38.21760177612305, 0, 22.930500030517578, -19.108800888061523, 0, 28.66320037841797, -19.30780029296875, 2.149739980697632, 28.215299606323242, -27.29159927368164, 2.149739980697632, 28.132699966430664, -27.468900680541992, 0, 28.57360076904297, -33.017398834228516, 2.149739980697632, 27.55470085144043, -33.440399169921875, 0, 27.94659996032715, -36.46649932861328, 2.149739980697632, 25.98579978942871, -37.02330017089844, 0, 26.244699478149414, -37.620399475097656, 2.149739980697632, 22.930500030517578, -38.21760177612305, 0, 22.930500030517578, -19.74570083618164, 2.8663198947906494, 27.229999542236328, -26.901599884033203, 2.8663198947906494, 27.162799835205078, -32.08679962158203, 2.8663198947906494, 26.69260025024414, -35.241798400878906, 2.8663198947906494, 25.416200637817383, -36.30670166015625, 2.8663198947906494, 22.930500030517578, -20.1835994720459, 2.149739980697632, 26.244699478149414, -26.511600494384766, 2.149739980697632, 26.192899703979492, -31.156299591064453, 2.149739980697632, 25.830400466918945, -34.016998291015625, 2.149739980697632, 24.846500396728516, -34.99290084838867, 2.149739980697632, 22.930500030517578, -20.382699966430664, 0, 25.796899795532227, -26.334299087524414, 0, 25.752099990844727, -30.733299255371094, 0, 25.438600540161133, -33.46030044555664, 0, 24.587600708007812, -34.39580154418945, 0, 22.930500030517578, -34.39580154418945, 0, 22.930500030517578, -34.99290084838867, -2.149739980697632, 22.930500030517578, -34.44089889526367, -2.149739980697632, 20.082199096679688, -33.89820098876953, 0, 20.33289909362793, -32.711299896240234, -2.149739980697632, 16.81529998779297, -32.32569885253906, 0, 17.197900772094727, -29.69420051574707, -2.149739980697632, 13.590499877929688, -29.558900833129883, 0, 14.062899589538574, -25.279300689697266, -2.149739980697632, 10.8681001663208, -25.4783992767334, 0, 11.465299606323242, -36.30670166015625, -2.8663198947906494, 22.930500030517578, -35.6348991394043, -2.8663198947906494, 19.530500411987305, -33.55979919433594, -2.8663198947906494, 15.973699569702148, -29.99180030822754, -2.8663198947906494, 12.551300048828125, -24.841400146484375, -2.8663198947906494, 9.554389953613281, -37.620399475097656, -2.149739980697632, 22.930500030517578, -36.82889938354492, -2.149739980697632, 18.97879981994629, -34.408199310302734, -2.149739980697632, 15.132100105285645, -30.289499282836914, -2.149739980697632, 11.512200355529785, -24.403499603271484, -2.149739980697632, 8.240659713745117, -38.21760177612305, 0, 22.930500030517578, -37.37160110473633, 0, 18.728099822998047, -34.79389953613281, 0, 14.749600410461426, -30.424800872802734, 0, 11.039799690246582, -24.204500198364258, 0, 7.643509864807129, -38.21760177612305, 0, 22.930500030517578, -37.620399475097656, 2.149739980697632, 22.930500030517578, -36.82889938354492, 2.149739980697632, 18.97879981994629, -37.37160110473633, 0, 18.728099822998047, -34.408199310302734, 2.149739980697632, 15.132100105285645, -34.79389953613281, 0, 14.749600410461426, -30.289499282836914, 2.149739980697632, 11.512200355529785, -30.424800872802734, 0, 11.039799690246582, -24.403499603271484, 2.149739980697632, 8.240659713745117, -24.204500198364258, 0, 7.643509864807129, -36.30670166015625, 2.8663198947906494, 22.930500030517578, -35.6348991394043, 2.8663198947906494, 19.530500411987305, -33.55979919433594, 2.8663198947906494, 15.973699569702148, -29.99180030822754, 2.8663198947906494, 12.551300048828125, -24.841400146484375, 2.8663198947906494, 9.554389953613281, -34.99290084838867, 2.149739980697632, 22.930500030517578, -34.44089889526367, 2.149739980697632, 20.082199096679688, -32.711299896240234, 2.149739980697632, 16.81529998779297, -29.69420051574707, 2.149739980697632, 13.590499877929688, -25.279300689697266, 2.149739980697632, 10.8681001663208, -34.39580154418945, 0, 22.930500030517578, -33.89820098876953, 0, 20.33289909362793, -32.32569885253906, 0, 17.197900772094727, -29.558900833129883, 0, 14.062899589538574, -25.4783992767334, 0, 11.465299606323242, 21.656600952148438, 0, 18.15329933166504, 21.656600952148438, -4.729420185089111, 16.511199951171875, 28.233999252319336, -4.270359992980957, 18.339000701904297, 27.76740074157715, 0, 19.55660057067871, 31.011899948120117, -3.2604401111602783, 22.221399307250977, 30.4148006439209, 0, 22.930500030517578, 32.59560012817383, -2.2505099773406982, 26.764400482177734, 31.867900848388672, 0, 27.020999908447266, 35.5900993347168, -1.791450023651123, 30.573999404907227, 34.39580154418945, 0, 30.573999404907227, 21.656600952148438, -6.3059000968933105, 12.89840030670166, 29.260299682617188, -5.693819999694824, 15.660200119018555, 32.32569885253906, -4.347249984741211, 20.661399841308594, 34.19670104980469, -3.0006699562072754, 26.199899673461914, 38.21760177612305, -2.3886001110076904, 30.573999404907227, 21.656600952148438, -4.729420185089111, 9.285670280456543, 30.286699295043945, -4.270359992980957, 12.981499671936035, 33.639400482177734, -3.2604401111602783, 19.101299285888672, 35.79790115356445, -2.2505099773406982, 25.635400772094727, 40.845001220703125, -1.791450023651123, 30.573999404907227, 21.656600952148438, 0, 7.643509864807129, 30.75320053100586, 0, 11.763799667358398, 34.23659896850586, 0, 18.392200469970703, 36.52560043334961, 0, 25.378799438476562, 42.03929901123047, 0, 30.573999404907227, 21.656600952148438, 0, 7.643509864807129, 21.656600952148438, 4.729420185089111, 9.285670280456543, 30.286699295043945, 4.270359992980957, 12.981499671936035, 30.75320053100586, 0, 11.763799667358398, 33.639400482177734, 3.2604401111602783, 19.101299285888672, 34.23659896850586, 0, 18.392200469970703, 35.79790115356445, 2.2505099773406982, 25.635400772094727, 36.52560043334961, 0, 25.378799438476562, 40.845001220703125, 1.791450023651123, 30.573999404907227, 42.03929901123047, 0, 30.573999404907227, 21.656600952148438, 6.3059000968933105, 12.89840030670166, 29.260299682617188, 5.693819999694824, 15.660200119018555, 32.32569885253906, 4.347249984741211, 20.661399841308594, 34.19670104980469, 3.0006699562072754, 26.199899673461914, 38.21760177612305, 2.3886001110076904, 30.573999404907227, 21.656600952148438, 4.729420185089111, 16.511199951171875, 28.233999252319336, 4.270359992980957, 18.339000701904297, 31.011899948120117, 3.2604401111602783, 22.221399307250977, 32.59560012817383, 2.2505099773406982, 26.764400482177734, 35.5900993347168, 1.791450023651123, 30.573999404907227, 21.656600952148438, 0, 18.15329933166504, 27.76740074157715, 0, 19.55660057067871, 30.4148006439209, 0, 22.930500030517578, 31.867900848388672, 0, 27.020999908447266, 34.39580154418945, 0, 30.573999404907227, 34.39580154418945, 0, 30.573999404907227, 35.5900993347168, -1.791450023651123, 30.573999404907227, 36.59049987792969, -1.679479956626892, 31.137699127197266, 35.3114013671875, 0, 31.111499786376953, 37.18870162963867, -1.4331599473953247, 31.332599639892578, 35.98820114135742, 0, 31.290599822998047, 37.206600189208984, -1.1868300437927246, 31.1481990814209, 36.187198638916016, 0, 31.111499786376953, 36.46590042114258, -1.074869990348816, 30.573999404907227, 35.669700622558594, 0, 30.573999404907227, 38.21760177612305, -2.3886001110076904, 30.573999404907227, 39.40439987182617, -2.2393100261688232, 31.195499420166016, 39.829898834228516, -1.9108799695968628, 31.424999237060547, 39.44919967651367, -1.582450032234192, 31.229000091552734, 38.21760177612305, -1.4331599473953247, 30.573999404907227, 40.845001220703125, -1.791450023651123, 30.573999404907227, 42.218299865722656, -1.679479956626892, 31.25320053100586, 42.47100067138672, -1.4331599473953247, 31.51740074157715, 41.69169998168945, -1.1868300437927246, 31.309900283813477, 39.969200134277344, -1.074869990348816, 30.573999404907227, 42.03929901123047, 0, 30.573999404907227, 43.49729919433594, 0, 31.279399871826172, 43.67150115966797, 0, 31.55929946899414, 42.71110153198242, 0, 31.346599578857422, 40.76539993286133, 0, 30.573999404907227, 42.03929901123047, 0, 30.573999404907227, 40.845001220703125, 1.791450023651123, 30.573999404907227, 42.218299865722656, 1.679479956626892, 31.25320053100586, 43.49729919433594, 0, 31.279399871826172, 42.47100067138672, 1.4331599473953247, 31.51740074157715, 43.67150115966797, 0, 31.55929946899414, 41.69169998168945, 1.1868300437927246, 31.309900283813477, 42.71110153198242, 0, 31.346599578857422, 39.969200134277344, 1.074869990348816, 30.573999404907227, 40.76539993286133, 0, 30.573999404907227, 38.21760177612305, 2.3886001110076904, 30.573999404907227, 39.40439987182617, 2.2393100261688232, 31.195499420166016, 39.829898834228516, 1.9108799695968628, 31.424999237060547, 39.44919967651367, 1.582450032234192, 31.229000091552734, 38.21760177612305, 1.4331599473953247, 30.573999404907227, 35.5900993347168, 1.791450023651123, 30.573999404907227, 36.59049987792969, 1.679479956626892, 31.137699127197266, 37.18870162963867, 1.4331599473953247, 31.332599639892578, 37.206600189208984, 1.1868300437927246, 31.1481990814209, 36.46590042114258, 1.074869990348816, 30.573999404907227, 34.39580154418945, 0, 30.573999404907227, 35.3114013671875, 0, 31.111499786376953, 35.98820114135742, 0, 31.290599822998047, 36.187198638916016, 0, 31.111499786376953, 35.669700622558594, 0, 30.573999404907227, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, 4.004499912261963, -1.7077000141143799, 39.501399993896484, 4.339280128479004, 0, 39.501399993896484, 3.8207099437713623, -1.6290700435638428, 37.97869873046875, 4.140230178833008, 0, 37.97869873046875, 2.314160108566284, -0.985912024974823, 36.09769821166992, 2.5080299377441406, 0, 36.09769821166992, 2.3503799438476562, -1.0000300407409668, 34.39580154418945, 2.547840118408203, 0, 34.39580154418945, 0, 0, 40.12839889526367, 3.0849199295043945, -3.0849199295043945, 39.501399993896484, 2.943150043487549, -2.943150043487549, 37.97869873046875, 1.782039999961853, -1.782039999961853, 36.09769821166992, 1.8089599609375, -1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, 1.7077000141143799, -4.004499912261963, 39.501399993896484, 1.6290700435638428, -3.8207099437713623, 37.97869873046875, 0.985912024974823, -2.314160108566284, 36.09769821166992, 1.0000300407409668, -2.3503799438476562, 34.39580154418945, 0, 0, 40.12839889526367, 0, -4.339280128479004, 39.501399993896484, 0, -4.140230178833008, 37.97869873046875, 0, -2.5080299377441406, 36.09769821166992, 0, -2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, -1.7077000141143799, -4.004499912261963, 39.501399993896484, 0, -4.339280128479004, 39.501399993896484, -1.6290700435638428, -3.8207099437713623, 37.97869873046875, 0, -4.140230178833008, 37.97869873046875, -0.985912024974823, -2.314160108566284, 36.09769821166992, 0, -2.5080299377441406, 36.09769821166992, -1.0000300407409668, -2.3503799438476562, 34.39580154418945, 0, -2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, -3.0849199295043945, -3.0849199295043945, 39.501399993896484, -2.943150043487549, -2.943150043487549, 37.97869873046875, -1.782039999961853, -1.782039999961853, 36.09769821166992, -1.8089599609375, -1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, -4.004499912261963, -1.7077000141143799, 39.501399993896484, -3.8207099437713623, -1.6290700435638428, 37.97869873046875, -2.314160108566284, -0.985912024974823, 36.09769821166992, -2.3503799438476562, -1.0000300407409668, 34.39580154418945, 0, 0, 40.12839889526367, -4.339280128479004, 0, 39.501399993896484, -4.140230178833008, 0, 37.97869873046875, -2.5080299377441406, 0, 36.09769821166992, -2.547840118408203, 0, 34.39580154418945, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, -4.004499912261963, 1.7077000141143799, 39.501399993896484, -4.339280128479004, 0, 39.501399993896484, -3.8207099437713623, 1.6290700435638428, 37.97869873046875, -4.140230178833008, 0, 37.97869873046875, -2.314160108566284, 0.985912024974823, 36.09769821166992, -2.5080299377441406, 0, 36.09769821166992, -2.3503799438476562, 1.0000300407409668, 34.39580154418945, -2.547840118408203, 0, 34.39580154418945, 0, 0, 40.12839889526367, -3.0849199295043945, 3.0849199295043945, 39.501399993896484, -2.943150043487549, 2.943150043487549, 37.97869873046875, -1.782039999961853, 1.782039999961853, 36.09769821166992, -1.8089599609375, 1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, -1.7077000141143799, 4.004499912261963, 39.501399993896484, -1.6290700435638428, 3.8207099437713623, 37.97869873046875, -0.985912024974823, 2.314160108566284, 36.09769821166992, -1.0000300407409668, 2.3503799438476562, 34.39580154418945, 0, 0, 40.12839889526367, 0, 4.339280128479004, 39.501399993896484, 0, 4.140230178833008, 37.97869873046875, 0, 2.5080299377441406, 36.09769821166992, 0, 2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, 1.7077000141143799, 4.004499912261963, 39.501399993896484, 0, 4.339280128479004, 39.501399993896484, 1.6290700435638428, 3.8207099437713623, 37.97869873046875, 0, 4.140230178833008, 37.97869873046875, 0.985912024974823, 2.314160108566284, 36.09769821166992, 0, 2.5080299377441406, 36.09769821166992, 1.0000300407409668, 2.3503799438476562, 34.39580154418945, 0, 2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, 3.0849199295043945, 3.0849199295043945, 39.501399993896484, 2.943150043487549, 2.943150043487549, 37.97869873046875, 1.782039999961853, 1.782039999961853, 36.09769821166992, 1.8089599609375, 1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, 4.004499912261963, 1.7077000141143799, 39.501399993896484, 3.8207099437713623, 1.6290700435638428, 37.97869873046875, 2.314160108566284, 0.985912024974823, 36.09769821166992, 2.3503799438476562, 1.0000300407409668, 34.39580154418945, 0, 0, 40.12839889526367, 4.339280128479004, 0, 39.501399993896484, 4.140230178833008, 0, 37.97869873046875, 2.5080299377441406, 0, 36.09769821166992, 2.547840118408203, 0, 34.39580154418945, 2.547840118408203, 0, 34.39580154418945, 2.3503799438476562, -1.0000300407409668, 34.39580154418945, 5.361800193786621, -2.2813100814819336, 33.261199951171875, 5.812250137329102, 0, 33.261199951171875, 9.695320129394531, -4.125110149383545, 32.484901428222656, 10.50979995727539, 0, 32.484901428222656, 13.58810043334961, -5.781400203704834, 31.708599090576172, 14.729700088500977, 0, 31.708599090576172, 15.27750015258789, -6.5001702308654785, 30.573999404907227, 16.56089973449707, 0, 30.573999404907227, 1.8089599609375, -1.8089599609375, 34.39580154418945, 4.126699924468994, -4.126699924468994, 33.261199951171875, 7.461979866027832, -7.461979866027832, 32.484901428222656, 10.458100318908691, -10.458100318908691, 31.708599090576172, 11.758299827575684, -11.758299827575684, 30.573999404907227, 1.0000300407409668, -2.3503799438476562, 34.39580154418945, 2.2813100814819336, -5.361800193786621, 33.261199951171875, 4.125110149383545, -9.695320129394531, 32.484901428222656, 5.781400203704834, -13.58810043334961, 31.708599090576172, 6.5001702308654785, -15.27750015258789, 30.573999404907227, 0, -2.547840118408203, 34.39580154418945, 0, -5.812250137329102, 33.261199951171875, 0, -10.50979995727539, 32.484901428222656, 0, -14.729700088500977, 31.708599090576172, 0, -16.56089973449707, 30.573999404907227, 0, -2.547840118408203, 34.39580154418945, -1.0000300407409668, -2.3503799438476562, 34.39580154418945, -2.2813100814819336, -5.361800193786621, 33.261199951171875, 0, -5.812250137329102, 33.261199951171875, -4.125110149383545, -9.695320129394531, 32.484901428222656, 0, -10.50979995727539, 32.484901428222656, -5.781400203704834, -13.58810043334961, 31.708599090576172, 0, -14.729700088500977, 31.708599090576172, -6.5001702308654785, -15.27750015258789, 30.573999404907227, 0, -16.56089973449707, 30.573999404907227, -1.8089599609375, -1.8089599609375, 34.39580154418945, -4.126699924468994, -4.126699924468994, 33.261199951171875, -7.461979866027832, -7.461979866027832, 32.484901428222656, -10.458100318908691, -10.458100318908691, 31.708599090576172, -11.758299827575684, -11.758299827575684, 30.573999404907227, -2.3503799438476562, -1.0000300407409668, 34.39580154418945, -5.361800193786621, -2.2813100814819336, 33.261199951171875, -9.695320129394531, -4.125110149383545, 32.484901428222656, -13.58810043334961, -5.781400203704834, 31.708599090576172, -15.27750015258789, -6.5001702308654785, 30.573999404907227, -2.547840118408203, 0, 34.39580154418945, -5.812250137329102, 0, 33.261199951171875, -10.50979995727539, 0, 32.484901428222656, -14.729700088500977, 0, 31.708599090576172, -16.56089973449707, 0, 30.573999404907227, -2.547840118408203, 0, 34.39580154418945, -2.3503799438476562, 1.0000300407409668, 34.39580154418945, -5.361800193786621, 2.2813100814819336, 33.261199951171875, -5.812250137329102, 0, 33.261199951171875, -9.695320129394531, 4.125110149383545, 32.484901428222656, -10.50979995727539, 0, 32.484901428222656, -13.58810043334961, 5.781400203704834, 31.708599090576172, -14.729700088500977, 0, 31.708599090576172, -15.27750015258789, 6.5001702308654785, 30.573999404907227, -16.56089973449707, 0, 30.573999404907227, -1.8089599609375, 1.8089599609375, 34.39580154418945, -4.126699924468994, 4.126699924468994, 33.261199951171875, -7.461979866027832, 7.461979866027832, 32.484901428222656, -10.458100318908691, 10.458100318908691, 31.708599090576172, -11.758299827575684, 11.758299827575684, 30.573999404907227, -1.0000300407409668, 2.3503799438476562, 34.39580154418945, -2.2813100814819336, 5.361800193786621, 33.261199951171875, -4.125110149383545, 9.695320129394531, 32.484901428222656, -5.781400203704834, 13.58810043334961, 31.708599090576172, -6.5001702308654785, 15.27750015258789, 30.573999404907227, 0, 2.547840118408203, 34.39580154418945, 0, 5.812250137329102, 33.261199951171875, 0, 10.50979995727539, 32.484901428222656, 0, 14.729700088500977, 31.708599090576172, 0, 16.56089973449707, 30.573999404907227, 0, 2.547840118408203, 34.39580154418945, 1.0000300407409668, 2.3503799438476562, 34.39580154418945, 2.2813100814819336, 5.361800193786621, 33.261199951171875, 0, 5.812250137329102, 33.261199951171875, 4.125110149383545, 9.695320129394531, 32.484901428222656, 0, 10.50979995727539, 32.484901428222656, 5.781400203704834, 13.58810043334961, 31.708599090576172, 0, 14.729700088500977, 31.708599090576172, 6.5001702308654785, 15.27750015258789, 30.573999404907227, 0, 16.56089973449707, 30.573999404907227, 1.8089599609375, 1.8089599609375, 34.39580154418945, 4.126699924468994, 4.126699924468994, 33.261199951171875, 7.461979866027832, 7.461979866027832, 32.484901428222656, 10.458100318908691, 10.458100318908691, 31.708599090576172, 11.758299827575684, 11.758299827575684, 30.573999404907227, 2.3503799438476562, 1.0000300407409668, 34.39580154418945, 5.361800193786621, 2.2813100814819336, 33.261199951171875, 9.695320129394531, 4.125110149383545, 32.484901428222656, 13.58810043334961, 5.781400203704834, 31.708599090576172, 15.27750015258789, 6.5001702308654785, 30.573999404907227, 2.547840118408203, 0, 34.39580154418945, 5.812250137329102, 0, 33.261199951171875, 10.50979995727539, 0, 32.484901428222656, 14.729700088500977, 0, 31.708599090576172, 16.56089973449707, 0, 30.573999404907227 ]);\nvar teapotNormals = new Float32Array([ -0.9667419791221619, 0, -0.25575199723243713, -0.8930140137672424, 0.3698819875717163, -0.2563450038433075, -0.8934370279312134, 0.36910200119018555, 0.2559970021247864, -0.9668239951133728, 0, 0.2554430067539215, -0.0838799998164177, 0.03550700098276138, 0.9958429932594299, -0.09205400198698044, 0, 0.9957540035247803, 0.629721999168396, -0.2604379951953888, 0.7318620085716248, 0.6820489764213562, 0, 0.7313070297241211, 0.803725004196167, -0.3325839936733246, 0.4933690130710602, 0.8703010082244873, 0, 0.4925200045108795, -0.6834070086479187, 0.6834070086479187, -0.2567310035228729, -0.6835309863090515, 0.6835309863090515, 0.25606799125671387, -0.06492599844932556, 0.06492500007152557, 0.9957759976387024, 0.48139700293540955, -0.48139700293540955, 0.7324709892272949, 0.6148040294647217, -0.6148040294647217, 0.4939970076084137, -0.3698819875717163, 0.8930140137672424, -0.2563450038433075, -0.36910200119018555, 0.8934370279312134, 0.2559959888458252, -0.03550700098276138, 0.0838790014386177, 0.9958429932594299, 0.26043900847435, -0.6297230124473572, 0.7318609952926636, 0.3325839936733246, -0.803725004196167, 0.4933690130710602, -0.002848000032827258, 0.9661769866943359, -0.25786298513412476, -0.001921999966725707, 0.9670090079307556, 0.2547360062599182, -0.00026500000967644155, 0.09227199852466583, 0.9957339763641357, 0.00002300000051036477, -0.6820600032806396, 0.7312960028648376, 0, -0.8703010082244873, 0.4925200045108795, -0.002848000032827258, 0.9661769866943359, -0.25786298513412476, 0.37905800342559814, 0.852770984172821, -0.35929998755455017, 0.37711000442504883, 0.9140909910202026, 0.14908500015735626, -0.001921999966725707, 0.9670090079307556, 0.2547360062599182, 0.0275030005723238, 0.12255500257015228, 0.9920809864997864, -0.00026500000967644155, 0.09227199852466583, 0.9957339763641357, -0.26100900769233704, -0.6353650093078613, 0.7267630100250244, 0.00002300000051036477, -0.6820600032806396, 0.7312960028648376, -0.33248499035835266, -0.8042709827423096, 0.4925459921360016, 0, -0.8703010082244873, 0.4925200045108795, 0.6635469794273376, 0.6252639889717102, -0.4107919931411743, 0.712664008140564, 0.6976209878921509, 0.07372400164604187, 0.09972699731588364, 0.12198299914598465, 0.98750901222229, -0.4873189926147461, -0.4885669946670532, 0.7237560153007507, -0.6152420043945312, -0.6154839992523193, 0.4926010072231293, 0.8800280094146729, 0.3387089967727661, -0.3329069912433624, 0.9172769784927368, 0.36149299144744873, 0.16711199283599854, 0.11358699947595596, 0.04806999862194061, 0.9923650026321411, -0.6341490149497986, -0.2618879973888397, 0.7275090217590332, -0.8041260242462158, -0.33270499110221863, 0.49263399839401245, 0.9666900038719177, -0.010453999973833561, -0.2557379901409149, 0.967441976070404, -0.00810300000011921, 0.25296199321746826, 0.0934389978647232, -0.0012799999676644802, 0.9956240057945251, -0.6821659803390503, 0.0003429999924264848, 0.7311969995498657, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, 0.9666900038719177, -0.010453999973833561, -0.2557379901409149, 0.8930140137672424, -0.3698819875717163, -0.2563450038433075, 0.8934370279312134, -0.36910200119018555, 0.2559970021247864, 0.967441976070404, -0.00810300000011921, 0.25296199321746826, 0.0838799998164177, -0.03550700098276138, 0.9958429932594299, 0.0934389978647232, -0.0012799999676644802, 0.9956240057945251, -0.629721999168396, 0.2604379951953888, 0.7318620085716248, -0.6821659803390503, 0.0003429999924264848, 0.7311969995498657, -0.803725004196167, 0.3325839936733246, 0.4933690130710602, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, 0.6834070086479187, -0.6834070086479187, -0.2567310035228729, 0.6835309863090515, -0.6835309863090515, 0.25606799125671387, 0.06492599844932556, -0.06492500007152557, 0.9957759976387024, -0.48139700293540955, 0.48139700293540955, 0.7324709892272949, -0.6148040294647217, 0.6148040294647217, 0.4939970076084137, 0.3698819875717163, -0.8930140137672424, -0.2563450038433075, 0.36910200119018555, -0.8934370279312134, 0.2559959888458252, 0.03550700098276138, -0.0838790014386177, 0.9958429932594299, -0.26043900847435, 0.6297230124473572, 0.7318609952926636, -0.3325839936733246, 0.803725004196167, 0.4933690130710602, 0, -0.9667419791221619, -0.25575199723243713, 0, -0.9668239951133728, 0.2554430067539215, 0, -0.09205400198698044, 0.9957540035247803, 0, 0.6820489764213562, 0.7313070297241211, 0, 0.8703010082244873, 0.4925200045108795, 0, -0.9667419791221619, -0.25575199723243713, -0.3698819875717163, -0.8930140137672424, -0.2563450038433075, -0.36910200119018555, -0.8934370279312134, 0.2559970021247864, 0, -0.9668239951133728, 0.2554430067539215, -0.03550700098276138, -0.0838799998164177, 0.9958429932594299, 0, -0.09205400198698044, 0.9957540035247803, 0.2604379951953888, 0.629721999168396, 0.7318620085716248, 0, 0.6820489764213562, 0.7313070297241211, 0.3325839936733246, 0.803725004196167, 0.4933690130710602, 0, 0.8703010082244873, 0.4925200045108795, -0.6834070086479187, -0.6834070086479187, -0.2567310035228729, -0.6835309863090515, -0.6835309863090515, 0.25606799125671387, -0.06492500007152557, -0.06492599844932556, 0.9957759976387024, 0.48139700293540955, 0.48139700293540955, 0.7324709892272949, 0.6148040294647217, 0.6148040294647217, 0.4939970076084137, -0.8930140137672424, -0.3698819875717163, -0.2563450038433075, -0.8934370279312134, -0.36910200119018555, 0.2559959888458252, -0.0838790014386177, -0.03550700098276138, 0.9958429932594299, 0.6297230124473572, 0.26043900847435, 0.7318609952926636, 0.803725004196167, 0.3325839936733246, 0.4933690130710602, -0.9667419791221619, 0, -0.25575199723243713, -0.9668239951133728, 0, 0.2554430067539215, -0.09205400198698044, 0, 0.9957540035247803, 0.6820489764213562, 0, 0.7313070297241211, 0.8703010082244873, 0, 0.4925200045108795, 0.8703010082244873, 0, 0.4925200045108795, 0.803725004196167, -0.3325839936733246, 0.4933690130710602, 0.8454390168190002, -0.34983500838279724, 0.40354499220848083, 0.9153209924697876, 0, 0.4027250111103058, 0.8699960112571716, -0.36004599928855896, 0.33685898780822754, 0.9418079853057861, 0, 0.33615100383758545, 0.9041929841041565, -0.37428000569343567, 0.20579099655151367, 0.9786900281906128, 0, 0.20534199476242065, 0.9218789935112, -0.38175201416015625, -0.06636899709701538, 0.9978039860725403, 0, -0.06623899936676025, 0.6148040294647217, -0.6148040294647217, 0.4939970076084137, 0.6468020081520081, -0.6468020081520081, 0.40409600734710693, 0.6656550168991089, -0.6656550168991089, 0.3373520076274872, 0.6919230222702026, -0.6919230222702026, 0.20611999928951263, 0.7055429816246033, -0.7055429816246033, -0.06647899746894836, 0.3325839936733246, -0.803725004196167, 0.4933690130710602, 0.34983500838279724, -0.8454390168190002, 0.40354499220848083, 0.36004701256752014, -0.8699960112571716, 0.33685800433158875, 0.37428000569343567, -0.9041929841041565, 0.20579099655151367, 0.38175201416015625, -0.9218789935112, -0.06636899709701538, 0, -0.8703010082244873, 0.4925200045108795, 0, -0.9153209924697876, 0.4027250111103058, 0, -0.9418079853057861, 0.33615100383758545, 0, -0.9786900281906128, 0.20534199476242065, 0, -0.9978039860725403, -0.06623899936676025, 0, -0.8703010082244873, 0.4925200045108795, -0.33248499035835266, -0.8042709827423096, 0.4925459921360016, -0.34983500838279724, -0.8454390168190002, 0.40354499220848083, 0, -0.9153209924697876, 0.4027250111103058, -0.36004599928855896, -0.8699960112571716, 0.33685898780822754, 0, -0.9418079853057861, 0.33615100383758545, -0.37428000569343567, -0.9041929841041565, 0.20579099655151367, 0, -0.9786900281906128, 0.20534199476242065, -0.38175201416015625, -0.9218789935112, -0.06636899709701538, 0, -0.9978039860725403, -0.06623899936676025, -0.6152420043945312, -0.6154839992523193, 0.4926010072231293, -0.6468020081520081, -0.6468020081520081, 0.40409600734710693, -0.6656550168991089, -0.6656550168991089, 0.3373520076274872, -0.6919230222702026, -0.6919230222702026, 0.20611999928951263, -0.7055429816246033, -0.7055429816246033, -0.06647899746894836, -0.8041260242462158, -0.33270499110221863, 0.49263399839401245, -0.8454390168190002, -0.34983500838279724, 0.40354499220848083, -0.8699960112571716, -0.36004701256752014, 0.33685800433158875, -0.9041929841041565, -0.37428000569343567, 0.20579099655151367, -0.9218789935112, -0.38175201416015625, -0.06636899709701538, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, -0.9153209924697876, 0, 0.4027250111103058, -0.9418079853057861, 0, 0.33615100383758545, -0.9786900281906128, 0, 0.20534199476242065, -0.9978039860725403, 0, -0.06623899936676025, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, -0.803725004196167, 0.3325839936733246, 0.4933690130710602, -0.8454390168190002, 0.34983500838279724, 0.40354499220848083, -0.9153209924697876, 0, 0.4027250111103058, -0.8699960112571716, 0.36004599928855896, 0.33685898780822754, -0.9418079853057861, 0, 0.33615100383758545, -0.9041929841041565, 0.37428000569343567, 0.20579099655151367, -0.9786900281906128, 0, 0.20534199476242065, -0.9218789935112, 0.38175201416015625, -0.06636899709701538, -0.9978039860725403, 0, -0.06623899936676025, -0.6148040294647217, 0.6148040294647217, 0.4939970076084137, -0.6468020081520081, 0.6468020081520081, 0.40409600734710693, -0.6656550168991089, 0.6656550168991089, 0.3373520076274872, -0.6919230222702026, 0.6919230222702026, 0.20611999928951263, -0.7055429816246033, 0.7055429816246033, -0.06647899746894836, -0.3325839936733246, 0.803725004196167, 0.4933690130710602, -0.34983500838279724, 0.8454390168190002, 0.40354499220848083, -0.36004701256752014, 0.8699960112571716, 0.33685800433158875, -0.37428000569343567, 0.9041929841041565, 0.20579099655151367, -0.38175201416015625, 0.9218789935112, -0.06636899709701538, 0, 0.8703010082244873, 0.4925200045108795, 0, 0.9153209924697876, 0.4027250111103058, 0, 0.9418079853057861, 0.33615100383758545, 0, 0.9786900281906128, 0.20534199476242065, 0, 0.9978039860725403, -0.06623899936676025, 0, 0.8703010082244873, 0.4925200045108795, 0.3325839936733246, 0.803725004196167, 0.4933690130710602, 0.34983500838279724, 0.8454390168190002, 0.40354499220848083, 0, 0.9153209924697876, 0.4027250111103058, 0.36004599928855896, 0.8699960112571716, 0.33685898780822754, 0, 0.9418079853057861, 0.33615100383758545, 0.37428000569343567, 0.9041929841041565, 0.20579099655151367, 0, 0.9786900281906128, 0.20534199476242065, 0.38175201416015625, 0.9218789935112, -0.06636899709701538, 0, 0.9978039860725403, -0.06623899936676025, 0.6148040294647217, 0.6148040294647217, 0.4939970076084137, 0.6468020081520081, 0.6468020081520081, 0.40409600734710693, 0.6656550168991089, 0.6656550168991089, 0.3373520076274872, 0.6919230222702026, 0.6919230222702026, 0.20611999928951263, 0.7055429816246033, 0.7055429816246033, -0.06647899746894836, 0.803725004196167, 0.3325839936733246, 0.4933690130710602, 0.8454390168190002, 0.34983500838279724, 0.40354499220848083, 0.8699960112571716, 0.36004701256752014, 0.33685800433158875, 0.9041929841041565, 0.37428000569343567, 0.20579099655151367, 0.9218789935112, 0.38175201416015625, -0.06636899709701538, 0.8703010082244873, 0, 0.4925200045108795, 0.9153209924697876, 0, 0.4027250111103058, 0.9418079853057861, 0, 0.33615100383758545, 0.9786900281906128, 0, 0.20534199476242065, 0.9978039860725403, 0, -0.06623899936676025, 0.9978039860725403, 0, -0.06623899936676025, 0.9218789935112, -0.38175201416015625, -0.06636899709701538, 0.8314369916915894, -0.3441790044307709, -0.4361799955368042, 0.9001820087432861, 0, -0.4355129897594452, 0.6735119819641113, -0.2785939872264862, -0.6846650242805481, 0.7296109795570374, 0, -0.6838629841804504, 0.6403989791870117, -0.26487401127815247, -0.7209240198135376, 0.6939510107040405, 0, -0.7200220227241516, 0.7329490184783936, -0.303166002035141, -0.6089959740638733, 0.7939500212669373, 0, -0.6079840064048767, 0.7055429816246033, -0.7055429816246033, -0.06647899746894836, 0.6360920071601868, -0.6360920071601868, -0.4367780089378357, 0.5149649977684021, -0.5149649977684021, -0.6852890253067017, 0.48965099453926086, -0.48965099453926086, -0.7214459776878357, 0.5605549812316895, -0.5605549812316895, -0.6095539927482605, 0.38175201416015625, -0.9218789935112, -0.06636899709701538, 0.3441790044307709, -0.8314369916915894, -0.4361799955368042, 0.2785939872264862, -0.6735119819641113, -0.6846650242805481, 0.26487401127815247, -0.6403989791870117, -0.7209240198135376, 0.303166002035141, -0.7329490184783936, -0.6089959740638733, 0, -0.9978039860725403, -0.06623899936676025, 0, -0.9001820087432861, -0.4355129897594452, 0, -0.7296109795570374, -0.6838629841804504, 0, -0.6939510107040405, -0.7200220227241516, 0, -0.7939500212669373, -0.6079840064048767, 0, -0.9978039860725403, -0.06623899936676025, -0.38175201416015625, -0.9218789935112, -0.06636899709701538, -0.3441790044307709, -0.8314369916915894, -0.4361799955368042, 0, -0.9001820087432861, -0.4355129897594452, -0.2785939872264862, -0.6735119819641113, -0.6846650242805481, 0, -0.7296109795570374, -0.6838629841804504, -0.26487401127815247, -0.6403989791870117, -0.7209240198135376, 0, -0.6939510107040405, -0.7200220227241516, -0.303166002035141, -0.7329490184783936, -0.6089959740638733, 0, -0.7939500212669373, -0.6079840064048767, -0.7055429816246033, -0.7055429816246033, -0.06647899746894836, -0.6360920071601868, -0.6360920071601868, -0.4367780089378357, -0.5149649977684021, -0.5149649977684021, -0.6852890253067017, -0.48965099453926086, -0.48965099453926086, -0.7214459776878357, -0.5605549812316895, -0.5605549812316895, -0.6095539927482605, -0.9218789935112, -0.38175201416015625, -0.06636899709701538, -0.8314369916915894, -0.3441790044307709, -0.4361799955368042, -0.6735119819641113, -0.2785939872264862, -0.6846650242805481, -0.6403989791870117, -0.26487401127815247, -0.7209240198135376, -0.7329490184783936, -0.303166002035141, -0.6089959740638733, -0.9978039860725403, 0, -0.06623899936676025, -0.9001820087432861, 0, -0.4355129897594452, -0.7296109795570374, 0, -0.6838629841804504, -0.6939510107040405, 0, -0.7200220227241516, -0.7939500212669373, 0, -0.6079840064048767, -0.9978039860725403, 0, -0.06623899936676025, -0.9218789935112, 0.38175201416015625, -0.06636899709701538, -0.8314369916915894, 0.3441790044307709, -0.4361799955368042, -0.9001820087432861, 0, -0.4355129897594452, -0.6735119819641113, 0.2785939872264862, -0.6846650242805481, -0.7296109795570374, 0, -0.6838629841804504, -0.6403989791870117, 0.26487401127815247, -0.7209240198135376, -0.6939510107040405, 0, -0.7200220227241516, -0.7329490184783936, 0.303166002035141, -0.6089959740638733, -0.7939500212669373, 0, -0.6079840064048767, -0.7055429816246033, 0.7055429816246033, -0.06647899746894836, -0.6360920071601868, 0.6360920071601868, -0.4367780089378357, -0.5149649977684021, 0.5149649977684021, -0.6852890253067017, -0.48965099453926086, 0.48965099453926086, -0.7214459776878357, -0.5605549812316895, 0.5605549812316895, -0.6095539927482605, -0.38175201416015625, 0.9218789935112, -0.06636899709701538, -0.3441790044307709, 0.8314369916915894, -0.4361799955368042, -0.2785939872264862, 0.6735119819641113, -0.6846650242805481, -0.26487401127815247, 0.6403989791870117, -0.7209240198135376, -0.303166002035141, 0.7329490184783936, -0.6089959740638733, 0, 0.9978039860725403, -0.06623899936676025, 0, 0.9001820087432861, -0.4355129897594452, 0, 0.7296109795570374, -0.6838629841804504, 0, 0.6939510107040405, -0.7200220227241516, 0, 0.7939500212669373, -0.6079840064048767, 0, 0.9978039860725403, -0.06623899936676025, 0.38175201416015625, 0.9218789935112, -0.06636899709701538, 0.3441790044307709, 0.8314369916915894, -0.4361799955368042, 0, 0.9001820087432861, -0.4355129897594452, 0.2785939872264862, 0.6735119819641113, -0.6846650242805481, 0, 0.7296109795570374, -0.6838629841804504, 0.26487401127815247, 0.6403989791870117, -0.7209240198135376, 0, 0.6939510107040405, -0.7200220227241516, 0.303166002035141, 0.7329490184783936, -0.6089959740638733, 0, 0.7939500212669373, -0.6079840064048767, 0.7055429816246033, 0.7055429816246033, -0.06647899746894836, 0.6360920071601868, 0.6360920071601868, -0.4367780089378357, 0.5149649977684021, 0.5149649977684021, -0.6852890253067017, 0.48965099453926086, 0.48965099453926086, -0.7214459776878357, 0.5605549812316895, 0.5605549812316895, -0.6095539927482605, 0.9218789935112, 0.38175201416015625, -0.06636899709701538, 0.8314369916915894, 0.3441790044307709, -0.4361799955368042, 0.6735119819641113, 0.2785939872264862, -0.6846650242805481, 0.6403989791870117, 0.26487401127815247, -0.7209240198135376, 0.7329490184783936, 0.303166002035141, -0.6089959740638733, 0.9978039860725403, 0, -0.06623899936676025, 0.9001820087432861, 0, -0.4355129897594452, 0.7296109795570374, 0, -0.6838629841804504, 0.6939510107040405, 0, -0.7200220227241516, 0.7939500212669373, 0, -0.6079840064048767, 0.7939500212669373, 0, -0.6079840064048767, 0.7329490184783936, -0.303166002035141, -0.6089959740638733, 0.576229989528656, -0.23821599781513214, -0.7818009853363037, 0.6238600015640259, 0, -0.7815359830856323, 0.16362899541854858, -0.06752700358629227, -0.9842079877853394, 0.17729100584983826, 0, -0.984158992767334, 0.04542100057005882, -0.018735000863671303, -0.9987919926643372, 0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, 0, 0, -1, 0.5605549812316895, -0.5605549812316895, -0.6095539927482605, 0.44041600823402405, -0.44041600823402405, -0.7823479771614075, 0.12490200251340866, -0.12490200251340866, -0.9842759966850281, 0.034662000834941864, -0.034662000834941864, -0.9987980127334595, 0, 0, -1, 0.303166002035141, -0.7329490184783936, -0.6089959740638733, 0.23821599781513214, -0.576229989528656, -0.7818009853363037, 0.06752700358629227, -0.16362899541854858, -0.9842079877853394, 0.018735000863671303, -0.04542100057005882, -0.9987919926643372, 0, 0, -1, 0, -0.7939500212669373, -0.6079840064048767, 0, -0.6238600015640259, -0.7815359830856323, 0, -0.17729100584983826, -0.984158992767334, 0, -0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, -0.7939500212669373, -0.6079840064048767, -0.303166002035141, -0.7329490184783936, -0.6089959740638733, -0.23821599781513214, -0.576229989528656, -0.7818009853363037, 0, -0.6238600015640259, -0.7815359830856323, -0.06752700358629227, -0.16362899541854858, -0.9842079877853394, 0, -0.17729100584983826, -0.984158992767334, -0.018735000863671303, -0.04542100057005882, -0.9987919926643372, 0, -0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, 0, -1, -0.5605549812316895, -0.5605549812316895, -0.6095539927482605, -0.44041600823402405, -0.44041600823402405, -0.7823479771614075, -0.12490200251340866, -0.12490200251340866, -0.9842759966850281, -0.034662000834941864, -0.034662000834941864, -0.9987980127334595, 0, 0, -1, -0.7329490184783936, -0.303166002035141, -0.6089959740638733, -0.576229989528656, -0.23821599781513214, -0.7818009853363037, -0.16362899541854858, -0.06752700358629227, -0.9842079877853394, -0.04542100057005882, -0.018735000863671303, -0.9987919926643372, 0, 0, -1, -0.7939500212669373, 0, -0.6079840064048767, -0.6238600015640259, 0, -0.7815359830856323, -0.17729100584983826, 0, -0.984158992767334, -0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, -0.7939500212669373, 0, -0.6079840064048767, -0.7329490184783936, 0.303166002035141, -0.6089959740638733, -0.576229989528656, 0.23821599781513214, -0.7818009853363037, -0.6238600015640259, 0, -0.7815359830856323, -0.16362899541854858, 0.06752700358629227, -0.9842079877853394, -0.17729100584983826, 0, -0.984158992767334, -0.04542100057005882, 0.018735000863671303, -0.9987919926643372, -0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, 0, 0, -1, -0.5605549812316895, 0.5605549812316895, -0.6095539927482605, -0.44041600823402405, 0.44041600823402405, -0.7823479771614075, -0.12490200251340866, 0.12490200251340866, -0.9842759966850281, -0.034662000834941864, 0.034662000834941864, -0.9987980127334595, 0, 0, -1, -0.303166002035141, 0.7329490184783936, -0.6089959740638733, -0.23821599781513214, 0.576229989528656, -0.7818009853363037, -0.06752700358629227, 0.16362899541854858, -0.9842079877853394, -0.018735000863671303, 0.04542100057005882, -0.9987919926643372, 0, 0, -1, 0, 0.7939500212669373, -0.6079840064048767, 0, 0.6238600015640259, -0.7815359830856323, 0, 0.17729100584983826, -0.984158992767334, 0, 0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, 0.7939500212669373, -0.6079840064048767, 0.303166002035141, 0.7329490184783936, -0.6089959740638733, 0.23821599781513214, 0.576229989528656, -0.7818009853363037, 0, 0.6238600015640259, -0.7815359830856323, 0.06752700358629227, 0.16362899541854858, -0.9842079877853394, 0, 0.17729100584983826, -0.984158992767334, 0.018735000863671303, 0.04542100057005882, -0.9987919926643372, 0, 0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, 0, -1, 0.5605549812316895, 0.5605549812316895, -0.6095539927482605, 0.44041600823402405, 0.44041600823402405, -0.7823479771614075, 0.12490200251340866, 0.12490200251340866, -0.9842759966850281, 0.034662000834941864, 0.034662000834941864, -0.9987980127334595, 0, 0, -1, 0.7329490184783936, 0.303166002035141, -0.6089959740638733, 0.576229989528656, 0.23821599781513214, -0.7818009853363037, 0.16362899541854858, 0.06752700358629227, -0.9842079877853394, 0.04542100057005882, 0.018735000863671303, -0.9987919926643372, 0, 0, -1, 0.7939500212669373, 0, -0.6079840064048767, 0.6238600015640259, 0, -0.7815359830856323, 0.17729100584983826, 0, -0.984158992767334, 0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, 0.007784999907016754, 0.00021499999274965376, -0.999970018863678, 0.007038000039756298, -0.5829259753227234, -0.8124949932098389, 0.0361270010471344, -0.5456140041351318, -0.837257981300354, 0.03913800045847893, 0.0009879999561235309, -0.9992330074310303, 0.16184599697589874, -0.5630490183830261, -0.8104209899902344, 0.17951199412345886, 0.0043680001981556416, -0.9837459921836853, 0.4823650121688843, -0.6427459716796875, -0.5951480269432068, 0.6122999787330627, 0.010459000244736671, -0.790556013584137, 0.7387199997901917, -0.6641989946365356, -0.11459299921989441, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, -0.0019079999765381217, -0.9867690205574036, 0.1621209979057312, 0.002761000068858266, -0.9998499751091003, 0.017105000093579292, 0.010532000102102757, -0.9972469806671143, 0.07339800149202347, -0.06604000180959702, -0.9893029928207397, 0.13006900250911713, -0.09442699700593948, -0.9953929781913757, 0.016594000160694122, -0.009201999753713608, -0.4902929961681366, 0.8715090155601501, -0.04860600084066391, -0.5394579768180847, 0.8406090140342712, -0.22329799830913544, -0.5527390241622925, 0.8028810024261475, -0.5963649749755859, -0.5751349925994873, 0.5599709749221802, -0.8033369779586792, -0.5916029810905457, 0.06823500245809555, -0.01056000031530857, -0.00010299999848939478, 0.9999439716339111, -0.05879800021648407, -0.0007089999853633344, 0.9982699751853943, -0.28071001172065735, -0.0032679999712854624, 0.9597870111465454, -0.7497230172157288, -0.004267000127583742, 0.6617379784584045, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.01056000031530857, -0.00010299999848939478, 0.9999439716339111, -0.008791999891400337, 0.49032899737358093, 0.8714929819107056, -0.04649300128221512, 0.5387560129165649, 0.8411779999732971, -0.05879800021648407, -0.0007089999853633344, 0.9982699751853943, -0.21790899336338043, 0.5491610169410706, 0.8068069815635681, -0.28071001172065735, -0.0032679999712854624, 0.9597870111465454, -0.5972909927368164, 0.5741199851036072, 0.560027003288269, -0.7497230172157288, -0.004267000127583742, 0.6617379784584045, -0.8040000200271606, 0.5912910103797913, 0.0629120022058487, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.0018050000071525574, 0.986840009689331, 0.16169099509716034, 0.0020310000982135534, 0.999891996383667, 0.014553000219166279, 0.009215000085532665, 0.9981520175933838, 0.060068998485803604, -0.059335000813007355, 0.9917230010032654, 0.11386600136756897, -0.08690100163221359, 0.9961410164833069, 0.01228999998420477, 0.006417000200599432, 0.5830950140953064, -0.812379002571106, 0.03378299996256828, 0.5453730225563049, -0.8375130295753479, 0.1571130007505417, 0.562188982963562, -0.8119469881057739, 0.4844059944152832, 0.6465290188789368, -0.5893650054931641, 0.7388700246810913, 0.6661880016326904, -0.10131999850273132, 0.007784999907016754, 0.00021499999274965376, -0.999970018863678, 0.03913800045847893, 0.0009879999561235309, -0.9992330074310303, 0.17951199412345886, 0.0043680001981556416, -0.9837459921836853, 0.6122999787330627, 0.010459000244736671, -0.790556013584137, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, 0.7387199997901917, -0.6641989946365356, -0.11459299921989441, 0.7256090044975281, -0.6373609900474548, 0.25935098528862, 0.94651198387146, 0.0033569999504834414, 0.3226499855518341, 0.6459450125694275, -0.6077200174331665, 0.46198800206184387, 0.8258299827575684, 0.007451999932527542, 0.5638700127601624, 0.5316150188446045, -0.5586140155792236, 0.6366599798202515, 0.6500110030174255, 0.006936000194400549, 0.759893000125885, 0.4249640107154846, -0.5955389738082886, 0.6817179918289185, 0.5324289798736572, 0.005243999883532524, 0.8464580178260803, -0.09442699700593948, -0.9953929781913757, 0.016594000160694122, -0.04956100136041641, -0.9985759854316711, -0.01975500024855137, -0.03781700134277344, -0.998649001121521, -0.035624999552965164, -0.0379129983484745, -0.9986140131950378, -0.03651199862360954, -0.1688539981842041, -0.9395300149917603, -0.2979460060596466, -0.8033369779586792, -0.5916029810905457, 0.06823500245809555, -0.7423409819602966, -0.5995240211486816, -0.2991659939289093, -0.6196020245552063, -0.5795029997825623, -0.5294060111045837, -0.483707994222641, -0.5438370108604431, -0.6857600212097168, -0.44529199600219727, -0.4131770133972168, -0.7943549752235413, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.9265130162239075, -0.0019950000569224358, -0.3762570023536682, -0.7539200186729431, -0.004317000042647123, -0.6569520235061646, -0.5662239789962769, -0.003461000043898821, -0.8242440223693848, -0.4818040132522583, -0.0018500000005587935, -0.8762770295143127, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.8040000200271606, 0.5912910103797913, 0.0629120022058487, -0.7446749806404114, 0.5989770293235779, -0.29442399740219116, -0.9265130162239075, -0.0019950000569224358, -0.3762570023536682, -0.6219490170478821, 0.5781649947166443, -0.5281140208244324, -0.7539200186729431, -0.004317000042647123, -0.6569520235061646, -0.48117101192474365, 0.5428280234336853, -0.6883400082588196, -0.5662239789962769, -0.003461000043898821, -0.8242440223693848, -0.43805500864982605, 0.41574400663375854, -0.7970349788665771, -0.4818040132522583, -0.0018500000005587935, -0.8762770295143127, -0.08690100163221359, 0.9961410164833069, 0.01228999998420477, -0.04433799907565117, 0.9988710284233093, -0.017055999487638474, -0.026177000254392624, 0.9992600083351135, -0.02816700004041195, -0.025293000042438507, 0.9992780089378357, -0.028332000598311424, -0.15748199820518494, 0.9441670179367065, -0.28939300775527954, 0.7388700246810913, 0.6661880016326904, -0.10131999850273132, 0.7282440066337585, 0.63714200258255, 0.25240999460220337, 0.6470540165901184, 0.6082550287246704, 0.4597249925136566, 0.5229939818382263, 0.5621700286865234, 0.6406570076942444, 0.4099780023097992, 0.6046689748764038, 0.6828569769859314, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, 0.94651198387146, 0.0033569999504834414, 0.3226499855518341, 0.8258299827575684, 0.007451999932527542, 0.5638700127601624, 0.6500110030174255, 0.006936000194400549, 0.759893000125885, 0.5324289798736572, 0.005243999883532524, 0.8464580178260803, -0.230786994099617, 0.006523000076413155, 0.9729819893836975, -0.15287800133228302, -0.7101899981498718, 0.6872109770774841, -0.31672099232673645, -0.7021129727363586, 0.6377500295639038, -0.5489360094070435, 0.0015109999803826213, 0.8358629941940308, -0.6010670065879822, -0.645330011844635, 0.471451997756958, -0.8756710290908813, -0.009891999885439873, 0.4828070104122162, -0.635890007019043, -0.629800021648407, 0.4460900127887726, -0.8775539994239807, -0.01909100078046322, 0.47909700870513916, -0.4357450008392334, -0.670009970664978, 0.6010090112686157, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, 0.11111299693584442, -0.9901599884033203, -0.08506900072097778, 0.22330999374389648, -0.9747260212898254, 0.006539999973028898, 0.19009700417518616, -0.9694579839706421, 0.15496399998664856, 0.005270000081509352, -0.9818699955940247, 0.18948200345039368, -0.011750999838113785, -0.9690240025520325, 0.24668699502944946, 0.3439059853553772, -0.5994120240211487, -0.7227950096130371, 0.5724899768829346, -0.5916270017623901, -0.5676559805870056, 0.7874360084533691, -0.5605109930038452, -0.2564600110054016, 0.6470969915390015, -0.6981409788131714, -0.3063740134239197, 0.4275279939174652, -0.7535750269889832, -0.49934399127960205, 0.4109260141849518, -0.0012839999981224537, -0.9116680026054382, 0.6715199947357178, 0.0008989999769255519, -0.7409859895706177, 0.9220259785652161, 0.00725199980661273, -0.3870599865913391, 0.8469099998474121, 0.01385399978607893, -0.5315560102462769, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.4109260141849518, -0.0012839999981224537, -0.9116680026054382, 0.3411880135536194, 0.6009309887886047, -0.7228230237960815, 0.5786640048027039, 0.591838002204895, -0.5611389875411987, 0.6715199947357178, 0.0008989999769255519, -0.7409859895706177, 0.7848690152168274, 0.5665420293807983, -0.25102001428604126, 0.9220259785652161, 0.00725199980661273, -0.3870599865913391, 0.6426810026168823, 0.7039899826049805, -0.3022570013999939, 0.8469099998474121, 0.01385399978607893, -0.5315560102462769, 0.4185889959335327, 0.7581170201301575, -0.5000420212745667, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.11580599844455719, 0.9901139736175537, -0.07913900166749954, 0.23281100392341614, 0.9724410176277161, 0.012564999982714653, 0.20666299760341644, 0.9662799835205078, 0.15360000729560852, 0.02449899911880493, 0.9865779876708984, 0.16144299507141113, 0.0033809999004006386, 0.9774550199508667, 0.2111150026321411, -0.13491199910640717, 0.7135509848594666, 0.6874909996986389, -0.31953999400138855, 0.7050619721412659, 0.6330729722976685, -0.6039019823074341, 0.6499029994010925, 0.4614419937133789, -0.6318150162696838, 0.6400719881057739, 0.43716898560523987, -0.4243049919605255, 0.6667500138282776, 0.6127070188522339, -0.230786994099617, 0.006523000076413155, 0.9729819893836975, -0.5489360094070435, 0.0015109999803826213, 0.8358629941940308, -0.8756710290908813, -0.009891999885439873, 0.4828070104122162, -0.8775539994239807, -0.01909100078046322, 0.47909700870513916, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, -0.4357450008392334, -0.670009970664978, 0.6010090112686157, -0.25985801219940186, -0.5525479912757874, 0.7919380068778992, -0.42579901218414307, -0.010804999619722366, 0.9047530293464661, 0.009537000209093094, 0.021669000387191772, 0.9997199773788452, 0.022041000425815582, -0.001623000018298626, 0.9997559785842896, 0.4101540148258209, 0.8490809798240662, 0.3329179883003235, 0.9995980262756348, -0.01155600044876337, 0.02587899938225746, 0.5415220260620117, 0.6370009779930115, -0.5486199855804443, 0.7095860242843628, -0.009670999832451344, -0.7045519948005676, -0.011750999838113785, -0.9690240025520325, 0.24668699502944946, 0.046310000121593475, -0.8891720175743103, 0.45522499084472656, -0.010688000358641148, -0.14889900386333466, 0.9887949824333191, -0.04437499865889549, 0.7291200160980225, 0.6829460263252258, 0.12282499670982361, 0.9923850297927856, 0.009232000447809696, 0.4275279939174652, -0.7535750269889832, -0.49934399127960205, 0.48183900117874146, -0.857479989528656, -0.18044300377368927, 0.45527198910713196, -0.49992498755455017, 0.7367510199546814, -0.22054199874401093, 0.3582780063152313, 0.9071930050849915, -0.23591899871826172, 0.7157959938049316, 0.6572499871253967, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.7280910015106201, 0.015584999695420265, -0.6853029727935791, 0.8887389898300171, 0.016679000109434128, 0.4581089913845062, -0.26009801030158997, -0.0007999999797903001, 0.965582013130188, -0.37161099910736084, 0.004416999872773886, 0.9283779859542847, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.4185889959335327, 0.7581170201301575, -0.5000420212745667, 0.4801650047302246, 0.8588529825210571, -0.17836299538612366, 0.7280910015106201, 0.015584999695420265, -0.6853029727935791, 0.4881030023097992, 0.49794700741767883, 0.7168020009994507, 0.8887389898300171, 0.016679000109434128, 0.4581089913845062, -0.2220049947500229, -0.36189401149749756, 0.9053990244865417, -0.26009801030158997, -0.0007999999797903001, 0.965582013130188, -0.23540399968624115, -0.7104769945144653, 0.6631799936294556, -0.37161099910736084, 0.004416999872773886, 0.9283779859542847, 0.0033809999004006386, 0.9774550199508667, 0.2111150026321411, 0.058719001710414886, 0.8971999883651733, 0.437703013420105, 0.0013249999610707164, 0.164000004529953, 0.9864590167999268, -0.04418899863958359, -0.7303190231323242, 0.6816750168800354, 0.13880200684070587, -0.9897300004959106, -0.034189000725746155, -0.4243049919605255, 0.6667500138282776, 0.6127070188522339, -0.25888898968696594, 0.5453789830207825, 0.7972059845924377, 0.012268000282347202, -0.01928500086069107, 0.9997389912605286, 0.3986299932003021, -0.8456630110740662, 0.3548929989337921, 0.5375639796257019, -0.6107370257377625, -0.5813990235328674, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, -0.42579901218414307, -0.010804999619722366, 0.9047530293464661, 0.022041000425815582, -0.001623000018298626, 0.9997559785842896, 0.9995980262756348, -0.01155600044876337, 0.02587899938225746, 0.7095860242843628, -0.009670999832451344, -0.7045519948005676, 0, 0, 1, 0, 0, 1, 0.7626410126686096, -0.31482499837875366, 0.5650339722633362, 0.8245400190353394, -0.00001700000029813964, 0.5658029913902283, 0.8479819893836975, -0.3500339984893799, -0.39799800515174866, 0.917701005935669, -0.00003300000025774352, -0.397271990776062, 0.8641409873962402, -0.35644200444221497, -0.3552600145339966, 0.9352689981460571, -0.00011200000153621659, -0.3539389967918396, 0.7209920287132263, -0.29793301224708557, 0.6256250143051147, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0, 0, 1, 0.5833569765090942, -0.5833380222320557, 0.5651649832725525, 0.648485004901886, -0.6484479904174805, -0.3987259864807129, 0.6608719825744629, -0.6607480049133301, -0.35589399933815, 0.5518630146980286, -0.5517799854278564, 0.6252880096435547, 0, 0, 1, 0.31482499837875366, -0.762628972530365, 0.5650510191917419, 0.35004499554634094, -0.8479880094528198, -0.39797601103782654, 0.35647401213645935, -0.8641520142555237, -0.35519900918006897, 0.29798200726509094, -0.7210670113563538, 0.6255149841308594, 0, 0, 1, -0.00001700000029813964, -0.8245400190353394, 0.5658029913902283, -0.00003300000025774352, -0.917701005935669, -0.397271990776062, -0.00011200000153621659, -0.9352689981460571, -0.3539389967918396, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, 0, 0, 1, 0, 0, 1, -0.31482499837875366, -0.7626410126686096, 0.5650339722633362, -0.00001700000029813964, -0.8245400190353394, 0.5658029913902283, -0.3500339984893799, -0.8479819893836975, -0.39799800515174866, -0.00003300000025774352, -0.917701005935669, -0.397271990776062, -0.35644200444221497, -0.8641409873962402, -0.3552600145339966, -0.00011200000153621659, -0.9352689981460571, -0.3539389967918396, -0.29793301224708557, -0.7209920287132263, 0.6256250143051147, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, 0, 0, 1, -0.5833380222320557, -0.5833569765090942, 0.5651649832725525, -0.6484479904174805, -0.648485004901886, -0.3987259864807129, -0.6607480049133301, -0.6608719825744629, -0.35589399933815, -0.5517799854278564, -0.5518630146980286, 0.6252880096435547, 0, 0, 1, -0.762628972530365, -0.31482499837875366, 0.5650510191917419, -0.8479880094528198, -0.35004499554634094, -0.39797601103782654, -0.8641520142555237, -0.35647401213645935, -0.35519900918006897, -0.7210670113563538, -0.29798200726509094, 0.6255149841308594, 0, 0, 1, -0.8245400190353394, 0.00001700000029813964, 0.5658029913902283, -0.917701005935669, 0.00003300000025774352, -0.397271990776062, -0.9352689981460571, 0.00011200000153621659, -0.3539389967918396, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, 0, 0, 1, 0, 0, 1, -0.7626410126686096, 0.31482499837875366, 0.5650339722633362, -0.8245400190353394, 0.00001700000029813964, 0.5658029913902283, -0.8479819893836975, 0.3500339984893799, -0.39799800515174866, -0.917701005935669, 0.00003300000025774352, -0.397271990776062, -0.8641409873962402, 0.35644200444221497, -0.3552600145339966, -0.9352689981460571, 0.00011200000153621659, -0.3539389967918396, -0.7209920287132263, 0.29793301224708557, 0.6256250143051147, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, 0, 0, 1, -0.5833569765090942, 0.5833380222320557, 0.5651649832725525, -0.648485004901886, 0.6484479904174805, -0.3987259864807129, -0.6608719825744629, 0.6607480049133301, -0.35589399933815, -0.5518630146980286, 0.5517799854278564, 0.6252880096435547, 0, 0, 1, -0.31482499837875366, 0.762628972530365, 0.5650510191917419, -0.35004499554634094, 0.8479880094528198, -0.39797601103782654, -0.35647401213645935, 0.8641520142555237, -0.35519900918006897, -0.29798200726509094, 0.7210670113563538, 0.6255149841308594, 0, 0, 1, 0.00001700000029813964, 0.8245400190353394, 0.5658029913902283, 0.00003300000025774352, 0.917701005935669, -0.397271990776062, 0.00011200000153621659, 0.9352689981460571, -0.3539389967918396, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0, 0, 1, 0, 0, 1, 0.31482499837875366, 0.7626410126686096, 0.5650339722633362, 0.00001700000029813964, 0.8245400190353394, 0.5658029913902283, 0.3500339984893799, 0.8479819893836975, -0.39799800515174866, 0.00003300000025774352, 0.917701005935669, -0.397271990776062, 0.35644200444221497, 0.8641409873962402, -0.3552600145339966, 0.00011200000153621659, 0.9352689981460571, -0.3539389967918396, 0.29793301224708557, 0.7209920287132263, 0.6256250143051147, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0, 0, 1, 0.5833380222320557, 0.5833569765090942, 0.5651649832725525, 0.6484479904174805, 0.648485004901886, -0.3987259864807129, 0.6607480049133301, 0.6608719825744629, -0.35589399933815, 0.5517799854278564, 0.5518630146980286, 0.6252880096435547, 0, 0, 1, 0.762628972530365, 0.31482499837875366, 0.5650510191917419, 0.8479880094528198, 0.35004499554634094, -0.39797601103782654, 0.8641520142555237, 0.35647401213645935, -0.35519900918006897, 0.7210670113563538, 0.29798200726509094, 0.6255149841308594, 0, 0, 1, 0.8245400190353394, -0.00001700000029813964, 0.5658029913902283, 0.917701005935669, -0.00003300000025774352, -0.397271990776062, 0.9352689981460571, -0.00011200000153621659, -0.3539389967918396, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0.7209920287132263, -0.29793301224708557, 0.6256250143051147, 0.21797800064086914, -0.0902160033583641, 0.9717749953269958, 0.23658299446105957, 0, 0.9716110229492188, 0.1595889925956726, -0.06596100330352783, 0.9849770069122314, 0.17308400571346283, 0, 0.9849069714546204, 0.3504979908466339, -0.1447400003671646, 0.9253119826316833, 0.37970298528671265, 0, 0.925108015537262, 0.48558899760246277, -0.20147399604320526, 0.8506529927253723, 0.5266720056533813, 0, 0.8500679731369019, 0.5518630146980286, -0.5517799854278564, 0.6252880096435547, 0.16663099825382233, -0.16663099825382233, 0.9718379974365234, 0.12190800160169601, -0.12190800160169601, 0.9850260019302368, 0.2676680088043213, -0.2676680088043213, 0.9255849719047546, 0.37131500244140625, -0.37131500244140625, 0.8510289788246155, 0.29798200726509094, -0.7210670113563538, 0.6255149841308594, 0.0902160033583641, -0.21797800064086914, 0.9717749953269958, 0.06596100330352783, -0.1595889925956726, 0.9849770069122314, 0.1447400003671646, -0.3504979908466339, 0.9253119826316833, 0.20147399604320526, -0.48558899760246277, 0.8506529927253723, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, 0, -0.23658299446105957, 0.9716110229492188, 0, -0.17308400571346283, 0.9849069714546204, 0, -0.37970298528671265, 0.925108015537262, 0, -0.5266720056533813, 0.8500679731369019, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, -0.29793301224708557, -0.7209920287132263, 0.6256250143051147, -0.0902160033583641, -0.21797800064086914, 0.9717749953269958, 0, -0.23658299446105957, 0.9716110229492188, -0.06596100330352783, -0.1595889925956726, 0.9849770069122314, 0, -0.17308400571346283, 0.9849069714546204, -0.1447400003671646, -0.3504979908466339, 0.9253119826316833, 0, -0.37970298528671265, 0.925108015537262, -0.20147399604320526, -0.48558899760246277, 0.8506529927253723, 0, -0.5266720056533813, 0.8500679731369019, -0.5517799854278564, -0.5518630146980286, 0.6252880096435547, -0.16663099825382233, -0.16663099825382233, 0.9718379974365234, -0.12190800160169601, -0.12190800160169601, 0.9850260019302368, -0.2676680088043213, -0.2676680088043213, 0.9255849719047546, -0.37131500244140625, -0.37131500244140625, 0.8510289788246155, -0.7210670113563538, -0.29798200726509094, 0.6255149841308594, -0.21797800064086914, -0.0902160033583641, 0.9717749953269958, -0.1595889925956726, -0.06596100330352783, 0.9849770069122314, -0.3504979908466339, -0.1447400003671646, 0.9253119826316833, -0.48558899760246277, -0.20147399604320526, 0.8506529927253723, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, -0.23658299446105957, 0, 0.9716110229492188, -0.17308400571346283, 0, 0.9849069714546204, -0.37970298528671265, 0, 0.925108015537262, -0.5266720056533813, 0, 0.8500679731369019, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, -0.7209920287132263, 0.29793301224708557, 0.6256250143051147, -0.21797800064086914, 0.0902160033583641, 0.9717749953269958, -0.23658299446105957, 0, 0.9716110229492188, -0.1595889925956726, 0.06596100330352783, 0.9849770069122314, -0.17308400571346283, 0, 0.9849069714546204, -0.3504979908466339, 0.1447400003671646, 0.9253119826316833, -0.37970298528671265, 0, 0.925108015537262, -0.48558899760246277, 0.20147399604320526, 0.8506529927253723, -0.5266720056533813, 0, 0.8500679731369019, -0.5518630146980286, 0.5517799854278564, 0.6252880096435547, -0.16663099825382233, 0.16663099825382233, 0.9718379974365234, -0.12190800160169601, 0.12190800160169601, 0.9850260019302368, -0.2676680088043213, 0.2676680088043213, 0.9255849719047546, -0.37131500244140625, 0.37131500244140625, 0.8510289788246155, -0.29798200726509094, 0.7210670113563538, 0.6255149841308594, -0.0902160033583641, 0.21797800064086914, 0.9717749953269958, -0.06596100330352783, 0.1595889925956726, 0.9849770069122314, -0.1447400003671646, 0.3504979908466339, 0.9253119826316833, -0.20147399604320526, 0.48558899760246277, 0.8506529927253723, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0, 0.23658299446105957, 0.9716110229492188, 0, 0.17308400571346283, 0.9849069714546204, 0, 0.37970298528671265, 0.925108015537262, 0, 0.5266720056533813, 0.8500679731369019, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0.29793301224708557, 0.7209920287132263, 0.6256250143051147, 0.0902160033583641, 0.21797800064086914, 0.9717749953269958, 0, 0.23658299446105957, 0.9716110229492188, 0.06596100330352783, 0.1595889925956726, 0.9849770069122314, 0, 0.17308400571346283, 0.9849069714546204, 0.1447400003671646, 0.3504979908466339, 0.9253119826316833, 0, 0.37970298528671265, 0.925108015537262, 0.20147399604320526, 0.48558899760246277, 0.8506529927253723, 0, 0.5266720056533813, 0.8500679731369019, 0.5517799854278564, 0.5518630146980286, 0.6252880096435547, 0.16663099825382233, 0.16663099825382233, 0.9718379974365234, 0.12190800160169601, 0.12190800160169601, 0.9850260019302368, 0.2676680088043213, 0.2676680088043213, 0.9255849719047546, 0.37131500244140625, 0.37131500244140625, 0.8510289788246155, 0.7210670113563538, 0.29798200726509094, 0.6255149841308594, 0.21797800064086914, 0.0902160033583641, 0.9717749953269958, 0.1595889925956726, 0.06596100330352783, 0.9849770069122314, 0.3504979908466339, 0.1447400003671646, 0.9253119826316833, 0.48558899760246277, 0.20147399604320526, 0.8506529927253723, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0.23658299446105957, 0, 0.9716110229492188, 0.17308400571346283, 0, 0.9849069714546204, 0.37970298528671265, 0, 0.925108015537262, 0.5266720056533813, 0, 0.8500679731369019 ]);\nvar teapotTangents = new Float32Array([ 0.012897999957203865, 0.998727023601532, -0.048757001757621765, 0.3861910104751587, 0.9210079908370972, -0.016421999782323837, 0.38136398792266846, 0.9230089783668518, 0.000155999994603917, 0.012866999953985214, 0.9987300038337708, 0.04870200157165527, 0.3750790059566498, 0.9061710238456726, -0.0007169999880716205, 0.19210100173950195, 0.9812139868736267, 0.01775900088250637, 0.3782620131969452, 0.9142940044403076, -0.00011300000187475234, 0.10451500117778778, 0.9897350072860718, -0.09747499972581863, 0.3655939996242523, 0.9257190227508545, 0.028463000431656837, 0.04767199978232384, 0.9953050017356873, -0.08423800021409988, 0.7092679738998413, 0.7031199932098389, -0.016364000737667084, 0.7061989903450012, 0.7061989903450012, 0, 0.6937360167503357, 0.6937360167503357, 0, 0.6997770071029663, 0.6997770071029663, 0, 0.6924030184745789, 0.7150859832763672, 0.02822900004684925, 0.9243540167808533, 0.37810400128364563, -0.01657800003886223, 0.9230089783668518, 0.38136398792266846, -0.000155999994603917, 0.9061710238456726, 0.3750790059566498, 0.0007169999880716205, 0.9142940044403076, 0.3782620131969452, 0.00011300000187475234, 0.9133660197257996, 0.39544400572776794, 0.028490999713540077, 0.9987040162086487, 0.015853000804781914, 0.04836999997496605, 0.9987369775772095, 0.014649000018835068, -0.04806999862194061, 0.9812150001525879, 0.19211700558662415, -0.01754000037908554, 0.9897350072860718, 0.10452800244092941, 0.09745799750089645, 0.9953050017356873, 0.04767199978232384, 0.08423800021409988, 0.9988179802894592, -0.009758999571204185, -0.047600001096725464, 0.9094679951667786, -0.4095839858055115, -0.012636999599635601, 0.9240090250968933, -0.3811509907245636, -0.0003150000120513141, 0.9987890124320984, -0.01066299993544817, 0.04801800101995468, 0.9072269797325134, -0.37142300605773926, 0.0207310002297163, 0.9814350008964539, -0.19095200300216675, 0.01795700006186962, 0.914870023727417, -0.3771440088748932, -0.0011480000102892518, 0.989749014377594, -0.10442499816417694, -0.09742700308561325, 0.925815999507904, -0.3653950095176697, 0.028308000415563583, 0.9953050017356873, -0.04767199978232384, -0.08423800021409988, 0.6768929958343506, -0.7314029932022095, -0.01988700032234192, 0.6994619965553284, -0.7145140171051025, -0.00029799999902024865, 0.6940590143203735, -0.6933979988098145, 0.015560000203549862, 0.7002580165863037, -0.6996300220489502, -0.000783999974373728, 0.715142011642456, -0.6923869848251343, 0.028078999370336533, 0.351936012506485, -0.933899998664856, -0.019843999296426773, 0.36654001474380493, -0.9298419952392578, -0.0005210000090301037, 0.37116900086402893, -0.9084830284118652, 0.00152299995534122, 0.3776479959487915, -0.9147650003433228, -0.00011000000085914508, 0.39533698558807373, -0.9134349822998047, 0.028410999104380608, 0.0013210000470280647, -0.9989479780197144, 0.045830998569726944, 0.003897000104188919, -0.9988909959793091, -0.04690299928188324, 0.18705999851226807, -0.9821630120277405, -0.018818000331521034, 0.10363999754190445, -0.9898579716682434, 0.09715499728918076, 0.04757700115442276, -0.9953129887580872, 0.08418799936771393, -0.02296699956059456, -0.9986780285835266, -0.04599199816584587, -0.3861910104751587, -0.9210079908370972, -0.016421999782323837, -0.38136398792266846, -0.9230089783668518, 0.000155999994603917, -0.020431000739336014, -0.9987260103225708, 0.04614400118589401, -0.3750790059566498, -0.9061710238456726, -0.0007169999880716205, -0.19216600060462952, -0.9812189936637878, 0.01677200011909008, -0.3782620131969452, -0.9142940044403076, -0.00011300000187475234, -0.10471200197935104, -0.9897390007972717, -0.09722500294446945, -0.3655939996242523, -0.9257190227508545, 0.028463000431656837, -0.047710999846458435, -0.9953050017356873, -0.08420699834823608, -0.7092679738998413, -0.7031199932098389, -0.016364000737667084, -0.7061989903450012, -0.7061989903450012, 0, -0.6937360167503357, -0.6937360167503357, 0, -0.6997770071029663, -0.6997770071029663, 0, -0.6924030184745789, -0.7150859832763672, 0.02822900004684925, -0.9243540167808533, -0.37810400128364563, -0.01657800003886223, -0.9230089783668518, -0.38136398792266846, -0.000155999994603917, -0.9061710238456726, -0.3750790059566498, 0.0007169999880716205, -0.9142940044403076, -0.3782620131969452, 0.00011300000187475234, -0.9133660197257996, -0.39544400572776794, 0.028490999713540077, -0.998727023601532, -0.012897999957203865, 0.048757001757621765, -0.9987300038337708, -0.012866999953985214, -0.04870200157165527, -0.9812139868736267, -0.19210100173950195, -0.01775900088250637, -0.9897350072860718, -0.10451500117778778, 0.09747499972581863, -0.9953050017356873, -0.04767199978232384, 0.08423800021409988, -0.998727023601532, 0.012897999957203865, -0.048757001757621765, -0.9210079908370972, 0.3861910104751587, -0.016421999782323837, -0.9230089783668518, 0.38136398792266846, 0.000155999994603917, -0.9987300038337708, 0.012866999953985214, 0.04870200157165527, -0.9061710238456726, 0.3750790059566498, -0.0007169999880716205, -0.9812139868736267, 0.19210100173950195, 0.01775900088250637, -0.9142940044403076, 0.3782620131969452, -0.00011300000187475234, -0.9897350072860718, 0.10451500117778778, -0.09747499972581863, -0.9257190227508545, 0.3655939996242523, 0.028463000431656837, -0.9953050017356873, 0.04767199978232384, -0.08423800021409988, -0.7031199932098389, 0.7092679738998413, -0.016364000737667084, -0.7061989903450012, 0.7061989903450012, 0, -0.6937360167503357, 0.6937360167503357, 0, -0.6997770071029663, 0.6997770071029663, 0, -0.7150859832763672, 0.6924030184745789, 0.02822900004684925, -0.37810400128364563, 0.9243540167808533, -0.01657800003886223, -0.38136398792266846, 0.9230089783668518, -0.000155999994603917, -0.3750790059566498, 0.9061710238456726, 0.0007169999880716205, -0.3782620131969452, 0.9142940044403076, 0.00011300000187475234, -0.39544400572776794, 0.9133660197257996, 0.028490999713540077, -0.012897999957203865, 0.998727023601532, 0.048757001757621765, -0.012866999953985214, 0.9987300038337708, -0.04870200157165527, -0.19210100173950195, 0.9812139868736267, -0.01775900088250637, -0.10451500117778778, 0.9897350072860718, 0.09747499972581863, -0.04767199978232384, 0.9953050017356873, 0.08423800021409988, 0.04767199978232384, 0.9953050017356873, -0.08423800021409988, 0.39544400572776794, 0.9133660197257996, -0.028490999713540077, 0.38111698627471924, 0.9210190176963806, -0.000015999999959603883, 0.031922999769449234, 0.9968529939651489, -0.07255599647760391, 0.3815299868583679, 0.9219080209732056, 0.0000019999999949504854, 0.022261999547481537, 0.9978039860725403, -0.06237399950623512, 0.3821389973163605, 0.9231889843940735, 0.00001700000029813964, 0.008317999541759491, 0.9991790056228638, -0.03964800015091896, 0.38228899240493774, 0.9239469766616821, -0.004430000204592943, 0.0008660000166855752, 0.9999139904975891, 0.013048999942839146, 0.7150859832763672, 0.6924030184745789, -0.02822900004684925, 0.7048519849777222, 0.7048519849777222, 0, 0.7055330276489258, 0.7055330276489258, 0, 0.7065179944038391, 0.7065179944038391, 0, 0.7068390250205994, 0.707252025604248, -0.004379999823868275, 0.9257190227508545, 0.3655939996242523, -0.028463000431656837, 0.9210180044174194, 0.38111698627471924, 0.000015999999959603883, 0.9219080209732056, 0.3815299868583679, -0.0000019999999949504854, 0.9231889843940735, 0.3821389973163605, -0.00001700000029813964, 0.9237229824066162, 0.38283199071884155, -0.004399999976158142, 0.9953050017356873, 0.04767199978232384, 0.08423800021409988, 0.9968529939651489, 0.031922999769449234, 0.07255599647760391, 0.9978039860725403, 0.022261999547481537, 0.06237399950623512, 0.9991790056228638, 0.008317999541759491, 0.03964800015091896, 0.9999139904975891, 0.0008660000166855752, -0.013048999942839146, 0.9953050017356873, -0.04767199978232384, -0.08423800021409988, 0.9135000109672546, -0.3951619863510132, -0.02861100062727928, 0.9210190176963806, -0.38111698627471924, -0.000015999999959603883, 0.9968529939651489, -0.031922999769449234, -0.07255599647760391, 0.9219080209732056, -0.3815299868583679, 0.0000019999999949504854, 0.9978039860725403, -0.022261999547481537, -0.06237399950623512, 0.9231889843940735, -0.3821389973163605, 0.00001700000029813964, 0.9991790056228638, -0.008317999541759491, -0.03964800015091896, 0.9239469766616821, -0.38228899240493774, -0.004430000204592943, 0.9999139904975891, -0.0008660000166855752, 0.013048999942839146, 0.6925899982452393, -0.7149369716644287, -0.028262000530958176, 0.7048519849777222, -0.7048519849777222, 0, 0.7055330276489258, -0.7055330276489258, 0, 0.7065179944038391, -0.7065179944038391, 0, 0.707252025604248, -0.7068390250205994, -0.004379999823868275, 0.3656100034713745, -0.9257280230522156, -0.02841299958527088, 0.38111698627471924, -0.9210180044174194, 0.000015999999959603883, 0.3815299868583679, -0.9219080209732056, -0.0000019999999949504854, 0.3821389973163605, -0.9231889843940735, -0.00001700000029813964, 0.38283199071884155, -0.9237229824066162, -0.004399999976158142, 0.04757700115442276, -0.9953129887580872, 0.08418799936771393, 0.031922999769449234, -0.9968529939651489, 0.07255599647760391, 0.022261999547481537, -0.9978039860725403, 0.06237399950623512, 0.008317999541759491, -0.9991790056228638, 0.03964800015091896, 0.0008660000166855752, -0.9999139904975891, -0.013048999942839146, -0.047710999846458435, -0.9953050017356873, -0.08420699834823608, -0.39544400572776794, -0.9133660197257996, -0.028490999713540077, -0.38111698627471924, -0.9210190176963806, -0.000015999999959603883, -0.031922999769449234, -0.9968529939651489, -0.07255599647760391, -0.3815299868583679, -0.9219080209732056, 0.0000019999999949504854, -0.022261999547481537, -0.9978039860725403, -0.06237399950623512, -0.3821389973163605, -0.9231889843940735, 0.00001700000029813964, -0.008317999541759491, -0.9991790056228638, -0.03964800015091896, -0.38228899240493774, -0.9239469766616821, -0.004430000204592943, -0.0008660000166855752, -0.9999139904975891, 0.013048999942839146, -0.7150859832763672, -0.6924030184745789, -0.02822900004684925, -0.7048519849777222, -0.7048519849777222, 0, -0.7055330276489258, -0.7055330276489258, 0, -0.7065179944038391, -0.7065179944038391, 0, -0.7068390250205994, -0.707252025604248, -0.004379999823868275, -0.9257190227508545, -0.3655939996242523, -0.028463000431656837, -0.9210180044174194, -0.38111698627471924, 0.000015999999959603883, -0.9219080209732056, -0.3815299868583679, -0.0000019999999949504854, -0.9231889843940735, -0.3821389973163605, -0.00001700000029813964, -0.9237229824066162, -0.38283199071884155, -0.004399999976158142, -0.9953050017356873, -0.04767199978232384, 0.08423800021409988, -0.9968529939651489, -0.031922999769449234, 0.07255599647760391, -0.9978039860725403, -0.022261999547481537, 0.06237399950623512, -0.9991790056228638, -0.008317999541759491, 0.03964800015091896, -0.9999139904975891, -0.0008660000166855752, -0.013048999942839146, -0.9953050017356873, 0.04767199978232384, -0.08423800021409988, -0.9133660197257996, 0.39544400572776794, -0.028490999713540077, -0.9210190176963806, 0.38111698627471924, -0.000015999999959603883, -0.9968529939651489, 0.031922999769449234, -0.07255599647760391, -0.9219080209732056, 0.3815299868583679, 0.0000019999999949504854, -0.9978039860725403, 0.022261999547481537, -0.06237399950623512, -0.9231889843940735, 0.3821389973163605, 0.00001700000029813964, -0.9991790056228638, 0.008317999541759491, -0.03964800015091896, -0.9239469766616821, 0.38228899240493774, -0.004430000204592943, -0.9999139904975891, 0.0008660000166855752, 0.013048999942839146, -0.6924030184745789, 0.7150859832763672, -0.02822900004684925, -0.7048519849777222, 0.7048519849777222, 0, -0.7055330276489258, 0.7055330276489258, 0, -0.7065179944038391, 0.7065179944038391, 0, -0.707252025604248, 0.7068390250205994, -0.004379999823868275, -0.3655939996242523, 0.9257190227508545, -0.028463000431656837, -0.38111698627471924, 0.9210180044174194, 0.000015999999959603883, -0.3815299868583679, 0.9219080209732056, -0.0000019999999949504854, -0.3821389973163605, 0.9231889843940735, -0.00001700000029813964, -0.38283199071884155, 0.9237229824066162, -0.004399999976158142, -0.04767199978232384, 0.9953050017356873, 0.08423800021409988, -0.031922999769449234, 0.9968529939651489, 0.07255599647760391, -0.022261999547481537, 0.9978039860725403, 0.06237399950623512, -0.008317999541759491, 0.9991790056228638, 0.03964800015091896, -0.0008660000166855752, 0.9999139904975891, -0.013048999942839146, 0.0008660000166855752, 0.9999139904975891, 0.013048999942839146, 0.38283199071884155, 0.9237229824066162, 0.004399999976158142, 0.38101500272750854, 0.9204739928245544, -0.00003899999865097925, 0.03731299936771393, 0.9963229894638062, 0.07712399959564209, 0.37877199053764343, 0.9154880046844482, 0.00008399999933317304, 0.09151100367307663, 0.9910060167312622, 0.097632996737957, 0.378387987613678, 0.9145749807357788, 0.00009999999747378752, 0.10134600102901459, 0.9900450110435486, 0.09767600148916245, 0.356795996427536, 0.9266510009765625, -0.03188199922442436, 0.07246600091457367, 0.9928709864616394, 0.09463199973106384, 0.707252025604248, 0.7068390250205994, 0.004379999823868275, 0.7044739723205566, 0.7044739723205566, 0, 0.7006790041923523, 0.7006790041923523, 0, 0.6999930143356323, 0.6999930143356323, 0, 0.6847820281982422, 0.7192310094833374, -0.03167999908328056, 0.9239469766616821, 0.38228899240493774, 0.004430000204592943, 0.9204739928245544, 0.38101500272750854, 0.00003899999865097925, 0.9154880046844482, 0.37877199053764343, -0.00008399999933317304, 0.9145749807357788, 0.378387987613678, -0.00009999999747378752, 0.9078760147094727, 0.40216198563575745, -0.03206299990415573, 0.9999139904975891, 0.0008660000166855752, -0.013048999942839146, 0.9963229894638062, 0.03731299936771393, -0.07712399959564209, 0.9910060167312622, 0.09151100367307663, -0.097632996737957, 0.9900450110435486, 0.10134600102901459, -0.09767600148916245, 0.9928709864616394, 0.07246600091457367, -0.09463199973106384, 0.9999139904975891, -0.0008660000166855752, 0.013048999942839146, 0.9237229824066162, -0.38283199071884155, 0.004399999976158142, 0.9204739928245544, -0.38101500272750854, -0.00003899999865097925, 0.9963229894638062, -0.03731299936771393, 0.07712399959564209, 0.9154880046844482, -0.37877199053764343, 0.00008399999933317304, 0.9910060167312622, -0.09151100367307663, 0.097632996737957, 0.9145749807357788, -0.378387987613678, 0.00009999999747378752, 0.9900450110435486, -0.10134600102901459, 0.09767600148916245, 0.9266510009765625, -0.356795996427536, -0.03188199922442436, 0.9928709864616394, -0.07246600091457367, 0.09463199973106384, 0.7068390250205994, -0.707252025604248, 0.004379999823868275, 0.7044739723205566, -0.7044739723205566, 0, 0.7006790041923523, -0.7006790041923523, 0, 0.6999930143356323, -0.6999930143356323, 0, 0.7192310094833374, -0.6847820281982422, -0.03167999908328056, 0.38228899240493774, -0.9239469766616821, 0.004430000204592943, 0.38101500272750854, -0.9204739928245544, 0.00003899999865097925, 0.37877199053764343, -0.9154880046844482, -0.00008399999933317304, 0.378387987613678, -0.9145749807357788, -0.00009999999747378752, 0.40216198563575745, -0.9078760147094727, -0.03206299990415573, 0.0008660000166855752, -0.9999139904975891, -0.013048999942839146, 0.03731299936771393, -0.9963229894638062, -0.07712399959564209, 0.09151100367307663, -0.9910060167312622, -0.097632996737957, 0.10134600102901459, -0.9900450110435486, -0.09767600148916245, 0.07246600091457367, -0.9928709864616394, -0.09463199973106384, -0.0008660000166855752, -0.9999139904975891, 0.013048999942839146, -0.38283199071884155, -0.9237229824066162, 0.004399999976158142, -0.38101500272750854, -0.9204739928245544, -0.00003899999865097925, -0.03731299936771393, -0.9963229894638062, 0.07712399959564209, -0.37877199053764343, -0.9154880046844482, 0.00008399999933317304, -0.09151100367307663, -0.9910060167312622, 0.097632996737957, -0.378387987613678, -0.9145749807357788, 0.00009999999747378752, -0.10134600102901459, -0.9900450110435486, 0.09767600148916245, -0.356795996427536, -0.9266510009765625, -0.03188199922442436, -0.07246600091457367, -0.9928709864616394, 0.09463199973106384, -0.707252025604248, -0.7068390250205994, 0.004379999823868275, -0.7044739723205566, -0.7044739723205566, 0, -0.7006790041923523, -0.7006790041923523, 0, -0.6999930143356323, -0.6999930143356323, 0, -0.6847820281982422, -0.7192310094833374, -0.03167999908328056, -0.9239469766616821, -0.38228899240493774, 0.004430000204592943, -0.9204739928245544, -0.38101500272750854, 0.00003899999865097925, -0.9154880046844482, -0.37877199053764343, -0.00008399999933317304, -0.9145749807357788, -0.378387987613678, -0.00009999999747378752, -0.9078760147094727, -0.40216198563575745, -0.03206299990415573, -0.9999139904975891, -0.0008660000166855752, -0.013048999942839146, -0.9963229894638062, -0.03731299936771393, -0.07712399959564209, -0.9910060167312622, -0.09151100367307663, -0.097632996737957, -0.9900450110435486, -0.10134600102901459, -0.09767600148916245, -0.9928709864616394, -0.07246600091457367, -0.09463199973106384, -0.9999139904975891, 0.0008660000166855752, 0.013048999942839146, -0.9237229824066162, 0.38283199071884155, 0.004399999976158142, -0.9204739928245544, 0.38101500272750854, -0.00003899999865097925, -0.9963229894638062, 0.03731299936771393, 0.07712399959564209, -0.9154880046844482, 0.37877199053764343, 0.00008399999933317304, -0.9910060167312622, 0.09151100367307663, 0.097632996737957, -0.9145749807357788, 0.378387987613678, 0.00009999999747378752, -0.9900450110435486, 0.10134600102901459, 0.09767600148916245, -0.9266510009765625, 0.356795996427536, -0.03188199922442436, -0.9928709864616394, 0.07246600091457367, 0.09463199973106384, -0.7068390250205994, 0.707252025604248, 0.004379999823868275, -0.7044739723205566, 0.7044739723205566, 0, -0.7006790041923523, 0.7006790041923523, 0, -0.6999930143356323, 0.6999930143356323, 0, -0.7192310094833374, 0.6847820281982422, -0.03167999908328056, -0.38228899240493774, 0.9239469766616821, 0.004430000204592943, -0.38101500272750854, 0.9204739928245544, 0.00003899999865097925, -0.37877199053764343, 0.9154880046844482, -0.00008399999933317304, -0.378387987613678, 0.9145749807357788, -0.00009999999747378752, -0.40216198563575745, 0.9078760147094727, -0.03206299990415573, -0.0008660000166855752, 0.9999139904975891, -0.013048999942839146, -0.03731299936771393, 0.9963229894638062, -0.07712399959564209, -0.09151100367307663, 0.9910060167312622, -0.097632996737957, -0.10134600102901459, 0.9900450110435486, -0.09767600148916245, -0.07246600091457367, 0.9928709864616394, -0.09463199973106384, 0.07246600091457367, 0.9928709864616394, 0.09463199973106384, 0.40216198563575745, 0.9078760147094727, 0.03206299990415573, 0.37766799330711365, 0.912958025932312, 0.00018099999579135329, 0.11919300258159637, 0.9883019924163818, 0.09514500200748444, 0.37516000866889954, 0.906607985496521, 0.00016799999866634607, 0.187733992934227, 0.9816380143165588, 0.03381900116801262, 0.2823430001735687, 0.767549991607666, -0.1682250052690506, 0.12883399426937103, 0.6540690064430237, -0.32698601484298706, 0.06457000225782394, 0.32701900601387024, -0.6666669845581055, 0, 0, -1, 0.7192320227622986, 0.6847820281982422, 0.03167999908328056, 0.6987630128860474, 0.6987630128860474, 0, 0.694034993648529, 0.694034993648529, 0, 0.5551990270614624, 0.6008960008621216, -0.16825300455093384, 0.1854030042886734, 0.27701398730278015, -0.6666669845581055, 0.9266499876976013, 0.3567950129508972, 0.03188199922442436, 0.912958025932312, 0.37766799330711365, -0.00018099999579135329, 0.906607985496521, 0.37516000866889954, -0.00016799999866634607, 0.742605984210968, 0.3426159918308258, -0.1683180034160614, 0.27701398730278015, 0.1854030042886734, -0.6666669845581055, 0.9928709864616394, 0.07246600091457367, -0.09463199973106384, 0.9883019924163818, 0.11919300258159637, -0.09514500200748444, 0.9816370010375977, 0.187733992934227, -0.03381900116801262, 0.9811030030250549, 0.19325199723243713, -0.009519999846816063, 0.49052900075912476, 0.0968559980392456, -0.5, 0.9928709864616394, -0.07246600091457367, 0.09463199973106384, 0.9078760147094727, -0.40216198563575745, 0.03206299990415573, 0.912958025932312, -0.37766799330711365, 0.00018099999579135329, 0.9883019924163818, -0.11919300258159637, 0.09514500200748444, 0.906607985496521, -0.37516000866889954, 0.00016799999866634607, 0.9816380143165588, -0.187733992934227, 0.03381900116801262, 0.767549991607666, -0.2823430001735687, -0.1682250052690506, 0.6540690064430237, -0.12883399426937103, -0.32698601484298706, 0.32701900601387024, -0.06457000225782394, -0.6666669845581055, 0, 0, -1, 0.6847820281982422, -0.7192320227622986, 0.03167999908328056, 0.6987630128860474, -0.6987630128860474, 0, 0.694034993648529, -0.694034993648529, 0, 0.6008960008621216, -0.5551990270614624, -0.16825300455093384, 0.27701398730278015, -0.1854030042886734, -0.6666669845581055, 0.3567950129508972, -0.9266499876976013, 0.03188199922442436, 0.37766799330711365, -0.912958025932312, -0.00018099999579135329, 0.37516000866889954, -0.906607985496521, -0.00016799999866634607, 0.3426159918308258, -0.742605984210968, -0.1683180034160614, 0.1854030042886734, -0.27701398730278015, -0.6666669845581055, 0.07246600091457367, -0.9928709864616394, -0.09463199973106384, 0.11919300258159637, -0.9883019924163818, -0.09514500200748444, 0.187733992934227, -0.9816370010375977, -0.03381900116801262, 0.19325199723243713, -0.9811030030250549, -0.009519999846816063, 0.0968559980392456, -0.49052900075912476, -0.5, -0.07246600091457367, -0.9928709864616394, 0.09463199973106384, -0.40216198563575745, -0.9078760147094727, 0.03206299990415573, -0.37766799330711365, -0.912958025932312, 0.00018099999579135329, -0.11919300258159637, -0.9883019924163818, 0.09514500200748444, -0.37516000866889954, -0.906607985496521, 0.00016799999866634607, -0.187733992934227, -0.9816380143165588, 0.03381900116801262, -0.2823430001735687, -0.767549991607666, -0.1682250052690506, -0.12883399426937103, -0.6540690064430237, -0.32698601484298706, -0.06457000225782394, -0.32701900601387024, -0.6666669845581055, 0, 0, -1, -0.7192320227622986, -0.6847820281982422, 0.03167999908328056, -0.6987630128860474, -0.6987630128860474, 0, -0.694034993648529, -0.694034993648529, 0, -0.5551990270614624, -0.6008960008621216, -0.16825300455093384, -0.1854030042886734, -0.27701398730278015, -0.6666669845581055, -0.9266499876976013, -0.3567950129508972, 0.03188199922442436, -0.912958025932312, -0.37766799330711365, -0.00018099999579135329, -0.906607985496521, -0.37516000866889954, -0.00016799999866634607, -0.742605984210968, -0.3426159918308258, -0.1683180034160614, -0.27701398730278015, -0.1854030042886734, -0.6666669845581055, -0.9928709864616394, -0.07246600091457367, -0.09463199973106384, -0.9883019924163818, -0.11919300258159637, -0.09514500200748444, -0.9816370010375977, -0.187733992934227, -0.03381900116801262, -0.9811030030250549, -0.19325199723243713, -0.009519999846816063, -0.49052900075912476, -0.0968559980392456, -0.5, -0.9928709864616394, 0.07246600091457367, 0.09463199973106384, -0.9078760147094727, 0.40216198563575745, 0.03206299990415573, -0.912958025932312, 0.37766799330711365, 0.00018099999579135329, -0.9883019924163818, 0.11919300258159637, 0.09514500200748444, -0.906607985496521, 0.37516000866889954, 0.00016799999866634607, -0.9816380143165588, 0.187733992934227, 0.03381900116801262, -0.767549991607666, 0.2823430001735687, -0.1682250052690506, -0.6540690064430237, 0.12883399426937103, -0.32698601484298706, -0.32701900601387024, 0.06457000225782394, -0.6666669845581055, 0, 0, -1, -0.6847820281982422, 0.7192320227622986, 0.03167999908328056, -0.6987630128860474, 0.6987630128860474, 0, -0.694034993648529, 0.694034993648529, 0, -0.6008960008621216, 0.5551990270614624, -0.16825300455093384, -0.27701398730278015, 0.1854030042886734, -0.6666669845581055, -0.3567950129508972, 0.9266499876976013, 0.03188199922442436, -0.37766799330711365, 0.912958025932312, -0.00018099999579135329, -0.37516000866889954, 0.906607985496521, -0.00016799999866634607, -0.3426159918308258, 0.742605984210968, -0.1683180034160614, -0.1854030042886734, 0.27701398730278015, -0.6666669845581055, -0.07246600091457367, 0.9928709864616394, -0.09463199973106384, -0.11919300258159637, 0.9883019924163818, -0.09514500200748444, -0.187733992934227, 0.9816370010375977, -0.03381900116801262, -0.19325199723243713, 0.9811030030250549, -0.009519999846816063, -0.0968559980392456, 0.49052900075912476, -0.5, -0.006597999949008226, 0.9961680173873901, 0.0001630000042496249, -0.043907999992370605, 0.779125988483429, -0.55936598777771, 0.23287899792194366, 0.79271000623703, -0.506534993648529, 0.11139900237321854, 0.9923329949378967, 0.0053449999541044235, 0.4521920084953308, 0.7370989918708801, -0.42180201411247253, 0.17797799408435822, 0.9827970266342163, 0.036841001361608505, 0.6075379848480225, 0.7066869735717773, -0.270797997713089, 0.11894699931144714, 0.9864829778671265, 0.10517799854278564, 0.6583719849586487, 0.7438470125198364, -0.06727500259876251, 0.0010629999451339245, 0.99891597032547, 0.04653400182723999, -0.1622990071773529, -0.14869500696659088, -0.9069569706916809, 0.3020159900188446, -0.014301000162959099, -0.8847119808197021, 0.7048640251159668, -0.042514998465776443, -0.6788020133972168, 0.8948519825935364, -0.11078000068664551, -0.38824599981307983, 0.9622920155525208, -0.09367900341749191, -0.14349600672721863, -0.12511900067329407, -0.8479049801826477, -0.4783349931240082, 0.11315400153398514, -0.8153669834136963, -0.5167160034179688, 0.3956319987773895, -0.7910019755363464, -0.4345270097255707, 0.5244609713554382, -0.8012329936027527, -0.2643829882144928, 0.571465015411377, -0.7902160286903381, -0.12332800030708313, -0.0943560004234314, -0.9955379962921143, -0.0010989999864250422, 0.012040999718010426, -0.9965500235557556, 0, 0.09501499682664871, -0.9936969876289368, 0.02440500073134899, 0.03737499937415123, -0.9978089928627014, 0.035909999161958694, -0.0008800000068731606, -0.9973530173301697, -0.04031199961900711, 0.007164000067859888, -0.9961649775505066, -0.00002700000004551839, 0.043988000601530075, -0.8330309987068176, 0.4691329896450043, -0.2334270030260086, -0.7983189821243286, 0.49840399622917175, -0.10737399756908417, -0.9927549958229065, -0.007029999978840351, -0.45147499442100525, -0.7576299905776978, 0.39375001192092896, -0.15364399552345276, -0.9863160252571106, -0.048294998705387115, -0.5575600266456604, -0.7753210067749023, 0.2001740038394928, -0.07242999970912933, -0.9923030138015747, -0.08845999836921692, -0.5877019762992859, -0.8041930198669434, 0.04768599942326546, 0.0005830000154674053, -0.9997940063476562, -0.020301999524235725, 0.13663700222969055, -0.14665700495243073, 0.8966140151023865, -0.3045389950275421, -0.012237999588251114, 0.8833180069923401, -0.7020289897918701, -0.033987998962402344, 0.6724730134010315, -0.8890330195426941, -0.09636799991130829, 0.37605398893356323, -0.9668099880218506, -0.08601800352334976, 0.1358419954776764, 0.12022499740123749, 0.7918559908866882, 0.5693140029907227, -0.11313500255346298, 0.8111780285835266, 0.5236610174179077, -0.39790698885917664, 0.7734419703483582, 0.45853298902511597, -0.5793390274047852, 0.7346490025520325, 0.32973799109458923, -0.6447499990463257, 0.7340419888496399, 0.12459299713373184, 0.09378799796104431, 0.9955919981002808, 0.000944000028539449, -0.01607999950647354, 0.9964879751205444, 0.00035600000410340726, -0.11933200061321259, 0.9912199974060059, -0.01737299934029579, -0.08618299663066864, 0.9940080046653748, -0.053598999977111816, -0.004110999871045351, 0.9980229735374451, 0.015703000128269196, 0.010142000392079353, 0.9933879971504211, 0.10034400224685669, 0.6597890257835388, 0.7114480137825012, 0.12964099645614624, 0.5634239912033081, 0.7594000101089478, 0.289902001619339, -0.021227000281214714, 0.9976930022239685, 0.05189099907875061, 0.3972559869289398, 0.7709670066833496, 0.45872700214385986, -0.05054600164294243, 0.9957669973373413, 0.060869000852108, 0.11805199831724167, 0.7611619830131531, 0.5692800283432007, -0.11414600163698196, 0.9869359731674194, 0.08862999826669693, -0.0012870000209659338, 0.7195389866828918, 0.6293820142745972, -0.18971200287342072, 0.9752820134162903, 0.11328700184822083, 0.9685969948768616, -0.08966200053691864, 0.13331100344657898, 0.8902140259742737, -0.051961999386548996, 0.39323100447654724, 0.6728280186653137, -0.050324998795986176, 0.6965069770812988, 0.25133201479911804, -0.04306900128722191, 0.9169719815254211, -0.19813700020313263, -0.2512879967689514, 0.9046909809112549, 0.5937719941139221, -0.8024669885635376, 0.03307799994945526, 0.5571249723434448, -0.7907459735870361, 0.2022089958190918, 0.4313510060310364, -0.8083119988441467, 0.37996000051498413, 0.19395600259304047, -0.8197799921035767, 0.5133119821548462, -0.1517219990491867, -0.8084930181503296, 0.5055829882621765, 0.0035200000274926424, -0.9997940063476562, 0.019979000091552734, 0.01159599982202053, -0.9981369972229004, -0.02326199971139431, 0.01310999970883131, -0.9988970160484314, -0.008480999618768692, -0.02485400065779686, -0.9978809952735901, 0.021263999864459038, -0.11335399746894836, -0.9881970286369324, 0.06441199779510498, -0.0035459999926388264, -0.9954169988632202, -0.07682599872350693, -0.5816869735717773, -0.7760900259017944, -0.13957500457763672, -0.5260769724845886, -0.790789008140564, -0.2781960070133209, 0.017288999632000923, -0.9983699917793274, -0.03728000074625015, -0.36800798773765564, -0.7982890009880066, -0.4405499994754791, 0.03743100166320801, -0.9973520040512085, -0.03640099987387657, -0.09636899828910828, -0.7829139828681946, -0.5500450134277344, 0.10426300019025803, -0.9894949793815613, -0.06746900081634521, 0.10083399713039398, -0.8161320090293884, -0.48112401366233826, 0.18510299921035767, -0.9776470065116882, -0.09971100091934204, -0.9615049958229065, -0.08203399926424026, -0.14958199858665466, -0.8876789808273315, -0.04622500017285347, -0.39955899119377136, -0.6675580143928528, -0.03723999857902527, -0.7007560133934021, -0.245511993765831, -0.03216199949383736, -0.9151920080184937, 0.15477199852466583, -0.24929499626159668, -0.8975690007209778, -0.6700729727745056, 0.7402250170707703, -0.01942499913275242, -0.5923460125923157, 0.7624830007553101, -0.21566900610923767, -0.45611900091171265, 0.7868310213088989, -0.39906400442123413, -0.21001900732517242, 0.8031420111656189, -0.5333020091056824, 0.05119999870657921, 0.7096909880638123, -0.6591699719429016, -0.014175999909639359, 0.9989240169525146, -0.04416000097990036, -0.0065449997782707214, 0.9983869791030884, 0.008813999593257904, 0.0023960000835359097, 0.9989259839057922, -0.016711000353097916, 0.03813000023365021, 0.9969249963760376, -0.04171599820256233, 0.11744900047779083, 0.986670970916748, -0.0799890011548996, -0.02072799950838089, -0.997963011264801, 0.0017740000039339066, 0.10236400365829468, -0.695684015750885, -0.6961740255355835, 0.28174999356269836, -0.7065439820289612, -0.6379269957542419, -0.027713999152183533, -0.9983959794044495, -0.016395000740885735, 0.4621469974517822, -0.7501789927482605, -0.43765199184417725, -0.014942999929189682, -0.9960020184516907, -0.04751100018620491, 0.6121799945831299, -0.7355859875679016, -0.1658719927072525, 0.08200599998235703, -0.9833409786224365, 0.11102399975061417, 0.7232419848442078, -0.6012910008430481, -0.14595800638198853, 0.32238098978996277, -0.9036369919776917, 0.28197699785232544, 0.1188960000872612, 0.09661199897527695, -0.9692260026931763, 0.3230240046977997, 0.06791900098323822, -0.9069269895553589, 0.6287810206413269, 0.00962899997830391, -0.711097002029419, 0.8952469825744629, -0.060169998556375504, -0.3366979956626892, 0.9689210057258606, -0.04508800059556961, -0.13095800578594208, 0.06500200182199478, 0.7708680033683777, -0.6083509922027588, 0.1816529929637909, 0.7457069754600525, -0.593995988368988, 0.37600401043891907, 0.7467949986457825, -0.4776870012283325, 0.6288849711418152, 0.7020969986915588, -0.27160701155662537, 0.8230010271072388, 0.5295370221138, -0.09450399875640869, -0.12820099294185638, 0.9899809956550598, -0.05917999893426895, -0.11097600311040878, 0.9872509837150574, -0.09937400370836258, -0.06767299771308899, 0.9865689873695374, -0.1427209973335266, -0.0003349999897181988, 0.9967420101165771, 0.025443999096751213, 0.29019099473953247, 0.9243509769439697, 0.1957239955663681, 0.07294999808073044, 0.9949049949645996, 0.03147900104522705, -0.04948300123214722, 0.7695090174674988, 0.6163870096206665, -0.24193400144577026, 0.7750219702720642, 0.5679330229759216, 0.05620399862527847, 0.9959489703178406, 0.052143000066280365, -0.4294399917125702, 0.779321014881134, 0.41615501046180725, 0.023887999355793, 0.9943940043449402, 0.07553800195455551, -0.6655910015106201, 0.6939520239830017, 0.20106400549411774, -0.09678799659013748, 0.9791589975357056, -0.12869000434875488, -0.7716730237007141, 0.5443729758262634, 0.1793539971113205, -0.417836993932724, 0.8721759915351868, -0.2544029951095581, -0.09499499946832657, 0.08934500068426132, 0.9787889719009399, -0.3299880027770996, 0.06701900064945221, 0.9273520112037659, -0.6511250138282776, 0.023523999378085136, 0.7280719876289368, -0.9116759896278381, -0.033263999968767166, 0.34162598848342896, -0.9896330237388611, -0.013496000319719315, 0.07834099978208542, -0.07044100016355515, -0.6954740285873413, 0.7080140113830566, -0.21969600021839142, -0.6959800124168396, 0.6642320156097412, -0.4075010120868683, -0.7370589971542358, 0.5047789812088013, -0.5866039991378784, -0.7473030090332031, 0.24636299908161163, -0.799036979675293, -0.5617390275001526, 0.05794600024819374, 0.07605399936437607, -0.9967970252037048, 0.02472200058400631, 0.08756300061941147, -0.9926980137825012, 0.05929899960756302, 0.07250799983739853, -0.9901790022850037, 0.11122000217437744, 0.015556000173091888, -0.9970260262489319, -0.011235999874770641, -0.194814994931221, -0.9439409971237183, -0.22127500176429749, 0.3417310118675232, -0.8896859884262085, 0.3012309968471527, 0.8375009894371033, -0.4931910037994385, 0.05739299952983856, 0.8273029923439026, -0.4684619903564453, -0.05539099872112274, 0.5311300158500671, -0.8121910095214844, 0.24026300013065338, 0.8069959878921509, -0.47689300775527954, 0.002638000063598156, 0.644743025302887, -0.7642210125923157, -0.015455000102519989, 0.8856800198554993, -0.4464530050754547, 0.047488000243902206, -0.011536000296473503, -0.999845027923584, -0.0008730000117793679, 0.7597830295562744, -0.6229599714279175, 0.026636000722646713, 0.321245014667511, -0.8855000138282776, 0.3356960117816925, 0.998091995716095, -0.005673000123351812, 0.025262000039219856, 0.9941530227661133, 0.046904999762773514, -0.00951599981635809, 0.9838590025901794, -0.00041700000292621553, 0.010572000406682491, 0.990556001663208, 0.01886500045657158, 0.04422200098633766, 0.9921990036964417, -0.12290599942207336, 0.011202000081539154, 0.828000009059906, 0.5258169770240784, -0.0846100002527237, 0.8704839944839478, 0.4878079891204834, 0.00635599996894598, 0.7773939967155457, 0.5659670233726501, -0.09634699672460556, 0.8190580010414124, 0.4740380048751831, 0.01190400030463934, 0.9017590284347534, 0.3486430048942566, -0.05601400136947632, 0.41038599610328674, 0.870602011680603, 0.27135801315307617, 0.3019320070743561, 0.8897680044174194, 0.34101900458335876, 0.13912299275398254, 0.9423390030860901, -0.3042120039463043, 0.6167309880256653, 0.7692840099334717, 0.1667650043964386, 0.5558350086212158, 0.8010749816894531, 0.21867799758911133, -0.4410029947757721, 0.8555399775505066, -0.2693159878253937, -0.8639690279960632, 0.464356005191803, -0.019222000613808632, -0.8705710172653198, 0.4855479896068573, -0.005623999983072281, -0.33969300985336304, 0.8762779831886292, -0.34097298979759216, -0.7608209848403931, 0.5840269923210144, 0.11236599832773209, -0.16763299703598022, 0.9419429898262024, 0.29091599583625793, -0.8260639905929565, 0.47304999828338623, -0.0134699996560812, -0.6006280183792114, 0.7822970151901245, -0.1611420065164566, -0.8495870232582092, 0.4440779983997345, 0.17417700588703156, -0.5251449942588806, 0.8236340284347534, -0.21412399411201477, -0.9991480112075806, 0.0017519999528303742, 0.007890000008046627, -0.9946579933166504, 0.06129400059580803, 0.007796999998390675, -0.9840919971466064, 0.008732999674975872, -0.0001289999927394092, -0.9916059970855713, 0.015207000076770782, -0.04798699915409088, -0.9899899959564209, -0.13816699385643005, -0.019433999434113503, -0.7927820086479187, -0.5669599771499634, 0.06795799732208252, -0.8363490104675293, -0.4685719907283783, 0.048955000936985016, -0.8138830065727234, -0.4743089973926544, 0.0008379999781027436, -0.8869869709014893, -0.4417180120944977, -0.05625399947166443, -0.7898640036582947, -0.5522750020027161, -0.15016800165176392, -0.297340989112854, -0.8998129963874817, -0.3192580044269562, -0.49759799242019653, -0.8317790031433105, -0.24411599338054657, -0.6295620203018188, -0.7765420079231262, 0.01261799968779087, -0.011338000185787678, -0.9998990297317505, -0.008561000227928162, -0.3547320067882538, -0.8679590225219727, -0.3453510105609894, 0.09618999809026718, 0.49066001176834106, -0.5, 0.1851000040769577, 0.27721700072288513, -0.6666669845581055, 0.32566601037979126, 0.76139897108078, -0.18199099600315094, 0.062401000410318375, 0.9939020276069641, -0.09090700000524521, 0.3803209960460663, 0.9214360117912292, -0.00007100000220816582, 0.030918000265955925, 0.9969729781150818, 0.07133600115776062, 0.3804109990596771, 0.9220889806747437, 0.0001630000042496249, 0.02471200004220009, 0.9975799918174744, 0.06498300284147263, 0.35510900616645813, 0.926891028881073, 0.03216100111603737, 0.07657899707555771, 0.9924740195274353, -0.09555599838495255, 0.27721700072288513, 0.1851000040769577, -0.6666669845581055, 0.5929989814758301, 0.5781109929084778, -0.18205299973487854, 0.7048519849777222, 0.7048519849777222, 0, 0.7052720189094543, 0.7054179906845093, -0.00002499999936844688, 0.6835219860076904, 0.7199410200119019, 0.03204600140452385, 0.3271070122718811, 0.06412599980831146, -0.6666669845581055, 0.7694699764251709, 0.3061000108718872, -0.18225300312042236, 0.9214379787445068, 0.38033199310302734, 0.0000670000008540228, 0.9220880270004272, 0.3804430067539215, -0.00016799999866634607, 0.9071130156517029, 0.403003990650177, 0.032437000423669815, 0, 0, -1, 0.6626030206680298, 0.04157499969005585, -0.272724986076355, 0.9969789981842041, 0.03082600049674511, -0.07129299640655518, 0.9975910186767578, 0.024447999894618988, -0.06492199748754501, 0.9925040006637573, 0.07630900293588638, 0.09545700252056122, 0.49066001176834106, -0.09618999809026718, -0.5, 0.27721700072288513, -0.1851000040769577, -0.6666669845581055, 0.76139897108078, -0.32566601037979126, -0.18199099600315094, 0.9939020276069641, -0.062401000410318375, -0.09090700000524521, 0.9214360117912292, -0.3803209960460663, -0.00007100000220816582, 0.9969729781150818, -0.030918000265955925, 0.07133600115776062, 0.9220889806747437, -0.3804109990596771, 0.0001630000042496249, 0.9975799918174744, -0.02471200004220009, 0.06498300284147263, 0.926891028881073, -0.35510900616645813, 0.03216100111603737, 0.9924740195274353, -0.07657899707555771, -0.09555599838495255, 0.1851000040769577, -0.27721700072288513, -0.6666669845581055, 0.5781109929084778, -0.5929989814758301, -0.18205299973487854, 0.7048519849777222, -0.7048519849777222, 0, 0.7054179906845093, -0.7052720189094543, -0.00002499999936844688, 0.7199410200119019, -0.6835219860076904, 0.03204600140452385, 0.06412599980831146, -0.3271070122718811, -0.6666669845581055, 0.3061000108718872, -0.7694699764251709, -0.18225300312042236, 0.38033199310302734, -0.9214379787445068, 0.0000670000008540228, 0.3804430067539215, -0.9220880270004272, -0.00016799999866634607, 0.403003990650177, -0.9071130156517029, 0.032437000423669815, 0, 0, -1, 0.04157499969005585, -0.6626030206680298, -0.272724986076355, 0.03082600049674511, -0.9969789981842041, -0.07129299640655518, 0.024447999894618988, -0.9975910186767578, -0.06492199748754501, 0.07630900293588638, -0.9925040006637573, 0.09545700252056122, -0.09618999809026718, -0.49066001176834106, -0.5, -0.1851000040769577, -0.27721700072288513, -0.6666669845581055, -0.32566601037979126, -0.76139897108078, -0.18199099600315094, -0.062401000410318375, -0.9939020276069641, -0.09090700000524521, -0.3803209960460663, -0.9214360117912292, -0.00007100000220816582, -0.030918000265955925, -0.9969729781150818, 0.07133600115776062, -0.3804109990596771, -0.9220889806747437, 0.0001630000042496249, -0.02471200004220009, -0.9975799918174744, 0.06498300284147263, -0.35510900616645813, -0.926891028881073, 0.03216100111603737, -0.07657899707555771, -0.9924740195274353, -0.09555599838495255, -0.27721700072288513, -0.1851000040769577, -0.6666669845581055, -0.5929989814758301, -0.5781109929084778, -0.18205299973487854, -0.7048519849777222, -0.7048519849777222, 0, -0.7052720189094543, -0.7054179906845093, -0.00002499999936844688, -0.6835219860076904, -0.7199410200119019, 0.03204600140452385, -0.3271070122718811, -0.06412599980831146, -0.6666669845581055, -0.7694699764251709, -0.3061000108718872, -0.18225300312042236, -0.9214379787445068, -0.38033199310302734, 0.0000670000008540228, -0.9220880270004272, -0.3804430067539215, -0.00016799999866634607, -0.9071130156517029, -0.403003990650177, 0.032437000423669815, 0, 0, -1, -0.6626030206680298, -0.04157499969005585, -0.272724986076355, -0.9969789981842041, -0.03082600049674511, -0.07129299640655518, -0.9975910186767578, -0.024447999894618988, -0.06492199748754501, -0.9925040006637573, -0.07630900293588638, 0.09545700252056122, -0.49066001176834106, 0.09618999809026718, -0.5, -0.27721700072288513, 0.1851000040769577, -0.6666669845581055, -0.76139897108078, 0.32566601037979126, -0.18199099600315094, -0.9939020276069641, 0.062401000410318375, -0.09090700000524521, -0.9214360117912292, 0.3803209960460663, -0.00007100000220816582, -0.9969729781150818, 0.030918000265955925, 0.07133600115776062, -0.9220889806747437, 0.3804109990596771, 0.0001630000042496249, -0.9975799918174744, 0.02471200004220009, 0.06498300284147263, -0.926891028881073, 0.35510900616645813, 0.03216100111603737, -0.9924740195274353, 0.07657899707555771, -0.09555599838495255, -0.1851000040769577, 0.27721700072288513, -0.6666669845581055, -0.5781109929084778, 0.5929989814758301, -0.18205299973487854, -0.7048519849777222, 0.7048519849777222, 0, -0.7054179906845093, 0.7052720189094543, -0.00002499999936844688, -0.7199410200119019, 0.6835219860076904, 0.03204600140452385, -0.06412599980831146, 0.3271070122718811, -0.6666669845581055, -0.3061000108718872, 0.7694699764251709, -0.18225300312042236, -0.38033199310302734, 0.9214379787445068, 0.0000670000008540228, -0.3804430067539215, 0.9220880270004272, -0.00016799999866634607, -0.403003990650177, 0.9071130156517029, 0.032437000423669815, 0, 0, -1, -0.04157499969005585, 0.6626030206680298, -0.272724986076355, -0.03082600049674511, 0.9969789981842041, -0.07129299640655518, -0.024447999894618988, 0.9975910186767578, -0.06492199748754501, -0.07630900293588638, 0.9925040006637573, 0.09545700252056122, 0.07657899707555771, 0.9924740195274353, -0.09555599838495255, 0.40307098627090454, 0.9070649743080139, -0.03255299851298332, 0.3753640055656433, 0.9070209860801697, 0.000007000000096013537, 0.18306200206279755, 0.9820899963378906, -0.04457399994134903, 0.3751649856567383, 0.9065750241279602, -0.00007400000322377309, 0.18801499903202057, 0.9816100001335144, -0.03304100036621094, 0.3759070038795471, 0.908607006072998, -0.00026199998683296144, 0.16623400151729584, 0.983722984790802, -0.06822899729013443, 0.33324098587036133, 0.9290030002593994, 0.029803000390529633, 0.14071400463581085, 0.9862040281295776, -0.08718100190162659, 0.7198299765586853, 0.6836559772491455, -0.032017000019550323, 0.6943539977073669, 0.6943539977073669, 0, 0.694034993648529, 0.694034993648529, 0, 0.6955100297927856, 0.6955100297927856, 0, 0.6639170050621033, 0.7306150197982788, 0.029100999236106873, 0.9268649816513062, 0.35523301362991333, -0.03203999996185303, 0.9070209860801697, 0.3753649890422821, -0.000007000000096013537, 0.9065750241279602, 0.3751649856567383, 0.00007300000288523734, 0.908607006072998, 0.3759070038795471, 0.00026199998683296144, 0.8926259875297546, 0.4211460053920746, 0.028991999104619026, 0.9924740195274353, 0.07646500319242477, 0.09565100073814392, 0.9820899963378906, 0.18306200206279755, 0.04457399994134903, 0.9816100001335144, 0.18801499903202057, 0.03304100036621094, 0.983722984790802, 0.16623400151729584, 0.06822899729013443, 0.9862040281295776, 0.14071400463581085, 0.08718100190162659, 0.9924740195274353, -0.07657899707555771, -0.09555599838495255, 0.9070649743080139, -0.40307098627090454, -0.03255299851298332, 0.9070209860801697, -0.3753640055656433, 0.000007000000096013537, 0.9820899963378906, -0.18306200206279755, -0.04457399994134903, 0.9065750241279602, -0.3751649856567383, -0.00007400000322377309, 0.9816100001335144, -0.18801499903202057, -0.03304100036621094, 0.908607006072998, -0.3759070038795471, -0.00026199998683296144, 0.983722984790802, -0.16623400151729584, -0.06822899729013443, 0.9290030002593994, -0.33324098587036133, 0.029803000390529633, 0.9862040281295776, -0.14071400463581085, -0.08718100190162659, 0.6836559772491455, -0.7198299765586853, -0.032017000019550323, 0.6943539977073669, -0.6943539977073669, 0, 0.694034993648529, -0.694034993648529, 0, 0.6955100297927856, -0.6955100297927856, 0, 0.7306150197982788, -0.6639170050621033, 0.029100999236106873, 0.35523301362991333, -0.9268649816513062, -0.03203999996185303, 0.3753649890422821, -0.9070209860801697, -0.000007000000096013537, 0.3751649856567383, -0.9065750241279602, 0.00007300000288523734, 0.3759070038795471, -0.908607006072998, 0.00026199998683296144, 0.4211460053920746, -0.8926259875297546, 0.028991999104619026, 0.07646500319242477, -0.9924740195274353, 0.09565100073814392, 0.18306200206279755, -0.9820899963378906, 0.04457399994134903, 0.18801499903202057, -0.9816100001335144, 0.03304100036621094, 0.16623400151729584, -0.983722984790802, 0.06822899729013443, 0.14071400463581085, -0.9862040281295776, 0.08718100190162659, -0.07657899707555771, -0.9924740195274353, -0.09555599838495255, -0.40307098627090454, -0.9070649743080139, -0.03255299851298332, -0.3753640055656433, -0.9070209860801697, 0.000007000000096013537, -0.18306200206279755, -0.9820899963378906, -0.04457399994134903, -0.3751649856567383, -0.9065750241279602, -0.00007400000322377309, -0.18801499903202057, -0.9816100001335144, -0.03304100036621094, -0.3759070038795471, -0.908607006072998, -0.00026199998683296144, -0.16623400151729584, -0.983722984790802, -0.06822899729013443, -0.33324098587036133, -0.9290030002593994, 0.029803000390529633, -0.14071400463581085, -0.9862040281295776, -0.08718100190162659, -0.7198299765586853, -0.6836559772491455, -0.032017000019550323, -0.6943539977073669, -0.6943539977073669, 0, -0.694034993648529, -0.694034993648529, 0, -0.6955100297927856, -0.6955100297927856, 0, -0.6639170050621033, -0.7306150197982788, 0.029100999236106873, -0.9268649816513062, -0.35523301362991333, -0.03203999996185303, -0.9070209860801697, -0.3753649890422821, -0.000007000000096013537, -0.9065750241279602, -0.3751649856567383, 0.00007300000288523734, -0.908607006072998, -0.3759070038795471, 0.00026199998683296144, -0.8926259875297546, -0.4211460053920746, 0.028991999104619026, -0.9924740195274353, -0.07646500319242477, 0.09565100073814392, -0.9820899963378906, -0.18306200206279755, 0.04457399994134903, -0.9816100001335144, -0.18801499903202057, 0.03304100036621094, -0.983722984790802, -0.16623400151729584, 0.06822899729013443, -0.9862040281295776, -0.14071400463581085, 0.08718100190162659, -0.9924740195274353, 0.07657899707555771, -0.09555599838495255, -0.9070649743080139, 0.40307098627090454, -0.03255299851298332, -0.9070209860801697, 0.3753640055656433, 0.000007000000096013537, -0.9820899963378906, 0.18306200206279755, -0.04457399994134903, -0.9065750241279602, 0.3751649856567383, -0.00007400000322377309, -0.9816100001335144, 0.18801499903202057, -0.03304100036621094, -0.908607006072998, 0.3759070038795471, -0.00026199998683296144, -0.983722984790802, 0.16623400151729584, -0.06822899729013443, -0.9290030002593994, 0.33324098587036133, 0.029803000390529633, -0.9862040281295776, 0.14071400463581085, -0.08718100190162659, -0.6836559772491455, 0.7198299765586853, -0.032017000019550323, -0.6943539977073669, 0.6943539977073669, 0, -0.694034993648529, 0.694034993648529, 0, -0.6955100297927856, 0.6955100297927856, 0, -0.7306150197982788, 0.6639170050621033, 0.029100999236106873, -0.35523301362991333, 0.9268649816513062, -0.03203999996185303, -0.3753649890422821, 0.9070209860801697, -0.000007000000096013537, -0.3751649856567383, 0.9065750241279602, 0.00007300000288523734, -0.3759070038795471, 0.908607006072998, 0.00026199998683296144, -0.4211460053920746, 0.8926259875297546, 0.028991999104619026, -0.07646500319242477, 0.9924740195274353, 0.09565100073814392, -0.18306200206279755, 0.9820899963378906, 0.04457399994134903, -0.18801499903202057, 0.9816100001335144, 0.03304100036621094, -0.16623400151729584, 0.983722984790802, 0.06822899729013443, -0.14071400463581085, 0.9862040281295776, 0.08718100190162659 ]);\nvar teapotBinormals = new Float32Array([ 0.2554270029067993, -0.05043400079011917, -0.9655119776725769, 0.2302899956703186, -0.11379700154066086, -0.9664459824562073, -0.23653900623321533, 0.09789499640464783, -0.9666780233383179, -0.2551180124282837, 0.05037299916148186, -0.9655969738960266, -0.9201610088348389, 0.38079801201820374, -0.09108299762010574, -0.9770479798316956, 0.1929199993610382, -0.09032399952411652, -0.6762400269508362, 0.2798590064048767, 0.6814529895782471, -0.723800003528595, 0.1429159939289093, 0.6750479936599731, -0.4681990146636963, 0.1581760048866272, 0.869350016117096, -0.4902079999446869, 0.09679199755191803, 0.8662149906158447, 0.16952399909496307, -0.1934960037469864, -0.9663439989089966, -0.18106800317764282, 0.18106800317764282, -0.9666590094566345, -0.7041199803352356, 0.7041199803352356, -0.09181900322437286, -0.5179349780082703, 0.5179349780082703, 0.6807990074157715, -0.37217798829078674, 0.3260670006275177, 0.8690019845962524, 0.08221600204706192, -0.243368998169899, -0.9664430022239685, -0.09789499640464783, 0.23653900623321533, -0.9666780233383179, -0.38079801201820374, 0.9201610088348389, -0.09108199924230576, -0.2798590064048767, 0.6762400269508362, 0.6814540028572083, -0.21894000470638275, 0.44305500388145447, 0.8693490028381348, 0.050822000950574875, -0.2573910057544708, -0.9649699926376343, -0.05021600052714348, 0.25432100892066956, -0.965815007686615, -0.19291600584983826, 0.9770249724388123, -0.09059000015258789, -0.14291299879550934, 0.7237870097160339, 0.6750609874725342, -0.09679199755191803, 0.4902079999446869, 0.8662149906158447, -0.048507001250982285, -0.2576940059661865, -0.965008020401001, -0.15833300352096558, -0.3227809965610504, -0.933135986328125, 0.05656199902296066, 0.13793900609016418, -0.9888240098953247, 0.049150001257658005, 0.2545199990272522, -0.9658179879188538, 0.378387987613678, 0.9173290133476257, -0.12381099909543991, 0.1917950063943863, 0.9772530198097229, -0.09050799906253815, 0.2777239978313446, 0.6716070175170898, 0.6868870258331299, 0.14281700551509857, 0.7238019704818726, 0.6750659942626953, 0.15788200497627258, 0.4674209952354431, 0.8698220252990723, 0.09679199755191803, 0.4902079999446869, 0.8662149906158447, -0.3139069974422455, -0.2657270133495331, -0.9115110039710999, 0.05247500166296959, 0.05178600177168846, -0.9972789883613586, 0.699787974357605, 0.6969379782676697, -0.15676100552082062, 0.511929988861084, 0.5116159915924072, 0.6900550127029419, 0.32515400648117065, 0.37111398577690125, 0.8697980046272278, -0.3181929886341095, -0.09987600147724152, -0.9427499771118164, 0.1552799940109253, 0.06176299974322319, -0.9859380125999451, 0.9187250137329102, 0.3751460015773773, -0.1233299970626831, 0.6724870204925537, 0.2775439918041229, 0.6860979795455933, 0.4424299895763397, 0.21853800117969513, 0.8697689771652222, -0.255948007106781, -0.04464200139045715, -0.9656590223312378, 0.25306200981140137, 0.046362001448869705, -0.9663389921188354, 0.9778940081596375, 0.18800100684165955, -0.09153299778699875, 0.7238150238990784, 0.14205799996852875, 0.675212025642395, 0.49017900228500366, 0.0967010036110878, 0.8662409782409668, -0.25491899251937866, 0.05033399909734726, -0.9656509757041931, -0.2302899956703186, 0.11379700154066086, -0.9664459824562073, 0.23653900623321533, -0.09789499640464783, -0.9666780233383179, 0.252265989780426, -0.04980999976396561, -0.9663749933242798, 0.9201610088348389, -0.38079801201820374, -0.09108299762010574, 0.9769039750099182, -0.19289200007915497, -0.09193000197410583, 0.6762400269508362, -0.2798590064048767, 0.6814529895782471, 0.7236610054969788, -0.14288799464702606, 0.6752020120620728, 0.4681990146636963, -0.1581760048866272, 0.869350016117096, 0.4901660084724426, -0.09678400307893753, 0.8662390112876892, -0.16952399909496307, 0.1934960037469864, -0.9663439989089966, 0.18106800317764282, -0.18106800317764282, -0.9666590094566345, 0.7041199803352356, -0.7041199803352356, -0.09181900322437286, 0.5179349780082703, -0.5179349780082703, 0.6807990074157715, 0.37217798829078674, -0.3260670006275177, 0.8690019845962524, -0.08221600204706192, 0.243368998169899, -0.9664430022239685, 0.09789499640464783, -0.23653900623321533, -0.9666780233383179, 0.38079801201820374, -0.9201610088348389, -0.09108199924230576, 0.2798590064048767, -0.6762400269508362, 0.6814540028572083, 0.21894000470638275, -0.44305500388145447, 0.8693490028381348, -0.05043400079011917, 0.2554270029067993, -0.9655119776725769, 0.05037299916148186, -0.2551180124282837, -0.9655969738960266, 0.1929199993610382, -0.9770479798316956, -0.09032399952411652, 0.1429159939289093, -0.723800003528595, 0.6750479936599731, 0.09679199755191803, -0.4902079999446869, 0.8662149906158447, 0.05043400079011917, 0.2554270029067993, -0.9655119776725769, 0.11379700154066086, 0.2302899956703186, -0.9664459824562073, -0.09789499640464783, -0.23653900623321533, -0.9666780233383179, -0.05037299916148186, -0.2551180124282837, -0.9655969738960266, -0.38079801201820374, -0.9201610088348389, -0.09108299762010574, -0.1929199993610382, -0.9770479798316956, -0.09032399952411652, -0.2798590064048767, -0.6762400269508362, 0.6814529895782471, -0.1429159939289093, -0.723800003528595, 0.6750479936599731, -0.1581760048866272, -0.4681990146636963, 0.869350016117096, -0.09679199755191803, -0.4902079999446869, 0.8662149906158447, 0.1934960037469864, 0.16952399909496307, -0.9663439989089966, -0.18106800317764282, -0.18106800317764282, -0.9666590094566345, -0.7041199803352356, -0.7041199803352356, -0.09181900322437286, -0.5179349780082703, -0.5179349780082703, 0.6807990074157715, -0.3260670006275177, -0.37217798829078674, 0.8690019845962524, 0.243368998169899, 0.08221600204706192, -0.9664430022239685, -0.23653900623321533, -0.09789499640464783, -0.9666780233383179, -0.9201610088348389, -0.38079801201820374, -0.09108199924230576, -0.6762400269508362, -0.2798590064048767, 0.6814540028572083, -0.44305500388145447, -0.21894000470638275, 0.8693490028381348, 0.2554270029067993, 0.05043400079011917, -0.9655119776725769, -0.2551180124282837, -0.05037299916148186, -0.9655969738960266, -0.9770479798316956, -0.1929199993610382, -0.09032399952411652, -0.723800003528595, -0.1429159939289093, 0.6750479936599731, -0.4902079999446869, -0.09679199755191803, 0.8662149906158447, -0.4902079999446869, 0.09679199755191803, 0.8662149906158447, -0.44305500388145447, 0.21893900632858276, 0.8693490028381348, -0.37287598848342896, 0.15431199967861176, 0.9149600267410278, -0.4014579951763153, 0.07926800101995468, 0.9124410152435303, -0.3112579882144928, 0.12881100177764893, 0.9415550231933594, -0.33541300892829895, 0.0662280023097992, 0.939740002155304, -0.19015200436115265, 0.07869099825620651, 0.9785959720611572, -0.20517399907112122, 0.040511999279260635, 0.977886974811554, 0.06301800161600113, -0.021289000287652016, 0.9977849721908569, 0.06623400002717972, -0.013078000396490097, 0.9977179765701294, -0.3260670006275177, 0.37217798829078674, 0.8690019845962524, -0.285739004611969, 0.285739004611969, 0.9147170186042786, -0.23854400217533112, 0.23854400217533112, 0.9413790106773376, -0.14574900269508362, 0.14574900269508362, 0.9785270094871521, 0.05011200159788132, -0.04390300065279007, 0.9977779984474182, -0.1581760048866272, 0.4681999981403351, 0.869350016117096, -0.15431199967861176, 0.37287598848342896, 0.9149600267410278, -0.12881100177764893, 0.3112579882144928, 0.9415550231933594, -0.07869099825620651, 0.19015100598335266, 0.9785959720611572, 0.02946699969470501, -0.05963199958205223, 0.9977849721908569, -0.09679199755191803, 0.4902079999446869, 0.8662149906158447, -0.07926800101995468, 0.4014579951763153, 0.9124410152435303, -0.0662280023097992, 0.33541300892829895, 0.939740002155304, -0.040511999279260635, 0.20517399907112122, 0.977886974811554, 0.013078000396490097, -0.06623400002717972, 0.9977179765701294, 0.09679199755191803, 0.4902079999446869, 0.8662149906158447, 0.21858200430870056, 0.4423219859600067, 0.86981201171875, 0.15431199967861176, 0.37287598848342896, 0.9149600267410278, 0.07926800101995468, 0.4014579951763153, 0.9124410152435303, 0.12881100177764893, 0.3112579882144928, 0.9415550231933594, 0.0662280023097992, 0.33541300892829895, 0.939740002155304, 0.07869099825620651, 0.19015200436115265, 0.9785959720611572, 0.040511999279260635, 0.20517399907112122, 0.977886974811554, -0.021289000287652016, -0.06301800161600113, 0.9977849721908569, -0.013078000396490097, -0.06623400002717972, 0.9977179765701294, 0.3711329996585846, 0.3251489996910095, 0.8697919845581055, 0.285739004611969, 0.285739004611969, 0.9147170186042786, 0.23854400217533112, 0.23854400217533112, 0.9413790106773376, 0.14574900269508362, 0.14574900269508362, 0.9785270094871521, -0.04390300065279007, -0.05011200159788132, 0.9977779984474182, 0.46750199794769287, 0.15794099867343903, 0.8697680234909058, 0.37287598848342896, 0.15431199967861176, 0.9149600267410278, 0.3112579882144928, 0.12881100177764893, 0.9415550231933594, 0.19015100598335266, 0.07869099825620651, 0.9785959720611572, -0.05963199958205223, -0.02946699969470501, 0.9977849721908569, 0.49017900228500366, 0.0967010036110878, 0.8662409782409668, 0.4014579951763153, 0.07926800101995468, 0.9124410152435303, 0.33541300892829895, 0.0662280023097992, 0.939740002155304, 0.20517399907112122, 0.040511999279260635, 0.977886974811554, -0.06623400002717972, -0.013078000396490097, 0.9977179765701294, 0.4901660084724426, -0.09678400307893753, 0.8662390112876892, 0.44305500388145447, -0.21893900632858276, 0.8693490028381348, 0.37287598848342896, -0.15431199967861176, 0.9149600267410278, 0.4014579951763153, -0.07926800101995468, 0.9124410152435303, 0.3112579882144928, -0.12881100177764893, 0.9415550231933594, 0.33541300892829895, -0.0662280023097992, 0.939740002155304, 0.19015200436115265, -0.07869099825620651, 0.9785959720611572, 0.20517399907112122, -0.040511999279260635, 0.977886974811554, -0.06301800161600113, 0.021289000287652016, 0.9977849721908569, -0.06623400002717972, 0.013078000396490097, 0.9977179765701294, 0.3260670006275177, -0.37217798829078674, 0.8690019845962524, 0.285739004611969, -0.285739004611969, 0.9147170186042786, 0.23854400217533112, -0.23854400217533112, 0.9413790106773376, 0.14574900269508362, -0.14574900269508362, 0.9785270094871521, -0.05011200159788132, 0.04390300065279007, 0.9977779984474182, 0.1581760048866272, -0.4681999981403351, 0.869350016117096, 0.15431199967861176, -0.37287598848342896, 0.9149600267410278, 0.12881100177764893, -0.3112579882144928, 0.9415550231933594, 0.07869099825620651, -0.19015100598335266, 0.9785959720611572, -0.02946699969470501, 0.05963199958205223, 0.9977849721908569, 0.09679199755191803, -0.4902079999446869, 0.8662149906158447, 0.07926800101995468, -0.4014579951763153, 0.9124410152435303, 0.0662280023097992, -0.33541300892829895, 0.939740002155304, 0.040511999279260635, -0.20517399907112122, 0.977886974811554, -0.013078000396490097, 0.06623400002717972, 0.9977179765701294, -0.09679199755191803, -0.4902079999446869, 0.8662149906158447, -0.21893900632858276, -0.44305500388145447, 0.8693490028381348, -0.15431199967861176, -0.37287598848342896, 0.9149600267410278, -0.07926800101995468, -0.4014579951763153, 0.9124410152435303, -0.12881100177764893, -0.3112579882144928, 0.9415550231933594, -0.0662280023097992, -0.33541300892829895, 0.939740002155304, -0.07869099825620651, -0.19015200436115265, 0.9785959720611572, -0.040511999279260635, -0.20517399907112122, 0.977886974811554, 0.021289000287652016, 0.06301800161600113, 0.9977849721908569, 0.013078000396490097, 0.06623400002717972, 0.9977179765701294, -0.37217798829078674, -0.3260670006275177, 0.8690019845962524, -0.285739004611969, -0.285739004611969, 0.9147170186042786, -0.23854400217533112, -0.23854400217533112, 0.9413790106773376, -0.14574900269508362, -0.14574900269508362, 0.9785270094871521, 0.04390300065279007, 0.05011200159788132, 0.9977779984474182, -0.4681999981403351, -0.1581760048866272, 0.869350016117096, -0.37287598848342896, -0.15431199967861176, 0.9149600267410278, -0.3112579882144928, -0.12881100177764893, 0.9415550231933594, -0.19015100598335266, -0.07869099825620651, 0.9785959720611572, 0.05963199958205223, 0.02946699969470501, 0.9977849721908569, -0.4902079999446869, -0.09679199755191803, 0.8662149906158447, -0.4014579951763153, -0.07926800101995468, 0.9124410152435303, -0.33541300892829895, -0.0662280023097992, 0.939740002155304, -0.20517399907112122, -0.040511999279260635, 0.977886974811554, 0.06623400002717972, 0.013078000396490097, 0.9977179765701294, 0.06623400002717972, -0.013078000396490097, 0.9977179765701294, 0.05963199958205223, -0.02946699969470501, 0.9977849721908569, 0.40303200483322144, -0.1667889952659607, 0.8998590111732483, 0.4339120090007782, -0.08567699790000916, 0.8968719840049744, 0.6326310038566589, -0.2618109881877899, 0.7288579940795898, 0.6777120232582092, -0.13381600379943848, 0.723048985004425, 0.6661339998245239, -0.27567601203918457, 0.6930140256881714, 0.7128540277481079, -0.14075499773025513, 0.6870430111885071, 0.5777599811553955, -0.19519099593162537, 0.792523980140686, 0.6036490201950073, -0.11919199675321579, 0.7882900238037109, 0.04390300065279007, -0.05011200159788132, 0.9977779984474182, 0.30884799361228943, -0.30884799361228943, 0.8995699882507324, 0.48457300662994385, -0.48457300662994385, 0.7282710075378418, 0.510138988494873, -0.510138988494873, 0.6924710273742676, 0.4591110050678253, -0.40222999453544617, 0.7921029925346375, 0.021289000287652016, -0.06301800161600113, 0.9977849721908569, 0.16678999364376068, -0.40303200483322144, 0.8998590111732483, 0.2618109881877899, -0.6326310038566589, 0.7288579940795898, 0.27567601203918457, -0.6661339998245239, 0.6930140256881714, 0.2701770067214966, -0.546737015247345, 0.7925170063972473, 0.013078000396490097, -0.06623400002717972, 0.9977179765701294, 0.08567599952220917, -0.4339120090007782, 0.8968719840049744, 0.13381600379943848, -0.6777120232582092, 0.723048985004425, 0.14075499773025513, -0.7128540277481079, 0.6870430111885071, 0.11919199675321579, -0.6036490201950073, 0.7882900238037109, -0.013078000396490097, -0.06623400002717972, 0.9977179765701294, -0.02946699969470501, -0.05963199958205223, 0.9977849721908569, -0.1667889952659607, -0.40303200483322144, 0.8998590111732483, -0.08567699790000916, -0.4339120090007782, 0.8968719840049744, -0.2618109881877899, -0.6326310038566589, 0.7288579940795898, -0.13381600379943848, -0.6777120232582092, 0.723048985004425, -0.27567601203918457, -0.6661339998245239, 0.6930140256881714, -0.14075499773025513, -0.7128540277481079, 0.6870430111885071, -0.19519099593162537, -0.5777599811553955, 0.792523980140686, -0.11919199675321579, -0.6036490201950073, 0.7882900238037109, -0.05011200159788132, -0.04390300065279007, 0.9977779984474182, -0.30884799361228943, -0.30884799361228943, 0.8995699882507324, -0.48457300662994385, -0.48457300662994385, 0.7282710075378418, -0.510138988494873, -0.510138988494873, 0.6924710273742676, -0.40222999453544617, -0.4591110050678253, 0.7921029925346375, -0.06301800161600113, -0.021289000287652016, 0.9977849721908569, -0.40303200483322144, -0.16678999364376068, 0.8998590111732483, -0.6326310038566589, -0.2618109881877899, 0.7288579940795898, -0.6661339998245239, -0.27567601203918457, 0.6930140256881714, -0.546737015247345, -0.2701770067214966, 0.7925170063972473, -0.06623400002717972, -0.013078000396490097, 0.9977179765701294, -0.4339120090007782, -0.08567599952220917, 0.8968719840049744, -0.6777120232582092, -0.13381600379943848, 0.723048985004425, -0.7128540277481079, -0.14075499773025513, 0.6870430111885071, -0.6036490201950073, -0.11919199675321579, 0.7882900238037109, -0.06623400002717972, 0.013078000396490097, 0.9977179765701294, -0.05963199958205223, 0.02946699969470501, 0.9977849721908569, -0.40303200483322144, 0.1667889952659607, 0.8998590111732483, -0.4339120090007782, 0.08567699790000916, 0.8968719840049744, -0.6326310038566589, 0.2618109881877899, 0.7288579940795898, -0.6777120232582092, 0.13381600379943848, 0.723048985004425, -0.6661339998245239, 0.27567601203918457, 0.6930140256881714, -0.7128540277481079, 0.14075499773025513, 0.6870430111885071, -0.5777599811553955, 0.19519099593162537, 0.792523980140686, -0.6036490201950073, 0.11919199675321579, 0.7882900238037109, -0.04390300065279007, 0.05011200159788132, 0.9977779984474182, -0.30884799361228943, 0.30884799361228943, 0.8995699882507324, -0.48457300662994385, 0.48457300662994385, 0.7282710075378418, -0.510138988494873, 0.510138988494873, 0.6924710273742676, -0.4591110050678253, 0.40222999453544617, 0.7921029925346375, -0.021289000287652016, 0.06301800161600113, 0.9977849721908569, -0.16678999364376068, 0.40303200483322144, 0.8998590111732483, -0.2618109881877899, 0.6326310038566589, 0.7288579940795898, -0.27567601203918457, 0.6661339998245239, 0.6930140256881714, -0.2701770067214966, 0.546737015247345, 0.7925170063972473, -0.013078000396490097, 0.06623400002717972, 0.9977179765701294, -0.08567599952220917, 0.4339120090007782, 0.8968719840049744, -0.13381600379943848, 0.6777120232582092, 0.723048985004425, -0.14075499773025513, 0.7128540277481079, 0.6870430111885071, -0.11919199675321579, 0.6036490201950073, 0.7882900238037109, 0.013078000396490097, 0.06623400002717972, 0.9977179765701294, 0.02946699969470501, 0.05963199958205223, 0.9977849721908569, 0.1667889952659607, 0.40303200483322144, 0.8998590111732483, 0.08567699790000916, 0.4339120090007782, 0.8968719840049744, 0.2618109881877899, 0.6326310038566589, 0.7288579940795898, 0.13381600379943848, 0.6777120232582092, 0.723048985004425, 0.27567601203918457, 0.6661339998245239, 0.6930140256881714, 0.14075499773025513, 0.7128540277481079, 0.6870430111885071, 0.19519099593162537, 0.5777599811553955, 0.792523980140686, 0.11919199675321579, 0.6036490201950073, 0.7882900238037109, 0.05011200159788132, 0.04390300065279007, 0.9977779984474182, 0.30884799361228943, 0.30884799361228943, 0.8995699882507324, 0.48457300662994385, 0.48457300662994385, 0.7282710075378418, 0.510138988494873, 0.510138988494873, 0.6924710273742676, 0.40222999453544617, 0.4591110050678253, 0.7921029925346375, 0.06301800161600113, 0.021289000287652016, 0.9977849721908569, 0.40303200483322144, 0.16678999364376068, 0.8998590111732483, 0.6326310038566589, 0.2618109881877899, 0.7288579940795898, 0.6661339998245239, 0.27567601203918457, 0.6930140256881714, 0.546737015247345, 0.2701770067214966, 0.7925170063972473, 0.06623400002717972, 0.013078000396490097, 0.9977179765701294, 0.4339120090007782, 0.08567599952220917, 0.8968719840049744, 0.6777120232582092, 0.13381600379943848, 0.723048985004425, 0.7128540277481079, 0.14075499773025513, 0.6870430111885071, 0.6036490201950073, 0.11919199675321579, 0.7882900238037109, 0.6036490201950073, -0.11919199675321579, 0.7882900238037109, 0.546737015247345, -0.2701770067214966, 0.7925170063972473, 0.7223830223083496, -0.2989569902420044, 0.623528003692627, 0.7723940014839172, -0.15251100063323975, 0.616562008857727, 0.9094089865684509, -0.3763520121574402, 0.1770150065422058, 0.9660869836807251, -0.19075599312782288, 0.1740349978208542, 0.9408230185508728, -0.3353259861469269, 0.04907499998807907, 0.9843119978904724, -0.16964000463485718, 0.048493001610040665, 0.9810580015182495, -0.1937119960784912, 0, 0.7071070075035095, -0.7071070075035095, 0, 0.40222999453544617, -0.4591110050678253, 0.7921029925346375, 0.5532029867172241, -0.5532029867172241, 0.622842013835907, 0.6959879994392395, -0.6959879994392395, 0.17663900554180145, 0.7403979897499084, -0.6703829765319824, 0.048958998173475266, 0.8310419917106628, -0.5562090277671814, 0, 0.19519099593162537, -0.5777599811553955, 0.792523980140686, 0.2989560067653656, -0.7223830223083496, 0.623528003692627, 0.3763520121574402, -0.9094089865684509, 0.1770150065422058, 0.4275760054588318, -0.902646005153656, 0.04906899854540825, 0.5562090277671814, -0.8310419917106628, 0, 0.11919199675321579, -0.6036490201950073, 0.7882900238037109, 0.15251100063323975, -0.7723940014839172, 0.616562008857727, 0.19075599312782288, -0.9660869836807251, 0.1740349978208542, 0.19348600506782532, -0.9799140095710754, 0.048277001827955246, 0.1937119960784912, -0.9810580015182495, 0, -0.11919199675321579, -0.6036490201950073, 0.7882900238037109, -0.2701770067214966, -0.546737015247345, 0.7925170063972473, -0.2989569902420044, -0.7223830223083496, 0.623528003692627, -0.15251100063323975, -0.7723940014839172, 0.616562008857727, -0.3763520121574402, -0.9094089865684509, 0.1770150065422058, -0.19075599312782288, -0.9660869836807251, 0.1740349978208542, -0.3353259861469269, -0.9408230185508728, 0.04907499998807907, -0.16964000463485718, -0.9843119978904724, 0.04849399998784065, -0.1937119960784912, -0.9810580015182495, 0, 0.7071070075035095, -0.7071070075035095, 0, -0.4591110050678253, -0.40222999453544617, 0.7921029925346375, -0.5532029867172241, -0.5532029867172241, 0.622842013835907, -0.6959879994392395, -0.6959879994392395, 0.17663900554180145, -0.6703829765319824, -0.7403979897499084, 0.048958998173475266, -0.5562090277671814, -0.8310419917106628, 0, -0.5777599811553955, -0.19519099593162537, 0.792523980140686, -0.7223830223083496, -0.2989560067653656, 0.623528003692627, -0.9094089865684509, -0.3763520121574402, 0.1770150065422058, -0.902646005153656, -0.4275760054588318, 0.04906899854540825, -0.8310419917106628, -0.5562090277671814, 0, -0.6036490201950073, -0.11919199675321579, 0.7882900238037109, -0.7723940014839172, -0.15251100063323975, 0.616562008857727, -0.9660869836807251, -0.19075599312782288, 0.1740349978208542, -0.9799140095710754, -0.19348600506782532, 0.048277001827955246, -0.9810580015182495, -0.1937119960784912, 0, -0.6036490201950073, 0.11919199675321579, 0.7882900238037109, -0.546737015247345, 0.2701770067214966, 0.7925170063972473, -0.7223830223083496, 0.2989569902420044, 0.623528003692627, -0.7723940014839172, 0.15251100063323975, 0.616562008857727, -0.9094089865684509, 0.3763520121574402, 0.1770150065422058, -0.9660869836807251, 0.19075599312782288, 0.1740349978208542, -0.9408230185508728, 0.3353259861469269, 0.04907499998807907, -0.9843119978904724, 0.16964000463485718, 0.04849399998784065, -0.9810580015182495, 0.1937119960784912, 0, 0.7071070075035095, -0.7071070075035095, 0, -0.40222999453544617, 0.4591110050678253, 0.7921029925346375, -0.5532029867172241, 0.5532029867172241, 0.622842013835907, -0.6959879994392395, 0.6959879994392395, 0.17663900554180145, -0.7403979897499084, 0.6703829765319824, 0.048958998173475266, -0.8310419917106628, 0.5562090277671814, 0, -0.19519099593162537, 0.5777599811553955, 0.792523980140686, -0.2989560067653656, 0.7223830223083496, 0.623528003692627, -0.3763520121574402, 0.9094089865684509, 0.1770150065422058, -0.4275760054588318, 0.902646005153656, 0.04906899854540825, -0.5562090277671814, 0.8310419917106628, 0, -0.11919199675321579, 0.6036490201950073, 0.7882900238037109, -0.15251100063323975, 0.7723940014839172, 0.616562008857727, -0.19075599312782288, 0.9660869836807251, 0.1740349978208542, -0.19348600506782532, 0.9799140095710754, 0.048277001827955246, -0.1937119960784912, 0.9810580015182495, 0, 0.11919199675321579, 0.6036490201950073, 0.7882900238037109, 0.2701770067214966, 0.546737015247345, 0.7925170063972473, 0.2989569902420044, 0.7223830223083496, 0.623528003692627, 0.15251100063323975, 0.7723940014839172, 0.616562008857727, 0.3763520121574402, 0.9094089865684509, 0.1770150065422058, 0.19075599312782288, 0.9660869836807251, 0.1740349978208542, 0.3353259861469269, 0.9408230185508728, 0.04907499998807907, 0.16964000463485718, 0.9843119978904724, 0.04849399998784065, 0.1937119960784912, 0.9810580015182495, 0, 0.7071070075035095, -0.7071070075035095, 0, 0.4591110050678253, 0.40222999453544617, 0.7921029925346375, 0.5532029867172241, 0.5532029867172241, 0.622842013835907, 0.6959879994392395, 0.6959879994392395, 0.17663900554180145, 0.6703829765319824, 0.7403979897499084, 0.048958998173475266, 0.5562090277671814, 0.8310419917106628, 0, 0.5777599811553955, 0.19519099593162537, 0.792523980140686, 0.7223830223083496, 0.2989560067653656, 0.623528003692627, 0.9094089865684509, 0.3763520121574402, 0.1770150065422058, 0.902646005153656, 0.4275760054588318, 0.04906899854540825, 0.8310419917106628, 0.5562090277671814, 0, 0.6036490201950073, 0.11919199675321579, 0.7882900238037109, 0.7723940014839172, 0.15251100063323975, 0.616562008857727, 0.9660869836807251, 0.19075599312782288, 0.1740349978208542, 0.9799150228500366, 0.19348600506782532, 0.048277001827955246, 0.9810580015182495, 0.1937119960784912, 0, 0.9999480247497559, 0.006622000131756067, 0.007786999922245741, 0.9989290237426758, 0.04125700145959854, -0.020945999771356583, 0.9700260162353516, -0.18230900168418884, 0.1606609970331192, 0.9929869771003723, -0.1116809993982315, 0.038782998919487, 0.8677089810371399, -0.30993399024009705, 0.38861599564552307, 0.9675049781799316, -0.18179599940776825, 0.17574100196361542, 0.6127229928970337, -0.23797500133514404, 0.753616988658905, 0.781611979007721, -0.1585649996995926, 0.6032750010490417, 0.13049399852752686, -0.02585900016129017, 0.9911119937896729, 0.16583800315856934, -0.04606600105762482, 0.9850770235061646, 0.9847609996795654, -0.03004699945449829, -0.17129500210285187, 0.9463850259780884, 0.008138000033795834, 0.3229379951953888, 0.6942890286445618, 0.06011800095438957, 0.7171810269355774, 0.405923992395401, 0.09244199842214584, 0.9092199802398682, 0.1477230042219162, 0.0024739999789744616, 0.9890260100364685, 0.991798996925354, -0.11557900160551071, -0.05454900115728378, 0.9920099973678589, 0.07202500104904175, 0.10358300060033798, 0.8882240056991577, 0.2238840013742447, 0.40116599202156067, 0.6046879887580872, 0.13691100478172302, 0.7846069931983948, 0.12908099591732025, -0.06111999973654747, 0.989749014377594, 0.9954820275306702, -0.09436299651861191, 0.010502999648451805, 0.9981970191001892, 0.012060999870300293, 0.05880200117826462, 0.9550639986991882, 0.09819000214338303, 0.27966299653053284, 0.6606940031051636, 0.05169999971985817, 0.7488729953765869, 0.07273799926042557, -0.04034300148487091, 0.9965350031852722, 0.9999179840087891, 0.007191000040620565, 0.010560999624431133, 0.9989050030708313, 0.04436499997973442, -0.014883999712765217, 0.9694769978523254, -0.17860299348831177, 0.1679760068655014, 0.9924619793891907, -0.10775599628686905, 0.058378998190164566, 0.8567489981651306, -0.28829601407051086, 0.4276289939880371, 0.9473999738693237, -0.16112199425697327, 0.27653801441192627, 0.5627779960632324, -0.19747799634933472, 0.8026729822158813, 0.6577669978141785, -0.11438000202178955, 0.7444859743118286, 0.07901199907064438, 0.0013689999468624592, 0.9968730211257935, 0.07274100184440613, -0.02020600065588951, 0.9971460103988647, 0.9888780117034912, 0.025808999314904213, -0.14647500216960907, 0.9453979730606079, -0.006663000211119652, 0.3258500099182129, 0.6921399831771851, -0.04972299933433533, 0.7200480103492737, 0.3957499861717224, -0.08134900033473969, 0.9147480130195618, 0.13914500176906586, -0.00007899999764049426, 0.9902719855308533, 0.9924669861793518, -0.10311000049114227, -0.06616800278425217, 0.9926300048828125, 0.07926999777555466, 0.09165900200605392, 0.900858998298645, 0.2553130090236664, 0.3510949909687042, 0.6513699889183044, 0.18318000435829163, 0.7363160252571106, 0.15978699922561646, -0.02714099921286106, 0.9867780208587646, 0.9955620169639587, -0.09379199892282486, 0.0077309999614953995, 0.9991030097007751, 0.01610800065100193, 0.039149001240730286, 0.9764699935913086, 0.12068899720907211, 0.17871999740600586, 0.7859060168266296, 0.10103499889373779, 0.6100350022315979, 0.16579000651836395, -0.014832000248134136, 0.986050009727478, 0.1655299961566925, -0.10078699886798859, 0.9810410141944885, -0.0046790000051259995, -0.1750659942626953, 0.9845460057258606, -0.3859579861164093, -0.06494200229644775, 0.9202280044555664, -0.3219670057296753, -0.05600599944591522, 0.9450929760932922, -0.6471610069274902, -0.11495299637317657, 0.7536370158195496, -0.5616440176963806, -0.078855000436306, 0.8236119747161865, -0.8379700183868408, -0.23749999701976776, 0.4913240075111389, -0.7512590289115906, -0.1447169929742813, 0.6439470052719116, -0.9052090048789978, -0.2807050049304962, 0.3190630078315735, -0.8249419927597046, -0.2209009975194931, 0.5202630162239075, -0.13363699615001678, 0.0291920006275177, 0.9905999898910522, -0.4039649963378906, 0.0019519999623298645, 0.9147719740867615, -0.7191359996795654, 0.002443999983370304, 0.6948649883270264, -0.9637579917907715, 0.026884999126195908, 0.26541900634765625, -0.9637719988822937, 0.2207069993019104, -0.14977200329303741, 0.03522900119423866, 0.06716900318861008, 0.9971190094947815, -0.3620629906654358, -0.01676199957728386, 0.9320030212402344, -0.6534259915351868, 0.007120999973267317, 0.7569569945335388, -0.8528590202331543, 0.11686599999666214, 0.5088940262794495, -0.8814889788627625, 0.3579840064048767, 0.3079349994659424, 0.0726580023765564, 0.02018200047314167, 0.9971529841423035, -0.37608298659324646, -0.025955000892281532, 0.926222026348114, -0.6568350195884705, -0.015021000057458878, 0.7538840174674988, -0.8238760232925415, 0.03257700055837631, 0.5658339858055115, -0.8688690066337585, 0.13078700006008148, 0.4774540066719055, 0.07265599817037582, -0.07700400054454803, 0.994379997253418, -0.0343950018286705, -0.151870995759964, 0.9878020286560059, -0.40362000465393066, -0.05282000079751015, 0.9134010076522827, -0.37586501240730286, -0.041078001260757446, 0.9257640242576599, -0.6878190040588379, -0.0810059979557991, 0.721347987651825, -0.6558970212936401, -0.052101001143455505, 0.7530509829521179, -0.8708800077438354, -0.2062380015850067, 0.44613200426101685, -0.8175939917564392, -0.12448199838399887, 0.5621780157089233, -0.8926960229873657, -0.3055669963359833, 0.33124300837516785, -0.8565059900283813, -0.21024300158023834, 0.4713769853115082, -0.15155400335788727, -0.025412000715732574, 0.9881219863891602, -0.41033700108528137, -0.0026420000940561295, 0.9119300246238708, -0.7240620255470276, 0.00047400000039488077, 0.6897349953651428, -0.9655590057373047, -0.017078999429941177, 0.2596229910850525, -0.973825991153717, -0.19711799919605255, -0.1131730005145073, 0.06214199960231781, 0.08235500007867813, 0.9946640133857727, -0.3334290087223053, 0.007625999860465527, 0.9427440166473389, -0.608610987663269, 0.04885999858379364, 0.7919629812240601, -0.8253309726715088, 0.14631199836730957, 0.5453640222549438, -0.9105669856071472, 0.3146660029888153, 0.2680560052394867, 0.16523399949073792, 0.04589800164103508, 0.985185980796814, -0.32260099053382874, -0.010471000336110592, 0.9464769959449768, -0.5639140009880066, 0.015166000463068485, 0.8256940245628357, -0.758965015411377, 0.05617399886250496, 0.6487039923667908, -0.8382350206375122, 0.14245299994945526, 0.5263739824295044, 0.9727830290794373, -0.019794000312685966, 0.2308720052242279, 0.9828159809112549, -0.036465998739004135, 0.18095199763774872, 0.9050639867782593, -0.02252200059592724, 0.4246790111064911, 0.8354039788246155, -0.03220000118017197, 0.5486930012702942, 0.6465700268745422, -0.045921001583337784, 0.7614709734916687, 0.4826749861240387, -0.04895399883389473, 0.8744300007820129, 0.4453999996185303, 0.17256900668144226, 0.8785430192947388, 0.47231200337409973, 0.13768500089645386, 0.8706120252609253, 0.4824250042438507, 0.3898639976978302, 0.7843930125236511, 0.641398012638092, 0.4275979995727539, 0.6370000243186951, 0.9863939881324768, 0.0994419977068901, 0.13091400265693665, 0.9154840111732483, 0.2120320051908493, 0.34195101261138916, 0.7246469855308533, 0.245046004652977, 0.6440799832344055, 0.35685500502586365, 0.17885500192642212, 0.9168779850006104, 0.14101800322532654, 0.24263200163841248, 0.9598140120506287, 0.9366779923439026, 0.16484400629997253, 0.3089669942855835, 0.7982620000839233, 0.2441370040178299, 0.5506129860877991, 0.4769439995288849, 0.2904820144176483, 0.8295450210571289, 0.4125959873199463, -0.017246000468730927, 0.9107509851455688, 0.341374009847641, -0.37689098715782166, 0.8610560297966003, 0.9026100039482117, 0.14119599759578705, 0.40664398670196533, 0.7326020002365112, 0.1491979956626892, 0.6641039848327637, 0.38115599751472473, 0.15792299807071686, 0.9109230041503906, 0.5317370295524597, -0.02143399976193905, 0.846638023853302, 0.7915729880332947, -0.3539769947528839, 0.49810999631881714, 0.9087340235710144, -0.07959599792957306, 0.40971601009368896, 0.9386569857597351, -0.17680299282073975, 0.29607900977134705, 0.7781569957733154, -0.19467000663280487, 0.5971400141716003, 0.738847017288208, -0.07674700021743774, 0.6694890260696411, 0.43915998935699463, -0.22276799380779266, 0.870352029800415, 0.3863860070705414, -0.0790880024433136, 0.918940007686615, 0.3576120138168335, 0.07325199991464615, 0.9309930205345154, 0.5227140188217163, 0.16167999804019928, 0.8370360136032104, 0.4246380031108856, 0.3233239948749542, 0.845661997795105, 0.733618974685669, 0.48907899856567383, 0.4718089997768402, 0.9886019825935364, -0.10717800259590149, 0.10573200136423111, 0.9131960272789001, -0.22303399443626404, 0.3410690128803253, 0.7163559794425964, -0.25636500120162964, 0.6489310264587402, 0.35149699449539185, -0.15968100726604462, 0.922469973564148, 0.07999800145626068, -0.2107039988040924, 0.9742709994316101, 0.9883249998092651, 0.04732999950647354, 0.14482200145721436, 0.9210500121116638, 0.07413999736309052, 0.3823229968547821, 0.6804890036582947, 0.11895299702882767, 0.7230389714241028, 0.4935390055179596, -0.10269299894571304, 0.8636389970779419, 0.3912479877471924, -0.4752289950847626, 0.7880880236625671, 0.9700270295143127, 0.07970499992370605, 0.2295520007610321, 0.831250011920929, 0.10592100024223328, 0.5457149744033813, 0.4774230122566223, 0.13252699375152588, 0.8686220049858093, 0.47922399640083313, -0.0024129999801516533, 0.877689003944397, 0.6902980208396912, -0.29711300134658813, 0.6597059965133667, 0.6312130093574524, 0.4550989866256714, 0.628055989742279, 0.26494699716567993, 0.5426689982414246, 0.797065019607544, 0.4216960072517395, 0.6728450059890747, 0.6078259944915771, 0.7324270009994507, 0.5829970240592957, 0.35166099667549133, 0.5086709856987, 0.8606399893760681, -0.023507000878453255, 0.7640720009803772, 0.6449369788169861, -0.015798000618815422, 0.19029100239276886, 0.2773289978504181, -0.9417420029640198, 0.02588699944317341, 0.0005750000127591193, -0.9996650218963623, -0.33045700192451477, -0.4387669861316681, -0.8356329798698425, -0.6271269917488098, -0.4645389914512634, -0.6252319812774658, -0.02311599999666214, 0.2469020038843155, 0.9687650203704834, -0.012950999662280083, 0.45514100790023804, 0.8903250098228455, -0.001180000021122396, 0.9888520240783691, 0.14889399707317352, 0.019520999863743782, 0.6841210126876831, -0.7291070222854614, 0.012253000400960445, 0.007784999907016754, -0.9998949766159058, 0.3314639925956726, -0.3832260072231293, 0.8621309995651245, 0.08274699747562408, -0.16047699749469757, 0.9835649728775024, -0.381630003452301, 0.638043999671936, 0.6687729954719543, -0.44988399744033813, 0.787883996963501, -0.42052799463272095, -0.2780170142650604, 0.5983560085296631, -0.7514500021934509, 0.7378140091896057, -0.4918749928474426, 0.4622659981250763, 0.6153389811515808, -0.45540300011634827, 0.6434019804000854, -0.43678900599479675, 0.33411499857902527, 0.8352140188217163, -0.7429530024528503, 0.6388909816741943, -0.1995989978313446, -0.7432950139045715, 0.5977380275726318, -0.3003700077533722, 0.7197920083999634, 0.5168960094451904, 0.4633769989013672, 0.22183099389076233, 0.4485720098018646, 0.8657789826393127, 0.08203200250864029, 0.15848000347614288, 0.9839479923248291, 0.5953459739685059, 0.4811680018901825, 0.6434599757194519, -0.37556400895118713, -0.6215270161628723, 0.6875, -0.42666301131248474, -0.33534398674964905, 0.8399419784545898, -0.44476398825645447, -0.7887529730796814, -0.42432600259780884, -0.7557309865951538, -0.6222699880599976, -0.20408600568771362, -0.4292669892311096, -0.5361850261688232, -0.7267979979515076, -0.7655900120735168, -0.5671039819717407, -0.30375200510025024, 0.007348000071942806, -0.21113499999046326, 0.9774289727210999, -0.01990099996328354, -0.4373210072517395, 0.8990849852561951, -0.008775000460445881, -0.9864199757575989, 0.16400499641895294, 0.024855999276041985, -0.6829339861869812, -0.7300570011138916, 0.014514000155031681, 0.03655200079083443, -0.9992259740829468, 0.40192899107933044, -0.4676550030708313, 0.7872430086135864, 0.41696101427078247, -0.6813820004463196, 0.6015490293502808, 0.5033609867095947, -0.8637740015983582, -0.022839000448584557, 0.2058819979429245, -0.2945750057697296, -0.9331870079040527, -0.2351589947938919, 0.5535579919815063, -0.7989199757575989, 0.6533820033073425, -0.435588002204895, 0.619156002998352, 0.7555500268936157, -0.554410994052887, 0.3489600121974945, 0.7765160202980042, -0.6298360228538513, -0.01814199984073639, 0.025975000113248825, 0.008264999836683273, -0.9996280074119568, -0.6086519956588745, 0.49536699056625366, -0.6198019981384277, -0.9813200235366821, 0.19238099455833435, 0, -0.831650972366333, 0.5552989840507507, 0, -0.4425640106201172, 0.38308998942375183, 0.8107889890670776, -0.5623509883880615, 0.11026400327682495, 0.8195139765739441, 0.367917001247406, -0.15178599953651428, 0.917385995388031, 0.3960669934749603, -0.07774800062179565, 0.9149240255355835, 0.3283520042896271, -0.13562799990177155, 0.9347670078277588, 0.3530749976634979, -0.06952299922704697, 0.9330080151557922, -0.5935590267181396, 0.20035800337791443, 0.7794510126113892, -0.6201800107955933, 0.12245599925518036, 0.7748429775238037, -0.5552989840507507, 0.831650972366333, 0, -0.2616960108280182, 0.523730993270874, 0.8106920123100281, 0.2819640040397644, -0.2819199860095978, 0.9170699715614319, 0.25169798731803894, -0.25161200761795044, 0.9345269799232483, -0.4710330069065094, 0.41249701380729675, 0.7797269821166992, -0.19238099455833435, 0.9813200235366821, 0, -0.04031100124120712, 0.5840420126914978, 0.8107219934463501, 0.1517850011587143, -0.3678950071334839, 0.9173960089683533, 0.13561999797821045, -0.3282899856567383, 0.9347900152206421, -0.27737799286842346, 0.561601996421814, 0.779528021812439, -1, 0, 0, 0.29074999690055847, 0.5413560271263123, 0.7889220118522644, 0.07767199724912643, -0.39607399702072144, 0.9149270057678223, 0.0693729966878891, -0.3530940115451813, 0.9330130219459534, -0.12221000343561172, 0.6202139854431152, 0.7748550176620483, 0.19238099455833435, 0.9813200235366821, 0, 0.5552989840507507, 0.831650972366333, 0, 0.38308998942375183, 0.4425640106201172, 0.8107889890670776, 0.11026400327682495, 0.5623509883880615, 0.8195139765739441, -0.15178599953651428, -0.367917001247406, 0.917385995388031, -0.07774800062179565, -0.3960669934749603, 0.9149240255355835, -0.13562799990177155, -0.3283520042896271, 0.9347670078277588, -0.06952299922704697, -0.3530749976634979, 0.9330080151557922, 0.20035800337791443, 0.5935590267181396, 0.7794510126113892, 0.12245599925518036, 0.6201800107955933, 0.7748429775238037, 0.831650972366333, 0.5552989840507507, 0, 0.523730993270874, 0.2616960108280182, 0.8106920123100281, -0.2819199860095978, -0.2819640040397644, 0.9170699715614319, -0.25161200761795044, -0.25169798731803894, 0.9345269799232483, 0.41249701380729675, 0.4710330069065094, 0.7797269821166992, 0.9813200235366821, 0.19238099455833435, 0, 0.5840420126914978, 0.04031100124120712, 0.8107219934463501, -0.3678950071334839, -0.1517850011587143, 0.9173960089683533, -0.3282899856567383, -0.13561999797821045, 0.9347900152206421, 0.561601996421814, 0.27737799286842346, 0.779528021812439, -1, 0, 0, 0.5413560271263123, -0.29074999690055847, 0.7889220118522644, -0.39607399702072144, -0.07767199724912643, 0.9149270057678223, -0.3530940115451813, -0.0693729966878891, 0.9330130219459534, 0.6202139854431152, 0.12221000343561172, 0.7748550176620483, 0.9813200235366821, -0.19238099455833435, 0, 0.831650972366333, -0.5552989840507507, 0, 0.4425640106201172, -0.38308998942375183, 0.8107889890670776, 0.5623509883880615, -0.11026400327682495, 0.8195139765739441, -0.367917001247406, 0.15178599953651428, 0.917385995388031, -0.3960669934749603, 0.07774800062179565, 0.9149240255355835, -0.3283520042896271, 0.13562799990177155, 0.9347670078277588, -0.3530749976634979, 0.06952299922704697, 0.9330080151557922, 0.5935590267181396, -0.20035800337791443, 0.7794510126113892, 0.6201800107955933, -0.12245599925518036, 0.7748429775238037, 0.5552989840507507, -0.831650972366333, 0, 0.2616960108280182, -0.523730993270874, 0.8106920123100281, -0.2819640040397644, 0.2819199860095978, 0.9170699715614319, -0.25169798731803894, 0.25161200761795044, 0.9345269799232483, 0.4710330069065094, -0.41249701380729675, 0.7797269821166992, 0.19238099455833435, -0.9813200235366821, 0, 0.04031100124120712, -0.5840420126914978, 0.8107219934463501, -0.1517850011587143, 0.3678950071334839, 0.9173960089683533, -0.13561999797821045, 0.3282899856567383, 0.9347900152206421, 0.27737799286842346, -0.561601996421814, 0.779528021812439, -1, 0, 0, -0.29074999690055847, -0.5413560271263123, 0.7889220118522644, -0.07767199724912643, 0.39607399702072144, 0.9149270057678223, -0.0693729966878891, 0.3530940115451813, 0.9330130219459534, 0.12221000343561172, -0.6202139854431152, 0.7748550176620483, -0.19238099455833435, -0.9813200235366821, 0, -0.5552989840507507, -0.831650972366333, 0, -0.38308998942375183, -0.4425640106201172, 0.8107889890670776, -0.11026400327682495, -0.5623509883880615, 0.8195139765739441, 0.15178599953651428, 0.367917001247406, 0.917385995388031, 0.07774800062179565, 0.3960669934749603, 0.9149240255355835, 0.13562799990177155, 0.3283520042896271, 0.9347670078277588, 0.06952299922704697, 0.3530749976634979, 0.9330080151557922, -0.20035800337791443, -0.5935590267181396, 0.7794510126113892, -0.12245599925518036, -0.6201800107955933, 0.7748429775238037, -0.831650972366333, -0.5552989840507507, 0, -0.523730993270874, -0.2616960108280182, 0.8106920123100281, 0.2819199860095978, 0.2819640040397644, 0.9170699715614319, 0.25161200761795044, 0.25169798731803894, 0.9345269799232483, -0.41249701380729675, -0.4710330069065094, 0.7797269821166992, -0.9813200235366821, -0.19238099455833435, 0, -0.5840420126914978, -0.04031100124120712, 0.8107219934463501, 0.3678950071334839, 0.1517850011587143, 0.9173960089683533, 0.3282899856567383, 0.13561999797821045, 0.9347900152206421, -0.561601996421814, -0.27737799286842346, 0.779528021812439, -1, 0, 0, -0.5413560271263123, 0.29074999690055847, 0.7889220118522644, 0.39607399702072144, 0.07767199724912643, 0.9149270057678223, 0.3530940115451813, 0.0693729966878891, 0.9330130219459534, -0.6202139854431152, -0.12221000343561172, 0.7748550176620483, -0.6201800107955933, 0.12245599925518036, 0.7748429775238037, -0.5616469979286194, 0.27755099534988403, 0.7794349789619446, -0.8979210257530212, 0.37159600853919983, 0.23590999841690063, -0.9542099833488464, 0.18841099739074707, 0.23234599828720093, -0.9101200103759766, 0.3766449987888336, 0.17268399894237518, -0.966795027256012, 0.19089600443840027, 0.16990099847316742, -0.8549879789352417, 0.35383298993110657, 0.3792079985141754, -0.9100499749183655, 0.17969100177288055, 0.3735229969024658, -0.8064150214195251, 0.2724289894104004, 0.5248600244522095, -0.8383409976959229, 0.16553199291229248, 0.5194069743156433, -0.4125959873199463, 0.47094500064849854, 0.7797279953956604, -0.687192976474762, 0.687192976474762, 0.23565199971199036, -0.6965190172195435, 0.6965190172195435, 0.17240400612354279, -0.6544880270957947, 0.6544870138168335, 0.37853899598121643, -0.6404970288276672, 0.5611429810523987, 0.5242909789085388, -0.20047900080680847, 0.5933949947357178, 0.7795450091362, -0.3715969920158386, 0.8979210257530212, 0.23590999841690063, -0.3766449987888336, 0.9101200103759766, 0.17268399894237518, -0.35383298993110657, 0.8549879789352417, 0.3792079985141754, -0.3770729899406433, 0.7630789875984192, 0.5249059796333313, -0.12245900183916092, 0.6201940178871155, 0.7748309969902039, -0.18841099739074707, 0.9542099833488464, 0.23234599828720093, -0.19089600443840027, 0.966795027256012, 0.16990099847316742, -0.17969200015068054, 0.9100499749183655, 0.3735229969024658, -0.16553199291229248, 0.8383409976959229, 0.5194069743156433, 0.12245599925518036, 0.6201800107955933, 0.7748429775238037, 0.27755099534988403, 0.5616469979286194, 0.7794349789619446, 0.37159600853919983, 0.8979210257530212, 0.23590999841690063, 0.18841099739074707, 0.9542099833488464, 0.23234599828720093, 0.3766449987888336, 0.9101200103759766, 0.17268399894237518, 0.19089600443840027, 0.966795027256012, 0.16990099847316742, 0.35383298993110657, 0.8549879789352417, 0.3792079985141754, 0.17969100177288055, 0.9100499749183655, 0.3735229969024658, 0.2724289894104004, 0.8064150214195251, 0.5248600244522095, 0.16553199291229248, 0.8383409976959229, 0.5194069743156433, 0.47094500064849854, 0.4125959873199463, 0.7797279953956604, 0.687192976474762, 0.687192976474762, 0.23565199971199036, 0.6965190172195435, 0.6965190172195435, 0.17240400612354279, 0.6544870138168335, 0.6544880270957947, 0.37853899598121643, 0.5611429810523987, 0.6404970288276672, 0.5242909789085388, 0.5933949947357178, 0.20047900080680847, 0.7795450091362, 0.8979210257530212, 0.3715969920158386, 0.23590999841690063, 0.9101200103759766, 0.3766449987888336, 0.17268399894237518, 0.8549879789352417, 0.35383298993110657, 0.3792079985141754, 0.7630789875984192, 0.3770729899406433, 0.5249059796333313, 0.6201940178871155, 0.12245900183916092, 0.7748309969902039, 0.9542099833488464, 0.18841099739074707, 0.23234599828720093, 0.966795027256012, 0.19089600443840027, 0.16990099847316742, 0.9100499749183655, 0.17969200015068054, 0.3735229969024658, 0.8383409976959229, 0.16553199291229248, 0.5194069743156433, 0.6201800107955933, -0.12245599925518036, 0.7748429775238037, 0.5616469979286194, -0.27755099534988403, 0.7794349789619446, 0.8979210257530212, -0.37159600853919983, 0.23590999841690063, 0.9542099833488464, -0.18841099739074707, 0.23234599828720093, 0.9101200103759766, -0.3766449987888336, 0.17268399894237518, 0.966795027256012, -0.19089600443840027, 0.16990099847316742, 0.8549879789352417, -0.35383298993110657, 0.3792079985141754, 0.9100499749183655, -0.17969100177288055, 0.3735229969024658, 0.8064150214195251, -0.2724289894104004, 0.5248600244522095, 0.8383409976959229, -0.16553199291229248, 0.5194069743156433, 0.4125959873199463, -0.47094500064849854, 0.7797279953956604, 0.687192976474762, -0.687192976474762, 0.23565199971199036, 0.6965190172195435, -0.6965190172195435, 0.17240400612354279, 0.6544880270957947, -0.6544870138168335, 0.37853899598121643, 0.6404970288276672, -0.5611429810523987, 0.5242909789085388, 0.20047900080680847, -0.5933949947357178, 0.7795450091362, 0.3715969920158386, -0.8979210257530212, 0.23590999841690063, 0.3766449987888336, -0.9101200103759766, 0.17268399894237518, 0.35383298993110657, -0.8549879789352417, 0.3792079985141754, 0.3770729899406433, -0.7630789875984192, 0.5249059796333313, 0.12245900183916092, -0.6201940178871155, 0.7748309969902039, 0.18841099739074707, -0.9542099833488464, 0.23234599828720093, 0.19089600443840027, -0.966795027256012, 0.16990099847316742, 0.17969200015068054, -0.9100499749183655, 0.3735229969024658, 0.16553199291229248, -0.8383409976959229, 0.5194069743156433, -0.12245599925518036, -0.6201800107955933, 0.7748429775238037, -0.27755099534988403, -0.5616469979286194, 0.7794349789619446, -0.37159600853919983, -0.8979210257530212, 0.23590999841690063, -0.18841099739074707, -0.9542099833488464, 0.23234599828720093, -0.3766449987888336, -0.9101200103759766, 0.17268399894237518, -0.19089600443840027, -0.966795027256012, 0.16990099847316742, -0.35383298993110657, -0.8549879789352417, 0.3792079985141754, -0.17969100177288055, -0.9100499749183655, 0.3735229969024658, -0.2724289894104004, -0.8064150214195251, 0.5248600244522095, -0.16553199291229248, -0.8383409976959229, 0.5194069743156433, -0.47094500064849854, -0.4125959873199463, 0.7797279953956604, -0.687192976474762, -0.687192976474762, 0.23565199971199036, -0.6965190172195435, -0.6965190172195435, 0.17240400612354279, -0.6544870138168335, -0.6544880270957947, 0.37853899598121643, -0.5611429810523987, -0.6404970288276672, 0.5242909789085388, -0.5933949947357178, -0.20047900080680847, 0.7795450091362, -0.8979210257530212, -0.3715969920158386, 0.23590999841690063, -0.9101200103759766, -0.3766449987888336, 0.17268399894237518, -0.8549879789352417, -0.35383298993110657, 0.3792079985141754, -0.7630789875984192, -0.3770729899406433, 0.5249059796333313, -0.6201940178871155, -0.12245900183916092, 0.7748309969902039, -0.9542099833488464, -0.18841099739074707, 0.23234599828720093, -0.966795027256012, -0.19089600443840027, 0.16990099847316742, -0.9100499749183655, -0.17969200015068054, 0.3735229969024658, -0.8383409976959229, -0.16553199291229248, 0.5194069743156433 ]);\nvar teapotTexCoords = new Float32Array([ 2, 2, 0, 1.75, 2, 0, 1.75, 1.975000023841858, 0, 2, 1.975000023841858, 0, 1.75, 1.9500000476837158, 0, 2, 1.9500000476837158, 0, 1.75, 1.9249999523162842, 0, 2, 1.9249999523162842, 0, 1.75, 1.899999976158142, 0, 2, 1.899999976158142, 0, 1.5, 2, 0, 1.5, 1.975000023841858, 0, 1.5, 1.9500000476837158, 0, 1.5, 1.9249999523162842, 0, 1.5, 1.899999976158142, 0, 1.25, 2, 0, 1.25, 1.975000023841858, 0, 1.25, 1.9500000476837158, 0, 1.25, 1.9249999523162842, 0, 1.25, 1.899999976158142, 0, 1, 2, 0, 1, 1.975000023841858, 0, 1, 1.9500000476837158, 0, 1, 1.9249999523162842, 0, 1, 1.899999976158142, 0, 1, 2, 0, 0.75, 2, 0, 0.75, 1.975000023841858, 0, 1, 1.975000023841858, 0, 0.75, 1.9500000476837158, 0, 1, 1.9500000476837158, 0, 0.75, 1.9249999523162842, 0, 1, 1.9249999523162842, 0, 0.75, 1.899999976158142, 0, 1, 1.899999976158142, 0, 0.5, 2, 0, 0.5, 1.975000023841858, 0, 0.5, 1.9500000476837158, 0, 0.5, 1.9249999523162842, 0, 0.5, 1.899999976158142, 0, 0.25, 2, 0, 0.25, 1.975000023841858, 0, 0.25, 1.9500000476837158, 0, 0.25, 1.9249999523162842, 0, 0.25, 1.899999976158142, 0, 0, 2, 0, 0, 1.975000023841858, 0, 0, 1.9500000476837158, 0, 0, 1.9249999523162842, 0, 0, 1.899999976158142, 0, 2, 2, 0, 1.75, 2, 0, 1.75, 1.975000023841858, 0, 2, 1.975000023841858, 0, 1.75, 1.9500000476837158, 0, 2, 1.9500000476837158, 0, 1.75, 1.9249999523162842, 0, 2, 1.9249999523162842, 0, 1.75, 1.899999976158142, 0, 2, 1.899999976158142, 0, 1.5, 2, 0, 1.5, 1.975000023841858, 0, 1.5, 1.9500000476837158, 0, 1.5, 1.9249999523162842, 0, 1.5, 1.899999976158142, 0, 1.25, 2, 0, 1.25, 1.975000023841858, 0, 1.25, 1.9500000476837158, 0, 1.25, 1.9249999523162842, 0, 1.25, 1.899999976158142, 0, 1, 2, 0, 1, 1.975000023841858, 0, 1, 1.9500000476837158, 0, 1, 1.9249999523162842, 0, 1, 1.899999976158142, 0, 1, 2, 0, 0.75, 2, 0, 0.75, 1.975000023841858, 0, 1, 1.975000023841858, 0, 0.75, 1.9500000476837158, 0, 1, 1.9500000476837158, 0, 0.75, 1.9249999523162842, 0, 1, 1.9249999523162842, 0, 0.75, 1.899999976158142, 0, 1, 1.899999976158142, 0, 0.5, 2, 0, 0.5, 1.975000023841858, 0, 0.5, 1.9500000476837158, 0, 0.5, 1.9249999523162842, 0, 0.5, 1.899999976158142, 0, 0.25, 2, 0, 0.25, 1.975000023841858, 0, 0.25, 1.9500000476837158, 0, 0.25, 1.9249999523162842, 0, 0.25, 1.899999976158142, 0, 0, 2, 0, 0, 1.975000023841858, 0, 0, 1.9500000476837158, 0, 0, 1.9249999523162842, 0, 0, 1.899999976158142, 0, 2, 1.899999976158142, 0, 1.75, 1.899999976158142, 0, 1.75, 1.6749999523162842, 0, 2, 1.6749999523162842, 0, 1.75, 1.4500000476837158, 0, 2, 1.4500000476837158, 0, 1.75, 1.225000023841858, 0, 2, 1.225000023841858, 0, 1.75, 1, 0, 2, 1, 0, 1.5, 1.899999976158142, 0, 1.5, 1.6749999523162842, 0, 1.5, 1.4500000476837158, 0, 1.5, 1.225000023841858, 0, 1.5, 1, 0, 1.25, 1.899999976158142, 0, 1.25, 1.6749999523162842, 0, 1.25, 1.4500000476837158, 0, 1.25, 1.225000023841858, 0, 1.25, 1, 0, 1, 1.899999976158142, 0, 1, 1.6749999523162842, 0, 1, 1.4500000476837158, 0, 1, 1.225000023841858, 0, 1, 1, 0, 1, 1.899999976158142, 0, 0.75, 1.899999976158142, 0, 0.75, 1.6749999523162842, 0, 1, 1.6749999523162842, 0, 0.75, 1.4500000476837158, 0, 1, 1.4500000476837158, 0, 0.75, 1.225000023841858, 0, 1, 1.225000023841858, 0, 0.75, 1, 0, 1, 1, 0, 0.5, 1.899999976158142, 0, 0.5, 1.6749999523162842, 0, 0.5, 1.4500000476837158, 0, 0.5, 1.225000023841858, 0, 0.5, 1, 0, 0.25, 1.899999976158142, 0, 0.25, 1.6749999523162842, 0, 0.25, 1.4500000476837158, 0, 0.25, 1.225000023841858, 0, 0.25, 1, 0, 0, 1.899999976158142, 0, 0, 1.6749999523162842, 0, 0, 1.4500000476837158, 0, 0, 1.225000023841858, 0, 0, 1, 0, 2, 1.899999976158142, 0, 1.75, 1.899999976158142, 0, 1.75, 1.6749999523162842, 0, 2, 1.6749999523162842, 0, 1.75, 1.4500000476837158, 0, 2, 1.4500000476837158, 0, 1.75, 1.225000023841858, 0, 2, 1.225000023841858, 0, 1.75, 1, 0, 2, 1, 0, 1.5, 1.899999976158142, 0, 1.5, 1.6749999523162842, 0, 1.5, 1.4500000476837158, 0, 1.5, 1.225000023841858, 0, 1.5, 1, 0, 1.25, 1.899999976158142, 0, 1.25, 1.6749999523162842, 0, 1.25, 1.4500000476837158, 0, 1.25, 1.225000023841858, 0, 1.25, 1, 0, 1, 1.899999976158142, 0, 1, 1.6749999523162842, 0, 1, 1.4500000476837158, 0, 1, 1.225000023841858, 0, 1, 1, 0, 1, 1.899999976158142, 0, 0.75, 1.899999976158142, 0, 0.75, 1.6749999523162842, 0, 1, 1.6749999523162842, 0, 0.75, 1.4500000476837158, 0, 1, 1.4500000476837158, 0, 0.75, 1.225000023841858, 0, 1, 1.225000023841858, 0, 0.75, 1, 0, 1, 1, 0, 0.5, 1.899999976158142, 0, 0.5, 1.6749999523162842, 0, 0.5, 1.4500000476837158, 0, 0.5, 1.225000023841858, 0, 0.5, 1, 0, 0.25, 1.899999976158142, 0, 0.25, 1.6749999523162842, 0, 0.25, 1.4500000476837158, 0, 0.25, 1.225000023841858, 0, 0.25, 1, 0, 0, 1.899999976158142, 0, 0, 1.6749999523162842, 0, 0, 1.4500000476837158, 0, 0, 1.225000023841858, 0, 0, 1, 0, 2, 1, 0, 1.75, 1, 0, 1.75, 0.8500000238418579, 0, 2, 0.8500000238418579, 0, 1.75, 0.699999988079071, 0, 2, 0.699999988079071, 0, 1.75, 0.550000011920929, 0, 2, 0.550000011920929, 0, 1.75, 0.4000000059604645, 0, 2, 0.4000000059604645, 0, 1.5, 1, 0, 1.5, 0.8500000238418579, 0, 1.5, 0.699999988079071, 0, 1.5, 0.550000011920929, 0, 1.5, 0.4000000059604645, 0, 1.25, 1, 0, 1.25, 0.8500000238418579, 0, 1.25, 0.699999988079071, 0, 1.25, 0.550000011920929, 0, 1.25, 0.4000000059604645, 0, 1, 1, 0, 1, 0.8500000238418579, 0, 1, 0.699999988079071, 0, 1, 0.550000011920929, 0, 1, 0.4000000059604645, 0, 1, 1, 0, 0.75, 1, 0, 0.75, 0.8500000238418579, 0, 1, 0.8500000238418579, 0, 0.75, 0.699999988079071, 0, 1, 0.699999988079071, 0, 0.75, 0.550000011920929, 0, 1, 0.550000011920929, 0, 0.75, 0.4000000059604645, 0, 1, 0.4000000059604645, 0, 0.5, 1, 0, 0.5, 0.8500000238418579, 0, 0.5, 0.699999988079071, 0, 0.5, 0.550000011920929, 0, 0.5, 0.4000000059604645, 0, 0.25, 1, 0, 0.25, 0.8500000238418579, 0, 0.25, 0.699999988079071, 0, 0.25, 0.550000011920929, 0, 0.25, 0.4000000059604645, 0, 0, 1, 0, 0, 0.8500000238418579, 0, 0, 0.699999988079071, 0, 0, 0.550000011920929, 0, 0, 0.4000000059604645, 0, 2, 1, 0, 1.75, 1, 0, 1.75, 0.8500000238418579, 0, 2, 0.8500000238418579, 0, 1.75, 0.699999988079071, 0, 2, 0.699999988079071, 0, 1.75, 0.550000011920929, 0, 2, 0.550000011920929, 0, 1.75, 0.4000000059604645, 0, 2, 0.4000000059604645, 0, 1.5, 1, 0, 1.5, 0.8500000238418579, 0, 1.5, 0.699999988079071, 0, 1.5, 0.550000011920929, 0, 1.5, 0.4000000059604645, 0, 1.25, 1, 0, 1.25, 0.8500000238418579, 0, 1.25, 0.699999988079071, 0, 1.25, 0.550000011920929, 0, 1.25, 0.4000000059604645, 0, 1, 1, 0, 1, 0.8500000238418579, 0, 1, 0.699999988079071, 0, 1, 0.550000011920929, 0, 1, 0.4000000059604645, 0, 1, 1, 0, 0.75, 1, 0, 0.75, 0.8500000238418579, 0, 1, 0.8500000238418579, 0, 0.75, 0.699999988079071, 0, 1, 0.699999988079071, 0, 0.75, 0.550000011920929, 0, 1, 0.550000011920929, 0, 0.75, 0.4000000059604645, 0, 1, 0.4000000059604645, 0, 0.5, 1, 0, 0.5, 0.8500000238418579, 0, 0.5, 0.699999988079071, 0, 0.5, 0.550000011920929, 0, 0.5, 0.4000000059604645, 0, 0.25, 1, 0, 0.25, 0.8500000238418579, 0, 0.25, 0.699999988079071, 0, 0.25, 0.550000011920929, 0, 0.25, 0.4000000059604645, 0, 0, 1, 0, 0, 0.8500000238418579, 0, 0, 0.699999988079071, 0, 0, 0.550000011920929, 0, 0, 0.4000000059604645, 0, 2, 0.4000000059604645, 0, 1.75, 0.4000000059604645, 0, 1.75, 0.30000001192092896, 0, 2, 0.30000001192092896, 0, 1.75, 0.20000000298023224, 0, 2, 0.20000000298023224, 0, 1.75, 0.10000000149011612, 0, 2, 0.10000000149011612, 0, 1.75, 0, 0, 2, 0, 0, 1.5, 0.4000000059604645, 0, 1.5, 0.30000001192092896, 0, 1.5, 0.20000000298023224, 0, 1.5, 0.10000000149011612, 0, 1.5, 0, 0, 1.25, 0.4000000059604645, 0, 1.25, 0.30000001192092896, 0, 1.25, 0.20000000298023224, 0, 1.25, 0.10000000149011612, 0, 1.25, 0, 0, 1, 0.4000000059604645, 0, 1, 0.30000001192092896, 0, 1, 0.20000000298023224, 0, 1, 0.10000000149011612, 0, 1, 0, 0, 1, 0.4000000059604645, 0, 0.75, 0.4000000059604645, 0, 0.75, 0.30000001192092896, 0, 1, 0.30000001192092896, 0, 0.75, 0.20000000298023224, 0, 1, 0.20000000298023224, 0, 0.75, 0.10000000149011612, 0, 1, 0.10000000149011612, 0, 0.75, 0, 0, 1, 0, 0, 0.5, 0.4000000059604645, 0, 0.5, 0.30000001192092896, 0, 0.5, 0.20000000298023224, 0, 0.5, 0.10000000149011612, 0, 0.5, 0, 0, 0.25, 0.4000000059604645, 0, 0.25, 0.30000001192092896, 0, 0.25, 0.20000000298023224, 0, 0.25, 0.10000000149011612, 0, 0.25, 0, 0, 0, 0.4000000059604645, 0, 0, 0.30000001192092896, 0, 0, 0.20000000298023224, 0, 0, 0.10000000149011612, 0, 0, 0, 0, 2, 0.4000000059604645, 0, 1.75, 0.4000000059604645, 0, 1.75, 0.30000001192092896, 0, 2, 0.30000001192092896, 0, 1.75, 0.20000000298023224, 0, 2, 0.20000000298023224, 0, 1.75, 0.10000000149011612, 0, 2, 0.10000000149011612, 0, 1.75, 0, 0, 2, 0, 0, 1.5, 0.4000000059604645, 0, 1.5, 0.30000001192092896, 0, 1.5, 0.20000000298023224, 0, 1.5, 0.10000000149011612, 0, 1.5, 0, 0, 1.25, 0.4000000059604645, 0, 1.25, 0.30000001192092896, 0, 1.25, 0.20000000298023224, 0, 1.25, 0.10000000149011612, 0, 1.25, 0, 0, 1, 0.4000000059604645, 0, 1, 0.30000001192092896, 0, 1, 0.20000000298023224, 0, 1, 0.10000000149011612, 0, 1, 0, 0, 1, 0.4000000059604645, 0, 0.75, 0.4000000059604645, 0, 0.75, 0.30000001192092896, 0, 1, 0.30000001192092896, 0, 0.75, 0.20000000298023224, 0, 1, 0.20000000298023224, 0, 0.75, 0.10000000149011612, 0, 1, 0.10000000149011612, 0, 0.75, 0, 0, 1, 0, 0, 0.5, 0.4000000059604645, 0, 0.5, 0.30000001192092896, 0, 0.5, 0.20000000298023224, 0, 0.5, 0.10000000149011612, 0, 0.5, 0, 0, 0.25, 0.4000000059604645, 0, 0.25, 0.30000001192092896, 0, 0.25, 0.20000000298023224, 0, 0.25, 0.10000000149011612, 0, 0.25, 0, 0, 0, 0.4000000059604645, 0, 0, 0.30000001192092896, 0, 0, 0.20000000298023224, 0, 0, 0.10000000149011612, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.875, 0, 1, 0.875, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.625, 0, 1, 0.625, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.75, 1, 0, 0.75, 0.875, 0, 0.75, 0.75, 0, 0.75, 0.625, 0, 0.75, 0.5, 0, 0.625, 1, 0, 0.625, 0.875, 0, 0.625, 0.75, 0, 0.625, 0.625, 0, 0.625, 0.5, 0, 0.5, 1, 0, 0.5, 0.875, 0, 0.5, 0.75, 0, 0.5, 0.625, 0, 0.5, 0.5, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.875, 0, 0.5, 0.875, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.625, 0, 0.5, 0.625, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.25, 1, 0, 0.25, 0.875, 0, 0.25, 0.75, 0, 0.25, 0.625, 0, 0.25, 0.5, 0, 0.125, 1, 0, 0.125, 0.875, 0, 0.125, 0.75, 0, 0.125, 0.625, 0, 0.125, 0.5, 0, 0, 1, 0, 0, 0.875, 0, 0, 0.75, 0, 0, 0.625, 0, 0, 0.5, 0, 1, 0.5, 0, 0.875, 0.5, 0, 0.875, 0.375, 0, 1, 0.375, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0.125, 0, 1, 0.125, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 0.5, 0, 0.75, 0.375, 0, 0.75, 0.25, 0, 0.75, 0.125, 0, 0.75, 0, 0, 0.625, 0.5, 0, 0.625, 0.375, 0, 0.625, 0.25, 0, 0.625, 0.125, 0, 0.625, 0, 0, 0.5, 0.5, 0, 0.5, 0.375, 0, 0.5, 0.25, 0, 0.5, 0.125, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0.375, 0.5, 0, 0.375, 0.375, 0, 0.5, 0.375, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0.125, 0, 0.5, 0.125, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 0.5, 0, 0.25, 0.375, 0, 0.25, 0.25, 0, 0.25, 0.125, 0, 0.25, 0, 0, 0.125, 0.5, 0, 0.125, 0.375, 0, 0.125, 0.25, 0, 0.125, 0.125, 0, 0.125, 0, 0, 0, 0.5, 0, 0, 0.375, 0, 0, 0.25, 0, 0, 0.125, 0, 0, 0, 0, 0.5, 0, 0, 0.625, 0, 0, 0.625, 0.22499999403953552, 0, 0.5, 0.22499999403953552, 0, 0.625, 0.44999998807907104, 0, 0.5, 0.44999998807907104, 0, 0.625, 0.675000011920929, 0, 0.5, 0.675000011920929, 0, 0.625, 0.8999999761581421, 0, 0.5, 0.8999999761581421, 0, 0.75, 0, 0, 0.75, 0.22499999403953552, 0, 0.75, 0.44999998807907104, 0, 0.75, 0.675000011920929, 0, 0.75, 0.8999999761581421, 0, 0.875, 0, 0, 0.875, 0.22499999403953552, 0, 0.875, 0.44999998807907104, 0, 0.875, 0.675000011920929, 0, 0.875, 0.8999999761581421, 0, 1, 0, 0, 1, 0.22499999403953552, 0, 1, 0.44999998807907104, 0, 1, 0.675000011920929, 0, 1, 0.8999999761581421, 0, 0, 0, 0, 0.125, 0, 0, 0.125, 0.22499999403953552, 0, 0, 0.22499999403953552, 0, 0.125, 0.44999998807907104, 0, 0, 0.44999998807907104, 0, 0.125, 0.675000011920929, 0, 0, 0.675000011920929, 0, 0.125, 0.8999999761581421, 0, 0, 0.8999999761581421, 0, 0.25, 0, 0, 0.25, 0.22499999403953552, 0, 0.25, 0.44999998807907104, 0, 0.25, 0.675000011920929, 0, 0.25, 0.8999999761581421, 0, 0.375, 0, 0, 0.375, 0.22499999403953552, 0, 0.375, 0.44999998807907104, 0, 0.375, 0.675000011920929, 0, 0.375, 0.8999999761581421, 0, 0.5, 0, 0, 0.5, 0.22499999403953552, 0, 0.5, 0.44999998807907104, 0, 0.5, 0.675000011920929, 0, 0.5, 0.8999999761581421, 0, 0.5, 0.8999999761581421, 0, 0.625, 0.8999999761581421, 0, 0.625, 0.925000011920929, 0, 0.5, 0.925000011920929, 0, 0.625, 0.949999988079071, 0, 0.5, 0.949999988079071, 0, 0.625, 0.9750000238418579, 0, 0.5, 0.9750000238418579, 0, 0.625, 1, 0, 0.5, 1, 0, 0.75, 0.8999999761581421, 0, 0.75, 0.925000011920929, 0, 0.75, 0.949999988079071, 0, 0.75, 0.9750000238418579, 0, 0.75, 1, 0, 0.875, 0.8999999761581421, 0, 0.875, 0.925000011920929, 0, 0.875, 0.949999988079071, 0, 0.875, 0.9750000238418579, 0, 0.875, 1, 0, 1, 0.8999999761581421, 0, 1, 0.925000011920929, 0, 1, 0.949999988079071, 0, 1, 0.9750000238418579, 0, 1, 1, 0, 0, 0.8999999761581421, 0, 0.125, 0.8999999761581421, 0, 0.125, 0.925000011920929, 0, 0, 0.925000011920929, 0, 0.125, 0.949999988079071, 0, 0, 0.949999988079071, 0, 0.125, 0.9750000238418579, 0, 0, 0.9750000238418579, 0, 0.125, 1, 0, 0, 1, 0, 0.25, 0.8999999761581421, 0, 0.25, 0.925000011920929, 0, 0.25, 0.949999988079071, 0, 0.25, 0.9750000238418579, 0, 0.25, 1, 0, 0.375, 0.8999999761581421, 0, 0.375, 0.925000011920929, 0, 0.375, 0.949999988079071, 0, 0.375, 0.9750000238418579, 0, 0.375, 1, 0, 0.5, 0.8999999761581421, 0, 0.5, 0.925000011920929, 0, 0.5, 0.949999988079071, 0, 0.5, 0.9750000238418579, 0, 0.5, 1, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0 ]);\nvar teapotIndices = new Uint16Array([ 0, 1, 2, 2, 3, 0, 3, 2, 4, 4, 5, 3, 5, 4, 6, 6, 7, 5, 7, 6, 8, 8, 9, 7, 1, 10, 11, 11, 2, 1, 2, 11, 12, 12, 4, 2, 4, 12, 13, 13, 6, 4, 6, 13, 14, 14, 8, 6, 10, 15, 16, 16, 11, 10, 11, 16, 17, 17, 12, 11, 12, 17, 18, 18, 13, 12, 13, 18, 19, 19, 14, 13, 15, 20, 21, 21, 16, 15, 16, 21, 22, 22, 17, 16, 17, 22, 23, 23, 18, 17, 18, 23, 24, 24, 19, 18, 25, 26, 27, 27, 28, 25, 28, 27, 29, 29, 30, 28, 30, 29, 31, 31, 32, 30, 32, 31, 33, 33, 34, 32, 26, 35, 36, 36, 27, 26, 27, 36, 37, 37, 29, 27, 29, 37, 38, 38, 31, 29, 31, 38, 39, 39, 33, 31, 35, 40, 41, 41, 36, 35, 36, 41, 42, 42, 37, 36, 37, 42, 43, 43, 38, 37, 38, 43, 44, 44, 39, 38, 40, 45, 46, 46, 41, 40, 41, 46, 47, 47, 42, 41, 42, 47, 48, 48, 43, 42, 43, 48, 49, 49, 44, 43, 50, 51, 52, 52, 53, 50, 53, 52, 54, 54, 55, 53, 55, 54, 56, 56, 57, 55, 57, 56, 58, 58, 59, 57, 51, 60, 61, 61, 52, 51, 52, 61, 62, 62, 54, 52, 54, 62, 63, 63, 56, 54, 56, 63, 64, 64, 58, 56, 60, 65, 66, 66, 61, 60, 61, 66, 67, 67, 62, 61, 62, 67, 68, 68, 63, 62, 63, 68, 69, 69, 64, 63, 65, 70, 71, 71, 66, 65, 66, 71, 72, 72, 67, 66, 67, 72, 73, 73, 68, 67, 68, 73, 74, 74, 69, 68, 75, 76, 77, 77, 78, 75, 78, 77, 79, 79, 80, 78, 80, 79, 81, 81, 82, 80, 82, 81, 83, 83, 84, 82, 76, 85, 86, 86, 77, 76, 77, 86, 87, 87, 79, 77, 79, 87, 88, 88, 81, 79, 81, 88, 89, 89, 83, 81, 85, 90, 91, 91, 86, 85, 86, 91, 92, 92, 87, 86, 87, 92, 93, 93, 88, 87, 88, 93, 94, 94, 89, 88, 90, 95, 96, 96, 91, 90, 91, 96, 97, 97, 92, 91, 92, 97, 98, 98, 93, 92, 93, 98, 99, 99, 94, 93, 100, 101, 102, 102, 103, 100, 103, 102, 104, 104, 105, 103, 105, 104, 106, 106, 107, 105, 107, 106, 108, 108, 109, 107, 101, 110, 111, 111, 102, 101, 102, 111, 112, 112, 104, 102, 104, 112, 113, 113, 106, 104, 106, 113, 114, 114, 108, 106, 110, 115, 116, 116, 111, 110, 111, 116, 117, 117, 112, 111, 112, 117, 118, 118, 113, 112, 113, 118, 119, 119, 114, 113, 115, 120, 121, 121, 116, 115, 116, 121, 122, 122, 117, 116, 117, 122, 123, 123, 118, 117, 118, 123, 124, 124, 119, 118, 125, 126, 127, 127, 128, 125, 128, 127, 129, 129, 130, 128, 130, 129, 131, 131, 132, 130, 132, 131, 133, 133, 134, 132, 126, 135, 136, 136, 127, 126, 127, 136, 137, 137, 129, 127, 129, 137, 138, 138, 131, 129, 131, 138, 139, 139, 133, 131, 135, 140, 141, 141, 136, 135, 136, 141, 142, 142, 137, 136, 137, 142, 143, 143, 138, 137, 138, 143, 144, 144, 139, 138, 140, 145, 146, 146, 141, 140, 141, 146, 147, 147, 142, 141, 142, 147, 148, 148, 143, 142, 143, 148, 149, 149, 144, 143, 150, 151, 152, 152, 153, 150, 153, 152, 154, 154, 155, 153, 155, 154, 156, 156, 157, 155, 157, 156, 158, 158, 159, 157, 151, 160, 161, 161, 152, 151, 152, 161, 162, 162, 154, 152, 154, 162, 163, 163, 156, 154, 156, 163, 164, 164, 158, 156, 160, 165, 166, 166, 161, 160, 161, 166, 167, 167, 162, 161, 162, 167, 168, 168, 163, 162, 163, 168, 169, 169, 164, 163, 165, 170, 171, 171, 166, 165, 166, 171, 172, 172, 167, 166, 167, 172, 173, 173, 168, 167, 168, 173, 174, 174, 169, 168, 175, 176, 177, 177, 178, 175, 178, 177, 179, 179, 180, 178, 180, 179, 181, 181, 182, 180, 182, 181, 183, 183, 184, 182, 176, 185, 186, 186, 177, 176, 177, 186, 187, 187, 179, 177, 179, 187, 188, 188, 181, 179, 181, 188, 189, 189, 183, 181, 185, 190, 191, 191, 186, 185, 186, 191, 192, 192, 187, 186, 187, 192, 193, 193, 188, 187, 188, 193, 194, 194, 189, 188, 190, 195, 196, 196, 191, 190, 191, 196, 197, 197, 192, 191, 192, 197, 198, 198, 193, 192, 193, 198, 199, 199, 194, 193, 200, 201, 202, 202, 203, 200, 203, 202, 204, 204, 205, 203, 205, 204, 206, 206, 207, 205, 207, 206, 208, 208, 209, 207, 201, 210, 211, 211, 202, 201, 202, 211, 212, 212, 204, 202, 204, 212, 213, 213, 206, 204, 206, 213, 214, 214, 208, 206, 210, 215, 216, 216, 211, 210, 211, 216, 217, 217, 212, 211, 212, 217, 218, 218, 213, 212, 213, 218, 219, 219, 214, 213, 215, 220, 221, 221, 216, 215, 216, 221, 222, 222, 217, 216, 217, 222, 223, 223, 218, 217, 218, 223, 224, 224, 219, 218, 225, 226, 227, 227, 228, 225, 228, 227, 229, 229, 230, 228, 230, 229, 231, 231, 232, 230, 232, 231, 233, 233, 234, 232, 226, 235, 236, 236, 227, 226, 227, 236, 237, 237, 229, 227, 229, 237, 238, 238, 231, 229, 231, 238, 239, 239, 233, 231, 235, 240, 241, 241, 236, 235, 236, 241, 242, 242, 237, 236, 237, 242, 243, 243, 238, 237, 238, 243, 244, 244, 239, 238, 240, 245, 246, 246, 241, 240, 241, 246, 247, 247, 242, 241, 242, 247, 248, 248, 243, 242, 243, 248, 249, 249, 244, 243, 250, 251, 252, 252, 253, 250, 253, 252, 254, 254, 255, 253, 255, 254, 256, 256, 257, 255, 257, 256, 258, 258, 259, 257, 251, 260, 261, 261, 252, 251, 252, 261, 262, 262, 254, 252, 254, 262, 263, 263, 256, 254, 256, 263, 264, 264, 258, 256, 260, 265, 266, 266, 261, 260, 261, 266, 267, 267, 262, 261, 262, 267, 268, 268, 263, 262, 263, 268, 269, 269, 264, 263, 265, 270, 271, 271, 266, 265, 266, 271, 272, 272, 267, 266, 267, 272, 273, 273, 268, 267, 268, 273, 274, 274, 269, 268, 275, 276, 277, 277, 278, 275, 278, 277, 279, 279, 280, 278, 280, 279, 281, 281, 282, 280, 282, 281, 283, 283, 284, 282, 276, 285, 286, 286, 277, 276, 277, 286, 287, 287, 279, 277, 279, 287, 288, 288, 281, 279, 281, 288, 289, 289, 283, 281, 285, 290, 291, 291, 286, 285, 286, 291, 292, 292, 287, 286, 287, 292, 293, 293, 288, 287, 288, 293, 294, 294, 289, 288, 290, 295, 296, 296, 291, 290, 291, 296, 297, 297, 292, 291, 292, 297, 298, 298, 293, 292, 293, 298, 299, 299, 294, 293, 300, 301, 302, 302, 303, 300, 303, 302, 304, 304, 305, 303, 305, 304, 306, 306, 307, 305, 307, 306, 308, 308, 309, 307, 301, 310, 311, 311, 302, 301, 302, 311, 312, 312, 304, 302, 304, 312, 313, 313, 306, 304, 306, 313, 314, 314, 308, 306, 310, 315, 316, 316, 311, 310, 311, 316, 317, 317, 312, 311, 312, 317, 318, 318, 313, 312, 313, 318, 319, 319, 314, 313, 315, 320, 321, 321, 316, 315, 316, 321, 322, 322, 317, 316, 317, 322, 323, 323, 318, 317, 318, 323, 324, 324, 319, 318, 325, 326, 327, 327, 328, 325, 328, 327, 329, 329, 330, 328, 330, 329, 331, 331, 332, 330, 332, 331, 333, 333, 334, 332, 326, 335, 336, 336, 327, 326, 327, 336, 337, 337, 329, 327, 329, 337, 338, 338, 331, 329, 331, 338, 339, 339, 333, 331, 335, 340, 341, 341, 336, 335, 336, 341, 342, 342, 337, 336, 337, 342, 343, 343, 338, 337, 338, 343, 344, 344, 339, 338, 340, 345, 346, 346, 341, 340, 341, 346, 347, 347, 342, 341, 342, 347, 348, 348, 343, 342, 343, 348, 349, 349, 344, 343, 350, 351, 352, 352, 353, 350, 353, 352, 354, 354, 355, 353, 355, 354, 356, 356, 357, 355, 357, 356, 358, 358, 359, 357, 351, 360, 361, 361, 352, 351, 352, 361, 362, 362, 354, 352, 354, 362, 363, 363, 356, 354, 356, 363, 364, 364, 358, 356, 360, 365, 366, 366, 361, 360, 361, 366, 367, 367, 362, 361, 362, 367, 368, 368, 363, 362, 363, 368, 369, 369, 364, 363, 365, 370, 371, 371, 366, 365, 366, 371, 372, 372, 367, 366, 367, 372, 373, 373, 368, 367, 368, 373, 374, 374, 369, 368, 375, 376, 377, 377, 378, 375, 378, 377, 379, 379, 380, 378, 380, 379, 381, 381, 382, 380, 382, 381, 383, 383, 384, 382, 376, 385, 386, 386, 377, 376, 377, 386, 387, 387, 379, 377, 379, 387, 388, 388, 381, 379, 381, 388, 389, 389, 383, 381, 385, 390, 391, 391, 386, 385, 386, 391, 392, 392, 387, 386, 387, 392, 393, 393, 388, 387, 388, 393, 394, 394, 389, 388, 390, 395, 396, 396, 391, 390, 391, 396, 397, 397, 392, 391, 392, 397, 398, 398, 393, 392, 393, 398, 399, 399, 394, 393, 400, 401, 402, 402, 403, 400, 403, 402, 404, 404, 405, 403, 405, 404, 406, 406, 407, 405, 407, 406, 408, 408, 409, 407, 401, 410, 411, 411, 402, 401, 402, 411, 412, 412, 404, 402, 404, 412, 413, 413, 406, 404, 406, 413, 414, 414, 408, 406, 410, 415, 416, 416, 411, 410, 411, 416, 417, 417, 412, 411, 412, 417, 418, 418, 413, 412, 413, 418, 419, 419, 414, 413, 415, 420, 421, 421, 416, 415, 416, 421, 422, 422, 417, 416, 417, 422, 423, 423, 418, 417, 418, 423, 424, 424, 419, 418, 425, 426, 427, 427, 428, 425, 428, 427, 429, 429, 430, 428, 430, 429, 431, 431, 432, 430, 432, 431, 433, 433, 434, 432, 426, 435, 436, 436, 427, 426, 427, 436, 437, 437, 429, 427, 429, 437, 438, 438, 431, 429, 431, 438, 439, 439, 433, 431, 435, 440, 441, 441, 436, 435, 436, 441, 442, 442, 437, 436, 437, 442, 443, 443, 438, 437, 438, 443, 444, 444, 439, 438, 440, 445, 446, 446, 441, 440, 441, 446, 447, 447, 442, 441, 442, 447, 448, 448, 443, 442, 443, 448, 449, 449, 444, 443, 450, 451, 452, 452, 453, 450, 453, 452, 454, 454, 455, 453, 455, 454, 456, 456, 457, 455, 457, 456, 458, 458, 459, 457, 451, 460, 461, 461, 452, 451, 452, 461, 462, 462, 454, 452, 454, 462, 463, 463, 456, 454, 456, 463, 464, 464, 458, 456, 460, 465, 466, 466, 461, 460, 461, 466, 467, 467, 462, 461, 462, 467, 468, 468, 463, 462, 463, 468, 469, 469, 464, 463, 465, 470, 471, 471, 466, 465, 466, 471, 472, 472, 467, 466, 467, 472, 473, 473, 468, 467, 468, 473, 474, 474, 469, 468, 475, 476, 477, 477, 478, 475, 478, 477, 479, 479, 480, 478, 480, 479, 481, 481, 482, 480, 482, 481, 483, 483, 484, 482, 476, 485, 486, 486, 477, 476, 477, 486, 487, 487, 479, 477, 479, 487, 488, 488, 481, 479, 481, 488, 489, 489, 483, 481, 485, 490, 491, 491, 486, 485, 486, 491, 492, 492, 487, 486, 487, 492, 493, 493, 488, 487, 488, 493, 494, 494, 489, 488, 490, 495, 496, 496, 491, 490, 491, 496, 497, 497, 492, 491, 492, 497, 498, 498, 493, 492, 493, 498, 499, 499, 494, 493, 500, 501, 502, 502, 503, 500, 503, 502, 504, 504, 505, 503, 505, 504, 506, 506, 507, 505, 507, 506, 508, 508, 509, 507, 501, 510, 511, 511, 502, 501, 502, 511, 512, 512, 504, 502, 504, 512, 513, 513, 506, 504, 506, 513, 514, 514, 508, 506, 510, 515, 516, 516, 511, 510, 511, 516, 517, 517, 512, 511, 512, 517, 518, 518, 513, 512, 513, 518, 519, 519, 514, 513, 515, 520, 521, 521, 516, 515, 516, 521, 522, 522, 517, 516, 517, 522, 523, 523, 518, 517, 518, 523, 524, 524, 519, 518, 525, 526, 527, 527, 528, 525, 528, 527, 529, 529, 530, 528, 530, 529, 531, 531, 532, 530, 532, 531, 533, 533, 534, 532, 526, 535, 536, 536, 527, 526, 527, 536, 537, 537, 529, 527, 529, 537, 538, 538, 531, 529, 531, 538, 539, 539, 533, 531, 535, 540, 541, 541, 536, 535, 536, 541, 542, 542, 537, 536, 537, 542, 543, 543, 538, 537, 538, 543, 544, 544, 539, 538, 540, 545, 546, 546, 541, 540, 541, 546, 547, 547, 542, 541, 542, 547, 548, 548, 543, 542, 543, 548, 549, 549, 544, 543, 550, 551, 552, 552, 553, 550, 553, 552, 554, 554, 555, 553, 555, 554, 556, 556, 557, 555, 557, 556, 558, 558, 559, 557, 551, 560, 561, 561, 552, 551, 552, 561, 562, 562, 554, 552, 554, 562, 563, 563, 556, 554, 556, 563, 564, 564, 558, 556, 560, 565, 566, 566, 561, 560, 561, 566, 567, 567, 562, 561, 562, 567, 568, 568, 563, 562, 563, 568, 569, 569, 564, 563, 565, 570, 571, 571, 566, 565, 566, 571, 572, 572, 567, 566, 567, 572, 573, 573, 568, 567, 568, 573, 574, 574, 569, 568, 575, 576, 577, 577, 578, 575, 578, 577, 579, 579, 580, 578, 580, 579, 581, 581, 582, 580, 582, 581, 583, 583, 584, 582, 576, 585, 586, 586, 577, 576, 577, 586, 587, 587, 579, 577, 579, 587, 588, 588, 581, 579, 581, 588, 589, 589, 583, 581, 585, 590, 591, 591, 586, 585, 586, 591, 592, 592, 587, 586, 587, 592, 593, 593, 588, 587, 588, 593, 594, 594, 589, 588, 590, 595, 596, 596, 591, 590, 591, 596, 597, 597, 592, 591, 592, 597, 598, 598, 593, 592, 593, 598, 599, 599, 594, 593, 600, 601, 602, 602, 603, 600, 603, 602, 604, 604, 605, 603, 605, 604, 606, 606, 607, 605, 607, 606, 608, 608, 609, 607, 601, 610, 611, 611, 602, 601, 602, 611, 612, 612, 604, 602, 604, 612, 613, 613, 606, 604, 606, 613, 614, 614, 608, 606, 610, 615, 616, 616, 611, 610, 611, 616, 617, 617, 612, 611, 612, 617, 618, 618, 613, 612, 613, 618, 619, 619, 614, 613, 615, 620, 621, 621, 616, 615, 616, 621, 622, 622, 617, 616, 617, 622, 623, 623, 618, 617, 618, 623, 624, 624, 619, 618, 625, 626, 627, 627, 628, 625, 628, 627, 629, 629, 630, 628, 630, 629, 631, 631, 632, 630, 632, 631, 633, 633, 634, 632, 626, 635, 636, 636, 627, 626, 627, 636, 637, 637, 629, 627, 629, 637, 638, 638, 631, 629, 631, 638, 639, 639, 633, 631, 635, 640, 641, 641, 636, 635, 636, 641, 642, 642, 637, 636, 637, 642, 643, 643, 638, 637, 638, 643, 644, 644, 639, 638, 640, 645, 646, 646, 641, 640, 641, 646, 647, 647, 642, 641, 642, 647, 648, 648, 643, 642, 643, 648, 649, 649, 644, 643, 650, 651, 652, 652, 653, 650, 653, 652, 654, 654, 655, 653, 655, 654, 656, 656, 657, 655, 657, 656, 658, 658, 659, 657, 651, 660, 661, 661, 652, 651, 652, 661, 662, 662, 654, 652, 654, 662, 663, 663, 656, 654, 656, 663, 664, 664, 658, 656, 660, 665, 666, 666, 661, 660, 661, 666, 667, 667, 662, 661, 662, 667, 668, 668, 663, 662, 663, 668, 669, 669, 664, 663, 665, 670, 671, 671, 666, 665, 666, 671, 672, 672, 667, 666, 667, 672, 673, 673, 668, 667, 668, 673, 674, 674, 669, 668, 675, 676, 677, 677, 678, 675, 678, 677, 679, 679, 680, 678, 680, 679, 681, 681, 682, 680, 682, 681, 683, 683, 684, 682, 676, 685, 686, 686, 677, 676, 677, 686, 687, 687, 679, 677, 679, 687, 688, 688, 681, 679, 681, 688, 689, 689, 683, 681, 685, 690, 691, 691, 686, 685, 686, 691, 692, 692, 687, 686, 687, 692, 693, 693, 688, 687, 688, 693, 694, 694, 689, 688, 690, 695, 696, 696, 691, 690, 691, 696, 697, 697, 692, 691, 692, 697, 698, 698, 693, 692, 693, 698, 699, 699, 694, 693, 700, 701, 702, 702, 703, 700, 703, 702, 704, 704, 705, 703, 705, 704, 706, 706, 707, 705, 707, 706, 708, 708, 709, 707, 701, 710, 711, 711, 702, 701, 702, 711, 712, 712, 704, 702, 704, 712, 713, 713, 706, 704, 706, 713, 714, 714, 708, 706, 710, 715, 716, 716, 711, 710, 711, 716, 717, 717, 712, 711, 712, 717, 718, 718, 713, 712, 713, 718, 719, 719, 714, 713, 715, 720, 721, 721, 716, 715, 716, 721, 722, 722, 717, 716, 717, 722, 723, 723, 718, 717, 718, 723, 724, 724, 719, 718, 725, 726, 727, 727, 728, 725, 728, 727, 729, 729, 730, 728, 730, 729, 731, 731, 732, 730, 732, 731, 733, 733, 734, 732, 726, 735, 736, 736, 727, 726, 727, 736, 737, 737, 729, 727, 729, 737, 738, 738, 731, 729, 731, 738, 739, 739, 733, 731, 735, 740, 741, 741, 736, 735, 736, 741, 742, 742, 737, 736, 737, 742, 743, 743, 738, 737, 738, 743, 744, 744, 739, 738, 740, 745, 746, 746, 741, 740, 741, 746, 747, 747, 742, 741, 742, 747, 748, 748, 743, 742, 743, 748, 749, 749, 744, 743, 750, 751, 752, 752, 753, 750, 753, 752, 754, 754, 755, 753, 755, 754, 756, 756, 757, 755, 757, 756, 758, 758, 759, 757, 751, 760, 761, 761, 752, 751, 752, 761, 762, 762, 754, 752, 754, 762, 763, 763, 756, 754, 756, 763, 764, 764, 758, 756, 760, 765, 766, 766, 761, 760, 761, 766, 767, 767, 762, 761, 762, 767, 768, 768, 763, 762, 763, 768, 769, 769, 764, 763, 765, 770, 771, 771, 766, 765, 766, 771, 772, 772, 767, 766, 767, 772, 773, 773, 768, 767, 768, 773, 774, 774, 769, 768, 775, 776, 777, 777, 778, 775, 778, 777, 779, 779, 780, 778, 780, 779, 781, 781, 782, 780, 782, 781, 783, 783, 784, 782, 776, 785, 786, 786, 777, 776, 777, 786, 787, 787, 779, 777, 779, 787, 788, 788, 781, 779, 781, 788, 789, 789, 783, 781, 785, 790, 791, 791, 786, 785, 786, 791, 792, 792, 787, 786, 787, 792, 793, 793, 788, 787, 788, 793, 794, 794, 789, 788, 790, 795, 796, 796, 791, 790, 791, 796, 797, 797, 792, 791, 792, 797, 798, 798, 793, 792, 793, 798, 799, 799, 794, 793 ]);\n"
  },
  {
    "path": "src/js/third_party/webgl_teapot/webgl-debug.js",
    "content": "/*\n** Copyright (c) 2012 The Khronos Group Inc.\n**\n** Permission is hereby granted, free of charge, to any person obtaining a\n** copy of this software and/or associated documentation files (the\n** \"Materials\"), to deal in the Materials without restriction, including\n** without limitation the rights to use, copy, modify, merge, publish,\n** distribute, sublicense, and/or sell copies of the Materials, and to\n** permit persons to whom the Materials are furnished to do so, subject to\n** the following conditions:\n**\n** The above copyright notice and this permission notice shall be included\n** in all copies or substantial portions of the Materials.\n**\n** THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\n*/\n\n// Various functions for helping debug WebGL apps.\n\nWebGLDebugUtils = function() {\n\n/**\n * Wrapped logging function.\n * @param {string} msg Message to log.\n */\nvar log = function(msg) {\n  if (window.console && window.console.log) {\n    window.console.log(msg);\n  }\n};\n\n/**\n * Wrapped error logging function.\n * @param {string} msg Message to log.\n */\nvar error = function(msg) {\n  if (window.console && window.console.error) {\n    window.console.error(msg);\n  } else {\n    log(msg);\n  }\n};\n\n\n/**\n * Which arguments are enums based on the number of arguments to the function.\n * So\n *    'texImage2D': {\n *       9: { 0:true, 2:true, 6:true, 7:true },\n *       6: { 0:true, 2:true, 3:true, 4:true },\n *    },\n *\n * means if there are 9 arguments then 6 and 7 are enums, if there are 6\n * arguments 3 and 4 are enums\n *\n * @type {!Object.<number, !Object.<number, string>}\n */\nvar glValidEnumContexts = {\n  // Generic setters and getters\n\n  'enable': {1: { 0:true }},\n  'disable': {1: { 0:true }},\n  'getParameter': {1: { 0:true }},\n\n  // Rendering\n\n  'drawArrays': {3:{ 0:true }},\n  'drawElements': {4:{ 0:true, 2:true }},\n\n  // Shaders\n\n  'createShader': {1: { 0:true }},\n  'getShaderParameter': {2: { 1:true }},\n  'getProgramParameter': {2: { 1:true }},\n  'getShaderPrecisionFormat': {2: { 0: true, 1:true }},\n\n  // Vertex attributes\n\n  'getVertexAttrib': {2: { 1:true }},\n  'vertexAttribPointer': {6: { 2:true }},\n\n  // Textures\n\n  'bindTexture': {2: { 0:true }},\n  'activeTexture': {1: { 0:true }},\n  'getTexParameter': {2: { 0:true, 1:true }},\n  'texParameterf': {3: { 0:true, 1:true }},\n  'texParameteri': {3: { 0:true, 1:true, 2:true }},\n  'texImage2D': {\n     9: { 0:true, 2:true, 6:true, 7:true },\n     6: { 0:true, 2:true, 3:true, 4:true }\n  },\n  'texSubImage2D': {\n    9: { 0:true, 6:true, 7:true },\n    7: { 0:true, 4:true, 5:true }\n  },\n  'copyTexImage2D': {8: { 0:true, 2:true }},\n  'copyTexSubImage2D': {8: { 0:true }},\n  'generateMipmap': {1: { 0:true }},\n  'compressedTexImage2D': {7: { 0: true, 2:true }},\n  'compressedTexSubImage2D': {8: { 0: true, 6:true }},\n\n  // Buffer objects\n\n  'bindBuffer': {2: { 0:true }},\n  'bufferData': {3: { 0:true, 2:true }},\n  'bufferSubData': {3: { 0:true }},\n  'getBufferParameter': {2: { 0:true, 1:true }},\n\n  // Renderbuffers and framebuffers\n\n  'pixelStorei': {2: { 0:true, 1:true }},\n  'readPixels': {7: { 4:true, 5:true }},\n  'bindRenderbuffer': {2: { 0:true }},\n  'bindFramebuffer': {2: { 0:true }},\n  'checkFramebufferStatus': {1: { 0:true }},\n  'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},\n  'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},\n  'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},\n  'getRenderbufferParameter': {2: { 0:true, 1:true }},\n  'renderbufferStorage': {4: { 0:true, 1:true }},\n\n  // Frame buffer operations (clear, blend, depth test, stencil)\n\n  'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}},\n  'depthFunc': {1: { 0:true }},\n  'blendFunc': {2: { 0:true, 1:true }},\n  'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},\n  'blendEquation': {1: { 0:true }},\n  'blendEquationSeparate': {2: { 0:true, 1:true }},\n  'stencilFunc': {3: { 0:true }},\n  'stencilFuncSeparate': {4: { 0:true, 1:true }},\n  'stencilMaskSeparate': {2: { 0:true }},\n  'stencilOp': {3: { 0:true, 1:true, 2:true }},\n  'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},\n\n  // Culling\n\n  'cullFace': {1: { 0:true }},\n  'frontFace': {1: { 0:true }},\n\n  // ANGLE_instanced_arrays extension\n\n  'drawArraysInstancedANGLE': {4: { 0:true }},\n  'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},\n\n  // EXT_blend_minmax extension\n\n  'blendEquationEXT': {1: { 0:true }}\n};\n\n/**\n * Map of numbers to names.\n * @type {Object}\n */\nvar glEnums = null;\n\n/**\n * Map of names to numbers.\n * @type {Object}\n */\nvar enumStringToValue = null;\n\n/**\n * Initializes this module. Safe to call more than once.\n * @param {!WebGLRenderingContext} ctx A WebGL context. If\n *    you have more than one context it doesn't matter which one\n *    you pass in, it is only used to pull out constants.\n */\nfunction init(ctx) {\n  if (glEnums == null) {\n    glEnums = { };\n    enumStringToValue = { };\n    for (var propertyName in ctx) {\n      if (typeof ctx[propertyName] == 'number') {\n        glEnums[ctx[propertyName]] = propertyName;\n        enumStringToValue[propertyName] = ctx[propertyName];\n      }\n    }\n  }\n}\n\n/**\n * Checks the utils have been initialized.\n */\nfunction checkInit() {\n  if (glEnums == null) {\n    throw 'WebGLDebugUtils.init(ctx) not called';\n  }\n}\n\n/**\n * Returns true or false if value matches any WebGL enum\n * @param {*} value Value to check if it might be an enum.\n * @return {boolean} True if value matches one of the WebGL defined enums\n */\nfunction mightBeEnum(value) {\n  checkInit();\n  return (glEnums[value] !== undefined);\n}\n\n/**\n * Gets an string version of an WebGL enum.\n *\n * Example:\n *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());\n *\n * @param {number} value Value to return an enum for\n * @return {string} The string version of the enum.\n */\nfunction glEnumToString(value) {\n  checkInit();\n  var name = glEnums[value];\n  return (name !== undefined) ? (\"gl.\" + name) :\n      (\"/*UNKNOWN WebGL ENUM*/ 0x\" + value.toString(16) + \"\");\n}\n\n/**\n * Returns the string version of a WebGL argument.\n * Attempts to convert enum arguments to strings.\n * @param {string} functionName the name of the WebGL function.\n * @param {number} numArgs the number of arguments passed to the function.\n * @param {number} argumentIndx the index of the argument.\n * @param {*} value The value of the argument.\n * @return {string} The value as a string.\n */\nfunction glFunctionArgToString(functionName, numArgs, argumentIndex, value) {\n  var funcInfo = glValidEnumContexts[functionName];\n  if (funcInfo !== undefined) {\n    var funcInfo = funcInfo[numArgs];\n    if (funcInfo !== undefined) {\n      if (funcInfo[argumentIndex]) {\n        if (typeof funcInfo[argumentIndex] === 'object' &&\n            funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) {\n          var enums = funcInfo[argumentIndex]['enumBitwiseOr'];\n          var orResult = 0;\n          var orEnums = [];\n          for (var i = 0; i < enums.length; ++i) {\n            var enumValue = enumStringToValue[enums[i]];\n            if ((value & enumValue) !== 0) {\n              orResult |= enumValue;\n              orEnums.push(glEnumToString(enumValue));\n            }\n          }\n          if (orResult === value) {\n            return orEnums.join(' | ');\n          } else {\n            return glEnumToString(value);\n          }\n        } else {\n          return glEnumToString(value);\n        }\n      }\n    }\n  }\n  if (value === null) {\n    return \"null\";\n  } else if (value === undefined) {\n    return \"undefined\";\n  } else {\n    return value.toString();\n  }\n}\n\n/**\n * Converts the arguments of a WebGL function to a string.\n * Attempts to convert enum arguments to strings.\n *\n * @param {string} functionName the name of the WebGL function.\n * @param {number} args The arguments.\n * @return {string} The arguments as a string.\n */\nfunction glFunctionArgsToString(functionName, args) {\n  // apparently we can't do args.join(\",\");\n  var argStr = \"\";\n  var numArgs = args.length;\n  for (var ii = 0; ii < numArgs; ++ii) {\n    argStr += ((ii == 0) ? '' : ', ') +\n        glFunctionArgToString(functionName, numArgs, ii, args[ii]);\n  }\n  return argStr;\n};\n\n\nfunction makePropertyWrapper(wrapper, original, propertyName) {\n  //log(\"wrap prop: \" + propertyName);\n  wrapper.__defineGetter__(propertyName, function() {\n    return original[propertyName];\n  });\n  // TODO(gmane): this needs to handle properties that take more than\n  // one value?\n  wrapper.__defineSetter__(propertyName, function(value) {\n    //log(\"set: \" + propertyName);\n    original[propertyName] = value;\n  });\n}\n\n// Makes a function that calls a function on another object.\nfunction makeFunctionWrapper(original, functionName) {\n  //log(\"wrap fn: \" + functionName);\n  var f = original[functionName];\n  return function() {\n    //log(\"call: \" + functionName);\n    var result = f.apply(original, arguments);\n    return result;\n  };\n}\n\n/**\n * Given a WebGL context returns a wrapped context that calls\n * gl.getError after every command and calls a function if the\n * result is not gl.NO_ERROR.\n *\n * @param {!WebGLRenderingContext} ctx The webgl context to\n *        wrap.\n * @param {!function(err, funcName, args): void} opt_onErrorFunc\n *        The function to call when gl.getError returns an\n *        error. If not specified the default function calls\n *        console.log with a message.\n * @param {!function(funcName, args): void} opt_onFunc The\n *        function to call when each webgl function is called.\n *        You can use this to log all calls for example.\n * @param {!WebGLRenderingContext} opt_err_ctx The webgl context\n *        to call getError on if different than ctx.\n */\nfunction makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) {\n  opt_err_ctx = opt_err_ctx || ctx;\n  init(ctx);\n  opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {\n        // apparently we can't do args.join(\",\");\n        var argStr = \"\";\n        var numArgs = args.length;\n        for (var ii = 0; ii < numArgs; ++ii) {\n          argStr += ((ii == 0) ? '' : ', ') +\n              glFunctionArgToString(functionName, numArgs, ii, args[ii]);\n        }\n        error(\"WebGL error \"+ glEnumToString(err) + \" in \"+ functionName +\n              \"(\" + argStr + \")\");\n      };\n\n  // Holds booleans for each GL error so after we get the error ourselves\n  // we can still return it to the client app.\n  var glErrorShadow = { };\n\n  // Makes a function that calls a WebGL function and then calls getError.\n  function makeErrorWrapper(ctx, functionName) {\n    return function() {\n      if (opt_onFunc) {\n        opt_onFunc(functionName, arguments);\n      }\n      var result = ctx[functionName].apply(ctx, arguments);\n      var err = opt_err_ctx.getError();\n      if (err != 0) {\n        glErrorShadow[err] = true;\n        opt_onErrorFunc(err, functionName, arguments);\n      }\n      return result;\n    };\n  }\n\n  // Make a an object that has a copy of every property of the WebGL context\n  // but wraps all functions.\n  var wrapper = {};\n  for (var propertyName in ctx) {\n    if (typeof ctx[propertyName] == 'function') {\n      if (propertyName != 'getExtension') {\n        wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);\n      } else {\n        var wrapped = makeErrorWrapper(ctx, propertyName);\n        wrapper[propertyName] = function () {\n          var result = wrapped.apply(ctx, arguments);\n          return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx);\n        };\n      }\n    } else {\n      makePropertyWrapper(wrapper, ctx, propertyName);\n    }\n  }\n\n  // Override the getError function with one that returns our saved results.\n  wrapper.getError = function() {\n    for (var err in glErrorShadow) {\n      if (glErrorShadow.hasOwnProperty(err)) {\n        if (glErrorShadow[err]) {\n          glErrorShadow[err] = false;\n          return err;\n        }\n      }\n    }\n    return ctx.NO_ERROR;\n  };\n\n  return wrapper;\n}\n\nfunction resetToInitialState(ctx) {\n  var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);\n  var tmp = ctx.createBuffer();\n  ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);\n  for (var ii = 0; ii < numAttribs; ++ii) {\n    ctx.disableVertexAttribArray(ii);\n    ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);\n    ctx.vertexAttrib1f(ii, 0);\n  }\n  ctx.deleteBuffer(tmp);\n\n  var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);\n  for (var ii = 0; ii < numTextureUnits; ++ii) {\n    ctx.activeTexture(ctx.TEXTURE0 + ii);\n    ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);\n    ctx.bindTexture(ctx.TEXTURE_2D, null);\n  }\n\n  ctx.activeTexture(ctx.TEXTURE0);\n  ctx.useProgram(null);\n  ctx.bindBuffer(ctx.ARRAY_BUFFER, null);\n  ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);\n  ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);\n  ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);\n  ctx.disable(ctx.BLEND);\n  ctx.disable(ctx.CULL_FACE);\n  ctx.disable(ctx.DEPTH_TEST);\n  ctx.disable(ctx.DITHER);\n  ctx.disable(ctx.SCISSOR_TEST);\n  ctx.blendColor(0, 0, 0, 0);\n  ctx.blendEquation(ctx.FUNC_ADD);\n  ctx.blendFunc(ctx.ONE, ctx.ZERO);\n  ctx.clearColor(0, 0, 0, 0);\n  ctx.clearDepth(1);\n  ctx.clearStencil(-1);\n  ctx.colorMask(true, true, true, true);\n  ctx.cullFace(ctx.BACK);\n  ctx.depthFunc(ctx.LESS);\n  ctx.depthMask(true);\n  ctx.depthRange(0, 1);\n  ctx.frontFace(ctx.CCW);\n  ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);\n  ctx.lineWidth(1);\n  ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);\n  ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);\n  ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);\n  ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n  // TODO: Delete this IF.\n  if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {\n    ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);\n  }\n  ctx.polygonOffset(0, 0);\n  ctx.sampleCoverage(1, false);\n  ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);\n  ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);\n  ctx.stencilMask(0xFFFFFFFF);\n  ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);\n  ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);\n  ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);\n\n  // TODO: This should NOT be needed but Firefox fails with 'hint'\n  while(ctx.getError());\n}\n\nfunction makeLostContextSimulatingCanvas(canvas) {\n  var unwrappedContext_;\n  var wrappedContext_;\n  var onLost_ = [];\n  var onRestored_ = [];\n  var wrappedContext_ = {};\n  var contextId_ = 1;\n  var contextLost_ = false;\n  var resourceId_ = 0;\n  var resourceDb_ = [];\n  var numCallsToLoseContext_ = 0;\n  var numCalls_ = 0;\n  var canRestore_ = false;\n  var restoreTimeout_ = 0;\n\n  // Holds booleans for each GL error so can simulate errors.\n  var glErrorShadow_ = { };\n\n  canvas.getContext = function(f) {\n    return function() {\n      var ctx = f.apply(canvas, arguments);\n      // Did we get a context and is it a WebGL context?\n      if (ctx instanceof WebGLRenderingContext) {\n        if (ctx != unwrappedContext_) {\n          if (unwrappedContext_) {\n            throw \"got different context\"\n          }\n          unwrappedContext_ = ctx;\n          wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_);\n        }\n        return wrappedContext_;\n      }\n      return ctx;\n    }\n  }(canvas.getContext);\n\n  function wrapEvent(listener) {\n    if (typeof(listener) == \"function\") {\n      return listener;\n    } else {\n      return function(info) {\n        listener.handleEvent(info);\n      }\n    }\n  }\n\n  var addOnContextLostListener = function(listener) {\n    onLost_.push(wrapEvent(listener));\n  };\n\n  var addOnContextRestoredListener = function(listener) {\n    onRestored_.push(wrapEvent(listener));\n  };\n\n\n  function wrapAddEventListener(canvas) {\n    var f = canvas.addEventListener;\n    canvas.addEventListener = function(type, listener, bubble) {\n      switch (type) {\n        case 'webglcontextlost':\n          addOnContextLostListener(listener);\n          break;\n        case 'webglcontextrestored':\n          addOnContextRestoredListener(listener);\n          break;\n        default:\n          f.apply(canvas, arguments);\n      }\n    };\n  }\n\n  wrapAddEventListener(canvas);\n\n  canvas.loseContext = function() {\n    if (!contextLost_) {\n      contextLost_ = true;\n      numCallsToLoseContext_ = 0;\n      ++contextId_;\n      while (unwrappedContext_.getError());\n      clearErrors();\n      glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true;\n      var event = makeWebGLContextEvent(\"context lost\");\n      var callbacks = onLost_.slice();\n      setTimeout(function() {\n          //log(\"numCallbacks:\" + callbacks.length);\n          for (var ii = 0; ii < callbacks.length; ++ii) {\n            //log(\"calling callback:\" + ii);\n            callbacks[ii](event);\n          }\n          if (restoreTimeout_ >= 0) {\n            setTimeout(function() {\n                canvas.restoreContext();\n              }, restoreTimeout_);\n          }\n        }, 0);\n    }\n  };\n\n  canvas.restoreContext = function() {\n    if (contextLost_) {\n      if (onRestored_.length) {\n        setTimeout(function() {\n            if (!canRestore_) {\n              throw \"can not restore. webglcontestlost listener did not call event.preventDefault\";\n            }\n            freeResources();\n            resetToInitialState(unwrappedContext_);\n            contextLost_ = false;\n            numCalls_ = 0;\n            canRestore_ = false;\n            var callbacks = onRestored_.slice();\n            var event = makeWebGLContextEvent(\"context restored\");\n            for (var ii = 0; ii < callbacks.length; ++ii) {\n              callbacks[ii](event);\n            }\n          }, 0);\n      }\n    }\n  };\n\n  canvas.loseContextInNCalls = function(numCalls) {\n    if (contextLost_) {\n      throw \"You can not ask a lost contet to be lost\";\n    }\n    numCallsToLoseContext_ = numCalls_ + numCalls;\n  };\n\n  canvas.getNumCalls = function() {\n    return numCalls_;\n  };\n\n  canvas.setRestoreTimeout = function(timeout) {\n    restoreTimeout_ = timeout;\n  };\n\n  function isWebGLObject(obj) {\n    //return false;\n    return (obj instanceof WebGLBuffer ||\n            obj instanceof WebGLFramebuffer ||\n            obj instanceof WebGLProgram ||\n            obj instanceof WebGLRenderbuffer ||\n            obj instanceof WebGLShader ||\n            obj instanceof WebGLTexture);\n  }\n\n  function checkResources(args) {\n    for (var ii = 0; ii < args.length; ++ii) {\n      var arg = args[ii];\n      if (isWebGLObject(arg)) {\n        return arg.__webglDebugContextLostId__ == contextId_;\n      }\n    }\n    return true;\n  }\n\n  function clearErrors() {\n    var k = Object.keys(glErrorShadow_);\n    for (var ii = 0; ii < k.length; ++ii) {\n      delete glErrorShadow_[k];\n    }\n  }\n\n  function loseContextIfTime() {\n    ++numCalls_;\n    if (!contextLost_) {\n      if (numCallsToLoseContext_ == numCalls_) {\n        canvas.loseContext();\n      }\n    }\n  }\n\n  // Makes a function that simulates WebGL when out of context.\n  function makeLostContextFunctionWrapper(ctx, functionName) {\n    var f = ctx[functionName];\n    return function() {\n      // log(\"calling:\" + functionName);\n      // Only call the functions if the context is not lost.\n      loseContextIfTime();\n      if (!contextLost_) {\n        //if (!checkResources(arguments)) {\n        //  glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true;\n        //  return;\n        //}\n        var result = f.apply(ctx, arguments);\n        return result;\n      }\n    };\n  }\n\n  function freeResources() {\n    for (var ii = 0; ii < resourceDb_.length; ++ii) {\n      var resource = resourceDb_[ii];\n      if (resource instanceof WebGLBuffer) {\n        unwrappedContext_.deleteBuffer(resource);\n      } else if (resource instanceof WebGLFramebuffer) {\n        unwrappedContext_.deleteFramebuffer(resource);\n      } else if (resource instanceof WebGLProgram) {\n        unwrappedContext_.deleteProgram(resource);\n      } else if (resource instanceof WebGLRenderbuffer) {\n        unwrappedContext_.deleteRenderbuffer(resource);\n      } else if (resource instanceof WebGLShader) {\n        unwrappedContext_.deleteShader(resource);\n      } else if (resource instanceof WebGLTexture) {\n        unwrappedContext_.deleteTexture(resource);\n      }\n    }\n  }\n\n  function makeWebGLContextEvent(statusMessage) {\n    return {\n      statusMessage: statusMessage,\n      preventDefault: function() {\n          canRestore_ = true;\n        }\n    };\n  }\n\n  return canvas;\n\n  function makeLostContextSimulatingContext(ctx) {\n    // copy all functions and properties to wrapper\n    for (var propertyName in ctx) {\n      if (typeof ctx[propertyName] == 'function') {\n         wrappedContext_[propertyName] = makeLostContextFunctionWrapper(\n             ctx, propertyName);\n       } else {\n         makePropertyWrapper(wrappedContext_, ctx, propertyName);\n       }\n    }\n\n    // Wrap a few functions specially.\n    wrappedContext_.getError = function() {\n      loseContextIfTime();\n      if (!contextLost_) {\n        var err;\n        while (err = unwrappedContext_.getError()) {\n          glErrorShadow_[err] = true;\n        }\n      }\n      for (var err in glErrorShadow_) {\n        if (glErrorShadow_[err]) {\n          delete glErrorShadow_[err];\n          return err;\n        }\n      }\n      return wrappedContext_.NO_ERROR;\n    };\n\n    var creationFunctions = [\n      \"createBuffer\",\n      \"createFramebuffer\",\n      \"createProgram\",\n      \"createRenderbuffer\",\n      \"createShader\",\n      \"createTexture\"\n    ];\n    for (var ii = 0; ii < creationFunctions.length; ++ii) {\n      var functionName = creationFunctions[ii];\n      wrappedContext_[functionName] = function(f) {\n        return function() {\n          loseContextIfTime();\n          if (contextLost_) {\n            return null;\n          }\n          var obj = f.apply(ctx, arguments);\n          obj.__webglDebugContextLostId__ = contextId_;\n          resourceDb_.push(obj);\n          return obj;\n        };\n      }(ctx[functionName]);\n    }\n\n    var functionsThatShouldReturnNull = [\n      \"getActiveAttrib\",\n      \"getActiveUniform\",\n      \"getBufferParameter\",\n      \"getContextAttributes\",\n      \"getAttachedShaders\",\n      \"getFramebufferAttachmentParameter\",\n      \"getParameter\",\n      \"getProgramParameter\",\n      \"getProgramInfoLog\",\n      \"getRenderbufferParameter\",\n      \"getShaderParameter\",\n      \"getShaderInfoLog\",\n      \"getShaderSource\",\n      \"getTexParameter\",\n      \"getUniform\",\n      \"getUniformLocation\",\n      \"getVertexAttrib\"\n    ];\n    for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {\n      var functionName = functionsThatShouldReturnNull[ii];\n      wrappedContext_[functionName] = function(f) {\n        return function() {\n          loseContextIfTime();\n          if (contextLost_) {\n            return null;\n          }\n          return f.apply(ctx, arguments);\n        }\n      }(wrappedContext_[functionName]);\n    }\n\n    var isFunctions = [\n      \"isBuffer\",\n      \"isEnabled\",\n      \"isFramebuffer\",\n      \"isProgram\",\n      \"isRenderbuffer\",\n      \"isShader\",\n      \"isTexture\"\n    ];\n    for (var ii = 0; ii < isFunctions.length; ++ii) {\n      var functionName = isFunctions[ii];\n      wrappedContext_[functionName] = function(f) {\n        return function() {\n          loseContextIfTime();\n          if (contextLost_) {\n            return false;\n          }\n          return f.apply(ctx, arguments);\n        }\n      }(wrappedContext_[functionName]);\n    }\n\n    wrappedContext_.checkFramebufferStatus = function(f) {\n      return function() {\n        loseContextIfTime();\n        if (contextLost_) {\n          return wrappedContext_.FRAMEBUFFER_UNSUPPORTED;\n        }\n        return f.apply(ctx, arguments);\n      };\n    }(wrappedContext_.checkFramebufferStatus);\n\n    wrappedContext_.getAttribLocation = function(f) {\n      return function() {\n        loseContextIfTime();\n        if (contextLost_) {\n          return -1;\n        }\n        return f.apply(ctx, arguments);\n      };\n    }(wrappedContext_.getAttribLocation);\n\n    wrappedContext_.getVertexAttribOffset = function(f) {\n      return function() {\n        loseContextIfTime();\n        if (contextLost_) {\n          return 0;\n        }\n        return f.apply(ctx, arguments);\n      };\n    }(wrappedContext_.getVertexAttribOffset);\n\n    wrappedContext_.isContextLost = function() {\n      return contextLost_;\n    };\n\n    return wrappedContext_;\n  }\n}\n\nreturn {\n  /**\n   * Initializes this module. Safe to call more than once.\n   * @param {!WebGLRenderingContext} ctx A WebGL context. If\n   *    you have more than one context it doesn't matter which one\n   *    you pass in, it is only used to pull out constants.\n   */\n  'init': init,\n\n  /**\n   * Returns true or false if value matches any WebGL enum\n   * @param {*} value Value to check if it might be an enum.\n   * @return {boolean} True if value matches one of the WebGL defined enums\n   */\n  'mightBeEnum': mightBeEnum,\n\n  /**\n   * Gets an string version of an WebGL enum.\n   *\n   * Example:\n   *   WebGLDebugUtil.init(ctx);\n   *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());\n   *\n   * @param {number} value Value to return an enum for\n   * @return {string} The string version of the enum.\n   */\n  'glEnumToString': glEnumToString,\n\n  /**\n   * Converts the argument of a WebGL function to a string.\n   * Attempts to convert enum arguments to strings.\n   *\n   * Example:\n   *   WebGLDebugUtil.init(ctx);\n   *   var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D);\n   *\n   * would return 'TEXTURE_2D'\n   *\n   * @param {string} functionName the name of the WebGL function.\n   * @param {number} numArgs The number of arguments\n   * @param {number} argumentIndx the index of the argument.\n   * @param {*} value The value of the argument.\n   * @return {string} The value as a string.\n   */\n  'glFunctionArgToString': glFunctionArgToString,\n\n  /**\n   * Converts the arguments of a WebGL function to a string.\n   * Attempts to convert enum arguments to strings.\n   *\n   * @param {string} functionName the name of the WebGL function.\n   * @param {number} args The arguments.\n   * @return {string} The arguments as a string.\n   */\n  'glFunctionArgsToString': glFunctionArgsToString,\n\n  /**\n   * Given a WebGL context returns a wrapped context that calls\n   * gl.getError after every command and calls a function if the\n   * result is not NO_ERROR.\n   *\n   * You can supply your own function if you want. For example, if you'd like\n   * an exception thrown on any GL error you could do this\n   *\n   *    function throwOnGLError(err, funcName, args) {\n   *      throw WebGLDebugUtils.glEnumToString(err) +\n   *            \" was caused by call to \" + funcName;\n   *    };\n   *\n   *    ctx = WebGLDebugUtils.makeDebugContext(\n   *        canvas.getContext(\"webgl\"), throwOnGLError);\n   *\n   * @param {!WebGLRenderingContext} ctx The webgl context to wrap.\n   * @param {!function(err, funcName, args): void} opt_onErrorFunc The function\n   *     to call when gl.getError returns an error. If not specified the default\n   *     function calls console.log with a message.\n   * @param {!function(funcName, args): void} opt_onFunc The\n   *     function to call when each webgl function is called. You\n   *     can use this to log all calls for example.\n   */\n  'makeDebugContext': makeDebugContext,\n\n  /**\n   * Given a canvas element returns a wrapped canvas element that will\n   * simulate lost context. The canvas returned adds the following functions.\n   *\n   * loseContext:\n   *   simulates a lost context event.\n   *\n   * restoreContext:\n   *   simulates the context being restored.\n   *\n   * lostContextInNCalls:\n   *   loses the context after N gl calls.\n   *\n   * getNumCalls:\n   *   tells you how many gl calls there have been so far.\n   *\n   * setRestoreTimeout:\n   *   sets the number of milliseconds until the context is restored\n   *   after it has been lost. Defaults to 0. Pass -1 to prevent\n   *   automatic restoring.\n   *\n   * @param {!Canvas} canvas The canvas element to wrap.\n   */\n  'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas,\n\n  /**\n   * Resets a context to the initial state.\n   * @param {!WebGLRenderingContext} ctx The webgl context to\n   *     reset.\n   */\n  'resetToInitialState': resetToInitialState\n};\n\n}();\n\n"
  },
  {
    "path": "src/js/third_party/webgl_teapot/webgl-utils.js",
    "content": "/*\n * Copyright 2010, Google Inc.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *     * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *     * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n\n/**\n * @fileoverview This file contains functions every webgl program will need\n * a version of one way or another.\n *\n * Instead of setting up a context manually it is recommended to\n * use. This will check for success or failure. On failure it\n * will attempt to present an approriate message to the user.\n *\n *       gl = WebGLUtils.setupWebGL(canvas);\n *\n * For animated WebGL apps use of setTimeout or setInterval are\n * discouraged. It is recommended you structure your rendering\n * loop like this.\n *\n *       function render() {\n *         window.requestAnimFrame(render, canvas);\n *\n *         // do rendering\n *         ...\n *       }\n *       render();\n *\n * This will call your rendering function up to the refresh rate\n * of your display but will stop rendering if your app is not\n * visible.\n */\n\nWebGLUtils = function() {\n\n/**\n * Creates the HTLM for a failure message\n * @param {string} canvasContainerId id of container of th\n *        canvas.\n * @return {string} The html.\n */\nvar makeFailHTML = function(msg) {\n  return '' +\n    '<table style=\"background-color: #8CE; width: 100%; height: 100%;\"><tr>' +\n    '<td align=\"center\">' +\n    '<div style=\"display: table-cell; vertical-align: middle;\">' +\n    '<div style=\"\">' + msg + '</div>' +\n    '</div>' +\n    '</td></tr></table>';\n};\n\n/**\n * Mesasge for getting a webgl browser\n * @type {string}\n */\nvar GET_A_WEBGL_BROWSER = '' +\n  'This page requires a browser that supports WebGL.<br/>' +\n  '<a href=\"http://get.webgl.org\">Click here to upgrade your browser.</a>';\n\n/**\n * Mesasge for need better hardware\n * @type {string}\n */\nvar OTHER_PROBLEM = '' +\n  \"It doesn't appear your computer can support WebGL.<br/>\" +\n  '<a href=\"http://get.webgl.org/troubleshooting/\">Click here for more information.</a>';\n\n/**\n * Creates a webgl context. If creation fails it will\n * change the contents of the container of the <canvas>\n * tag to an error message with the correct links for WebGL.\n * @param {Element} canvas. The canvas element to create a\n *     context from.\n * @param {WebGLContextCreationAttirbutes} opt_attribs Any\n *     creation attributes you want to pass in.\n * @return {WebGLRenderingContext} The created context.\n */\nvar setupWebGL = function(canvas, opt_attribs) {\n  function showLink(str) {\n    var container = canvas.parentNode;\n    if (container) {\n      container.innerHTML = makeFailHTML(str);\n    }\n  };\n\n  if (!window.WebGLRenderingContext) {\n    showLink(GET_A_WEBGL_BROWSER);\n    return null;\n  }\n\n  var context = create3DContext(canvas, opt_attribs);\n  if (!context) {\n    showLink(OTHER_PROBLEM);\n  }\n  return context;\n};\n\n/**\n * Creates a webgl context.\n * @param {!Canvas} canvas The canvas tag to get context\n *     from. If one is not passed in one will be created.\n * @return {!WebGLContext} The created context.\n */\nvar create3DContext = function(canvas, opt_attribs) {\n  var names = [\"webgl\", \"experimental-webgl\", \"webkit-3d\", \"moz-webgl\"];\n  var context = null;\n  for (var ii = 0; ii < names.length; ++ii) {\n    try {\n      context = canvas.getContext(names[ii], opt_attribs);\n    } catch(e) {}\n    if (context) {\n      break;\n    }\n  }\n  return context;\n};\n\nreturn {\n  create3DContext: create3DContext,\n  setupWebGL: setupWebGL\n};\n}();\n\n/**\n * Provides requestAnimationFrame in a cross browser way.\n */\nwindow.requestAnimFrame = (function() {\n  return window.requestAnimationFrame ||\n         window.webkitRequestAnimationFrame ||\n         window.mozRequestAnimationFrame ||\n         window.oRequestAnimationFrame ||\n         window.msRequestAnimationFrame ||\n         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {\n           return window.setTimeout(callback, 1000/60);\n         };\n})();\n\n/**\n * Provides cancelAnimationFrame in a cross browser way.\n */\nwindow.cancelAnimFrame = (function() {\n  return window.cancelAnimationFrame ||\n         window.webkitCancelAnimationFrame ||\n         window.mozCancelAnimationFrame ||\n         window.oCancelAnimationFrame ||\n         window.msCancelAnimationFrame ||\n         window.clearTimeout;\n})();\n\n\n"
  },
  {
    "path": "src/js/videopipe.js",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n//\n// A \"videopipe\" abstraction on top of WebRTC.\n//\n// The usage of this abstraction:\n// var pipe = new VideoPipe(mediastream, handlerFunction);\n// handlerFunction = function(mediastream) {\n//   do_something\n// }\n// pipe.close();\n//\n// The VideoPipe will set up 2 PeerConnections, connect them to each\n// other, and call HandlerFunction when the stream is available in the\n// second PeerConnection.\n//\n\nfunction errorHandler(context) {\n  return function(error) {\n    trace('Failure in ' + context + ': ' + error.toString);\n  };\n}\n\n// eslint-disable-next-line no-unused-vars\nfunction successHandler(context) {\n  return function() {\n    trace('Success in ' + context);\n  };\n}\n\nfunction noAction() {\n}\n\n\nfunction VideoPipe(stream, handler) {\n  let servers = null;\n  let pc1 = new RTCPeerConnection(servers);\n  let pc2 = new RTCPeerConnection(servers);\n\n  pc1.addStream(stream);\n  pc1.onicecandidate = function(event) {\n    if (event.candidate) {\n      pc2.addIceCandidate(new RTCIceCandidate(event.candidate),\n                          noAction, errorHandler('AddIceCandidate'));\n    }\n  };\n  pc2.onicecandidate = function(event) {\n    if (event.candidate) {\n      pc1.addIceCandidate(new RTCIceCandidate(event.candidate),\n                          noAction, errorHandler('AddIceCandidate'));\n    }\n  };\n  pc2.onaddstream = function(e) {\n    handler(e.stream);\n  };\n  pc1.createOffer(function(desc) {\n    pc1.setLocalDescription(desc);\n    pc2.setRemoteDescription(desc);\n    pc2.createAnswer(function(desc2) {\n      pc2.setLocalDescription(desc2);\n      pc1.setRemoteDescription(desc2);\n    }, errorHandler('pc2.createAnswer'));\n  }, errorHandler('pc1.createOffer'));\n  this.pc1 = pc1;\n  this.pc2 = pc2;\n}\n\nVideoPipe.prototype.close = function() {\n  this.pc1.close();\n  this.pc2.close();\n};\n"
  },
  {
    "path": "test/download-browsers.js",
    "content": "const {buildDriver} = require('./webdriver');\n// Download the browser(s).\nasync function download() {\n  if (process.env.BROWSER_A && process.env.BROWSER_B) {\n    (await buildDriver(process.env.BROWSER_A)).quit();\n    (await buildDriver(process.env.BROWSER_B)).quit();\n  } else {\n    (await buildDriver()).quit();\n  }\n}\ndownload();\n"
  },
  {
    "path": "test/interop/connection.test.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nconst {buildDriver} = require('../webdriver');\nconst {PeerConnection, MediaDevices} = require('../webrtcclient');\nconst steps = require('../steps');\n\nconst browserA = process.env.BROWSER_A || 'chrome';\nconst browserB = process.env.BROWSER_B || 'chrome';\n\ndescribe(`basic interop test ${browserA} => ${browserB}`, function() {\n    let drivers;\n    let clients;\n    beforeAll(async () => {\n        const options = {\n            version: process.env.BVER || 'stable',\n            browserLogging: true,\n        }\n        drivers = [\n            await buildDriver(browserA, options),\n            await buildDriver(browserB, options),\n        ];\n        clients = drivers.map(driver => {\n            return {\n                connection: new PeerConnection(driver),\n                mediaDevices: new MediaDevices(driver),\n            };\n        });\n    });\n    afterAll(async () => {\n        await drivers.map(driver => driver.close());\n    });\n\n    it('establishes a connection', async () => {\n        await Promise.all(drivers); // timeouts in before(Each)?\n        await steps.step(drivers, (d) => d.get('https://webrtc.github.io/samples/emptypage.html'), 'Empty page loaded');\n        await steps.step(clients, (client) => client.connection.create(), 'Created RTCPeerConnection');\n        await steps.step(clients, async (client) => {\n            const stream = await client.mediaDevices.getUserMedia({audio: true, video: true});\n            return Promise.all(stream.getTracks().map(async track => {\n                return client.connection.addTrack(track, stream);\n            }));\n        }, 'Acquired and added audio/video stream');\n        const offerWithCandidates = await clients[0].connection.setLocalDescription();\n        await clients[1].connection.setRemoteDescription(offerWithCandidates);\n        const answerWithCandidates = await clients[1].connection.setLocalDescription();\n        await clients[0].connection.setRemoteDescription(answerWithCandidates);\n\n        await steps.step(drivers, (d) => steps.waitNVideosExist(d, 1), 'Video elements exist');\n        await steps.step(drivers, steps.waitAllVideosHaveEnoughData, 'Video elements have enough data');\n    }, 30000);\n}, 90000);\n"
  },
  {
    "path": "test/steps.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nconst TIMEOUT = 10000;\n\nfunction step(drivers, cb, logMessage) {\n  return Promise.all(drivers.map(driver => {\n    return cb(driver);\n  })).then(() => {\n    if (logMessage) {\n      console.log(logMessage);\n    }\n  });\n}\nfunction waitNVideosExist(driver, n) {\n  return driver.wait(() => {\n    return driver.executeScript(n => document.querySelectorAll('video').length === n, n);\n  }, TIMEOUT);\n}\n\nfunction waitAllVideosHaveEnoughData(driver) {\n  return driver.wait(() => {\n    return driver.executeScript(() => {\n      const videos = document.querySelectorAll('video');\n      let ready = 0;\n      for (let i = 0; i < videos.length; i++) {\n        if (videos[i].readyState >= videos[i].HAVE_ENOUGH_DATA) {\n          ready++;\n        }\n      }\n      return ready === videos.length;\n    });\n  }, TIMEOUT);\n}\n\nmodule.exports = {\n  step,\n  waitNVideosExist,\n  waitAllVideosHaveEnoughData,\n};\n"
  },
  {
    "path": "test/webdriver.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\nconst os = require('os');\nconst path = require('path');\n\nconst webdriver = require('selenium-webdriver');\nconst chrome = require('selenium-webdriver/chrome');\nconst firefox = require('selenium-webdriver/firefox');\nconst safari = require('selenium-webdriver/safari');\n\nconst puppeteerBrowsers = require('@puppeteer/browsers');\n\nasync function download(browser, version, cacheDir, platform) {\n  const buildId = await puppeteerBrowsers\n      .resolveBuildId(browser, platform, version);\n  await puppeteerBrowsers.install({\n    browser,\n    buildId,\n    cacheDir,\n    platform\n  });\n  return buildId;\n}\nconst cacheDir = path.join(process.cwd(), 'browsers');\n\nif (os.platform() === 'win32') {\n  process.env.PATH += ';' + process.cwd() + '\\\\node_modules\\\\chromedriver\\\\lib\\\\chromedriver\\\\';\n  process.env.PATH += ';' + process.cwd() + '\\\\node_modules\\\\geckodriver';\n} else {\n  process.env.PATH += ':node_modules/.bin';\n}\n\nfunction mapVersion(browser, version) {\n  const versionMap = {\n    chrome: {\n      unstable: 'canary',\n    },\n    firefox: {\n      unstable: 'nightly',\n    }\n  };\n  return (versionMap[browser] || {})[version] || version;\n}\n\nasync function buildDriver(browser = process.env.BROWSER || 'chrome', options = {version: process.env.BVER}) {\n  const version = mapVersion(browser, options.version);\n  const platform = puppeteerBrowsers.detectBrowserPlatform();\n\n  const buildId = await download(browser, version || 'stable',\n      cacheDir, platform);\n\n  // Chrome options.\n  const chromeOptions = new chrome.Options()\n      .addArguments('allow-insecure-localhost')\n      .addArguments('use-fake-device-for-media-stream')\n      .addArguments('allow-file-access-from-files');\n  if (options.chromeFlags) {\n    options.chromeFlags.forEach((flag) => chromeOptions.addArguments(flag));\n  }\n  if (options.chromepath) {\n    chromeOptions.setChromeBinaryPath(options.chromepath);\n  } else {\n    chromeOptions.setChromeBinaryPath(puppeteerBrowsers\n        .computeExecutablePath({browser, buildId, cacheDir, platform}));\n  }\n\n  if (!options.devices || options.headless) {\n    // GUM doesn't work in headless mode so we need this. See\n    // https://bugs.chromium.org/p/chromium/issues/detail?id=776649\n    chromeOptions.addArguments('use-fake-ui-for-media-stream');\n  } else {\n    // see https://bugs.chromium.org/p/chromium/issues/detail?id=459532#c22\n    const domain = 'https://' + (options.devices.domain || 'localhost') + ':' + (options.devices.port || 443) + ',*';\n    const exceptions = {\n      media_stream_mic: {},\n      media_stream_camera: {},\n    };\n\n    exceptions.media_stream_mic[domain] = {\n      last_used: Date.now(),\n      setting: options.devices.audio ? 1 : 2 // 0: ask, 1: allow, 2: denied\n    };\n    exceptions.media_stream_camera[domain] = {\n      last_used: Date.now(),\n      setting: options.devices.video ? 1 : 2\n    };\n\n    chromeOptions.setUserPreferences({\n      profile: {\n        content_settings: {\n          exceptions: exceptions\n        }\n      }\n    });\n  }\n\n  // Safari options.\n  const safariOptions = new safari.Options();\n  safariOptions.setTechnologyPreview(version === 'unstable');\n\n  // Firefox options.\n  const firefoxOptions = new firefox.Options();\n  let firefoxPath = firefox.Channel.RELEASE;\n  if (options.firefoxpath) {\n    firefoxPath = options.firefoxpath;\n  } else {\n    firefoxPath = puppeteerBrowsers\n        .computeExecutablePath({browser, buildId, cacheDir, platform});\n  }\n  if (options.headless) {\n    firefoxOptions.addArguments('-headless');\n  }\n  firefoxOptions.setBinary(firefoxPath);\n  firefoxOptions.setPreference('media.navigator.streams.fake', true);\n  firefoxOptions.setPreference('media.navigator.permission.disabled', true);\n\n  const driver = new webdriver.Builder()\n      .setChromeOptions(chromeOptions)\n      .setSafariOptions(safariOptions)\n      .setFirefoxOptions(firefoxOptions)\n      .forBrowser(browser)\n      .setChromeService(\n          new chrome.ServiceBuilder().addArguments('--disable-build-check')\n      );\n\n  if (browser === 'firefox') {\n    driver.getCapabilities().set('marionette', true);\n    driver.getCapabilities().set('acceptInsecureCerts', true);\n  }\n  return driver.build();\n}\n\nmodule.exports = {\n  buildDriver,\n};\n"
  },
  {
    "path": "test/webrtcclient.js",
    "content": "/*\n *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree.\n */\n// Disable no-undef since this file is a mix of code executed\n// in JS and the browser.\n/* eslint no-undef: 0 */\nclass MediaStream {\n  constructor(tracks = []) {\n    this.tracks = tracks;\n    this.id = 0;\n  }\n\n  getTracks() {\n    return this.tracks;\n  }\n\n  getAudioTracks() {\n    return this.getTracks().filter(t => t.kind === 'audio');\n  }\n\n  getVideoTracks() {\n    return this.getTracks().filter(t => t.kind === 'video');\n  }\n}\n\nclass MediaDevices {\n  constructor(driver) {\n    this.driver = driver;\n  }\n\n  getUserMedia(constraints) {\n    return this.driver.executeAsyncScript((constraints) => {\n      const callback = arguments[arguments.length - 1];\n      if (!window.localStreams) {\n        window.localStreams = {};\n      }\n\n      return navigator.mediaDevices.getUserMedia(constraints)\n          .then((stream) => {\n            window.localStreams[stream.id] = stream;\n            callback({id: stream.id, tracks: stream.getTracks().map((t) => {\n              return {id: t.id, kind: t.kind};\n            })});\n          }, (e) => callback(e));\n    }, constraints || {audio: true, video: true})\n        .then((streamObj) => {\n          const stream = new MediaStream(streamObj.tracks);\n          stream.id = streamObj.id;\n          return stream;\n        });\n  }\n}\n\nclass PeerConnection {\n  constructor(driver) {\n    this.driver = driver;\n  }\n\n  create(rtcConfiguration) {\n    return this.driver.executeScript(rtcConfiguration => {\n      window.pc = new RTCPeerConnection(rtcConfiguration);\n    }, rtcConfiguration);\n  }\n\n  addTrack(track, stream) {\n    return this.driver.executeScript((track, stream) => {\n      stream = localStreams[stream.id];\n      track = stream.getTracks().find(t => t.id === track.id);\n      pc.addTrack(track, stream);\n    }, track, stream);\n  }\n\n  createOffer(offerOptions) {\n    return this.driver.executeAsyncScript((offerOptions) => {\n      const callback = arguments[arguments.length - 1];\n\n      pc.createOffer(offerOptions)\n          .then(callback, callback);\n    }, offerOptions);\n  }\n  createAnswer() {\n    return this.driver.executeAsyncScript(() => {\n      const callback = arguments[arguments.length - 1];\n\n      pc.createAnswer()\n          .then(callback, callback);\n    });\n  }\n\n  // resolves with non-trickle description including candidates.\n  setLocalDescription(desc) {\n    return this.driver.executeAsyncScript((desc) => {\n      const callback = arguments[arguments.length - 1];\n\n      pc.onicecandidate = (event) => {\n        console.log('candidate', event.candidate);\n        if (!event.candidate) {\n          pc.onicecandidate = null;\n          callback(pc.localDescription);\n        }\n      };\n      pc.setLocalDescription(desc)\n          .catch(callback);\n    }, desc);\n  }\n\n  // TODO: this implicitly creates video elements, is that deseriable?\n  setRemoteDescription(desc) {\n    return this.driver.executeAsyncScript(function(desc) {\n      const callback = arguments[arguments.length - 1];\n\n      pc.ontrack = function(event) {\n        const id = event.streams[0].id;\n        if (document.getElementById('video-' + id)) {\n          return;\n        }\n        const video = document.createElement('video');\n        video.id = 'video-' + id;\n        video.autoplay = true;\n        video.srcObject = event.streams[0];\n        document.body.appendChild(video);\n      };\n      pc.setRemoteDescription(new RTCSessionDescription(desc))\n          .then(callback, callback);\n    }, desc);\n  }\n}\n\nmodule.exports = {\n  PeerConnection,\n  MediaDevices,\n  MediaStream,\n};\n\n"
  }
]