Team Ai
Apppublic

RobinsAIWorld/whisper.cpp-wasm

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
index.html887 linesDownload Raw Back to root
1<!doctype html>2<html lang="en-us">3  <head>4    <title>whisper.cpp : WASM example</title>5    <script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>6 7    <style>8      #output {9        width: 100%;10        height: 100%;11        margin: 0 auto;12        margin-top: 10px;13        border-left: 0px;14        border-right: 0px;15        padding-left: 0px;16        padding-right: 0px;17        display: block;18        background-color: black;19        color: white;20        font-size: 10px;21        font-family: "Lucida Console", Monaco, monospace;22        outline: none;23        white-space: pre;24        overflow-wrap: normal;25        overflow-x: scroll;26      } 27    </style>28  </head>29  <body>30    <div id="main-container">31      <div id="warning" style="display: none; padding: 1rem">32        <b style="color: red; font-size: large"33          >Warning: your browser does not support SharedArrayBuffer please try34          open the page in a new tab.35        </b>36<!--         <a target="_blank" href="https://radames-whisper-wasm.hf.space" target="_blank"37          >https://radames-whisper-wasm.hf.space</a38        > -->39      </div>40      <b41        >Minimal42        <a target="_blank" href="https://github.com/ggerganov/whisper.cpp">whisper.cpp</a>43        example running fully in the browser</b44      >45 46      <br /><br />47 48      Usage instructions:<br />49      <ul>50        <li>51          Load a ggml model file (you can obtain one from52          <a target="_blank" href="https://ggml.ggerganov.com/">here</a>, recommended:53          <b>tiny</b> or <b>base</b>)54        </li>55        <li>56          Select audio file to transcribe or record audio from the microphone57          (sample: <a target="_blank" href="https://whisper.ggerganov.com/jfk.wav">jfk.wav</a>)58        </li>59        <li>Click on the "Transcribe" button to start the transcription</li>60      </ul>61 62      Note that the computation is quite heavy and may take a few seconds to63      complete.<br />64      The transcription results will be displayed in the text area below.<br /><br />65      <b>Important:</b>66      <ul>67        <li>68          your browser must support WASM SIMD instructions for this to work69        </li>70        <li>71          Firefox cannot load files larger than 256 MB - use Chrome instead72        </li>73      </ul>74 75      <b>More examples:</b>76      <a target="_blank" href="https://whisper.ggerganov.com/">main</a> |77      <a target="_blank" href="https://whisper.ggerganov.com/bench">bench</a> |78      <a target="_blank" href="https://whisper.ggerganov.com/stream">stream</a> |79      <a target="_blank" href="https://whisper.ggerganov.com/command">command</a> |80      <a target="_blank" href="https://whisper.ggerganov.com/talk">talk</a> |81 82      <hr />83 84      <div id="model">85        Whisper models: <span id="model-whisper-status"></span><br /><br />86        <button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">87          tiny.en (75 MB)88        </button>89        <button id="fetch-whisper-tiny" onclick="loadWhisper('tiny')">90          tiny (75 MB)91        </button>92        <button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">93          base.en (142 MB)94        </button>95        <button id="fetch-whisper-base" onclick="loadWhisper('base')">96          base (142 MB)97        </button>98        <button id="fetch-whisper-small-en" onclick="loadWhisper('small.en')">99          small.en (466 MB)100        </button>101        <button id="fetch-whisper-small" onclick="loadWhisper('small')">102          small (466 MB)103        </button>104        <input105          type="file"106          id="whisper-file"107          name="file"108          onchange="loadFile(event, 'whisper.bin')"109        />110        <br /><br />111        Quantized models:<br /><br />112        <button113          id="fetch-whisper-tiny-en-q5_1"114          onclick="loadWhisper('tiny-en-q5_1')"115        >116          tiny.en (Q5_1, 31 MB)117        </button>118        <button id="fetch-whisper-tiny-q5_1" onclick="loadWhisper('tiny-q5_1')">119          tiny (Q5_1, 31 MB)120        </button>121        <button122          id="fetch-whisper-base-en-q5_1"123          onclick="loadWhisper('base-en-q5_1')"124        >125          base.en (Q5_1, 57 MB)126        </button>127        <button id="fetch-whisper-base-q5_1" onclick="loadWhisper('base-q5_1')">128          base (Q5_1, 57 MB)129        </button>130        <button131          id="fetch-whisper-small-en-q5_1"132          onclick="loadWhisper('small-en-q5_1')"133        >134          small.en (Q5_1, 182 MB)135        </button>136        <button137          id="fetch-whisper-small-q5_1"138          onclick="loadWhisper('small-q5_1')"139        >140          small (Q5_1, 182 MB)</button141        ><br />142        <button143          id="fetch-whisper-medium-en-q5_0"144          onclick="loadWhisper('medium-en-q5_0')"145        >146          medium.en (Q5_0, 515 MB)147        </button>148        <button149          id="fetch-whisper-medium-q5_0"150          onclick="loadWhisper('medium-q5_0')"151        >152          medium (Q5_0, 515 MB)153        </button>154        <button155          id="fetch-whisper-large-q5_0"156          onclick="loadWhisper('large-q5_0')"157        >158          large (Q5_0, 1030 MB)159        </button>160        <span id="fetch-whisper-progress"></span>161      </div>162 163      <br />164 165      <!-- radio button to select between file upload or microphone -->166      <div id="input">167        Input:168        <input169          type="radio"170          id="file"171          name="input"172          value="file"173          checked="checked"174          onchange="changeInput('file')"175        />176        <label for="file">File</label>177        <input178          type="radio"179          id="mic"180          name="input"181          value="mic"182          onchange="changeInput('mic')"183        />184        <label for="mic">Microphone</label>185      </div>186 187      <br />188 189      <div id="input_file">190        Audio file:191        <input type="file" id="file" name="file" onchange="loadAudio(event)" />192      </div>193 194      <div id="input_mic" style="display: none">195        Microphone:196        <button id="start" onclick="startRecording()">Start</button>197        <button id="stop" onclick="stopRecording()" disabled>Stop</button>198 199        <!-- progress bar to show recording progress -->200        <br /><br />201        <div id="progress" style="display: none">202          <div203            id="progress-bar"204            style="width: 0%; height: 10px; background-color: #4caf50"205          ></div>206          <div id="progress-text">0%</div>207        </div>208      </div>209 210      <a target="_blank"udio controls="controls" id="audio" loop hidden>211        Your browser does not support the &lt;audio&gt; tag.212        <source id="source" src="" type="audio/wav" />213      </audio>214 215      <hr />216      <br />217 218      <table>219        <tr>220          <td>221            Language:222            <select id="language" name="language">223              <option value="en">English</option>224              <option value="ar">Arabic</option>225              <option value="hy">Armenian</option>226              <option value="az">Azerbaijani</option>227              <option value="eu">Basque</option>228              <option value="be">Belarusian</option>229              <option value="bn">Bengali</option>230              <option value="bg">Bulgarian</option>231              <option value="ca">Catalan</option>232              <option value="zh">Chinese</option>233              <option value="hr">Croatian</option>234              <option value="cs">Czech</option>235              <option value="da">Danish</option>236              <option value="nl">Dutch</option>237              <option value="en">English</option>238              <option value="et">Estonian</option>239              <option value="tl">Filipino</option>240              <option value="fi">Finnish</option>241              <option value="fr">French</option>242              <option value="gl">Galician</option>243              <option value="ka">Georgian</option>244              <option value="de">German</option>245              <option value="el">Greek</option>246              <option value="gu">Gujarati</option>247              <option value="iw">Hebrew</option>248              <option value="hi">Hindi</option>249              <option value="hu">Hungarian</option>250              <option value="is">Icelandic</option>251              <option value="id">Indonesian</option>252              <option value="ga">Irish</option>253              <option value="it">Italian</option>254              <option value="ja">Japanese</option>255              <option value="kn">Kannada</option>256              <option value="ko">Korean</option>257              <option value="la">Latin</option>258              <option value="lv">Latvian</option>259              <option value="lt">Lithuanian</option>260              <option value="mk">Macedonian</option>261              <option value="ms">Malay</option>262              <option value="mt">Maltese</option>263              <option value="no">Norwegian</option>264              <option value="fa">Persian</option>265              <option value="pl">Polish</option>266              <option value="pt">Portuguese</option>267              <option value="ro">Romanian</option>268              <option value="ru">Russian</option>269              <option value="sr">Serbian</option>270              <option value="sk">Slovak</option>271              <option value="sl">Slovenian</option>272              <option value="es">Spanish</option>273              <option value="sw">Swahili</option>274              <option value="sv">Swedish</option>275              <option value="ta">Tamil</option>276              <option value="te">Telugu</option>277              <option value="th">Thai</option>278              <option value="tr">Turkish</option>279              <option value="uk">Ukrainian</option>280              <option value="ur">Urdu</option>281              <option value="vi">Vietnamese</option>282              <option value="cy">Welsh</option>283              <option value="yi">Yiddish</option>284            </select>285          </td>286          <!-- Slider to select number of threads between 1 and 16 -->287          <td>288            Threads:289            <input290              type="range"291              id="threads"292              name="threads"293              min="1"294              max="16"295              value="8"296              onchange="changeThreads(this.value)"297            />298            <span id="threads-value">8</span>299          </td>300          <td>301            <button onclick="onProcess(false);">Transcribe</button>302          </td>303          <td>304            <button onclick="onProcess(true);">Translate</button>305          </td>306        </tr>307      </table>308 309      <br />310 311      <!-- textarea with height filling the rest of the page -->312      <textarea id="output" rows="20"></textarea>313 314      <br /><br />315 316      <div class="cell-version">317        <span>318          | Build time: <span class="nav-link">@GIT_DATE@</span> | Commit hash:319          <a target="_blank"320            class="nav-link"321            href="https://github.com/ggerganov/whisper.cpp/commit/@GIT_SHA1@"322            >@GIT_SHA1@</a323          >324          | Commit subject: <span class="nav-link">@GIT_COMMIT_SUBJECT@</span> |325          <a target="_blank"326            class="nav-link"327            href="https://github.com/ggerganov/whisper.cpp/tree/master/examples/whisper.wasm"328            >Source Code</a329          >330          |331        </span>332      </div>333    </div>334 335    <script type="text/javascript" src="helpers.js"></script>336    <script type="text/javascript">337      document.addEventListener("DOMContentLoaded", () => {338        //check is shared array buffer is supported339        if (!window.SharedArrayBuffer) {340          document.querySelector("#warning").style.display = "block";341        }342      });343      // TODO: convert audio buffer to WAV344      function setAudio(audio) {345        //if (audio) {346        //    // convert to 16-bit PCM347        //    var blob = new Blob([audio], { type: 'audio/wav' });348        //    var url = URL.createObjectURL(blob);349        //    document.getElementById('source').src = url;350        //    document.getElementById('audio').hidden = false;351        //    document.getElementById('audio').loop = false;352        //    document.getElementById('audio').load();353        //} else {354        //    document.getElementById('audio').hidden = true;355        //}356      }357 358      function changeInput(input) {359        if (input == "file") {360          document.getElementById("input_file").style.display = "block";361          document.getElementById("input_mic").style.display = "none";362          document.getElementById("progress").style.display = "none";363        } else {364          document.getElementById("input_file").style.display = "none";365          document.getElementById("input_mic").style.display = "block";366          document.getElementById("progress").style.display = "block";367        }368      }369 370      var Module = {371        print: printTextarea,372        printErr: printTextarea,373        setStatus: function (text) {374          printTextarea("js: " + text);375        },376        monitorRunDependencies: function (left) {},377      };378 379      // web audio context380      var context = null;381 382      // audio data383      var audio = null;384 385      // the whisper instance386      var instance = null;387      var model_whisper = "";388 389      // helper function390      function convertTypedArray(src, type) {391        var buffer = new ArrayBuffer(src.byteLength);392        var baseView = new src.constructor(buffer).set(src);393        return new type(buffer);394      }395 396      //397      // load model398      //399 400      let dbVersion = 1;401      let dbName = "whisper.ggerganov.com";402      let indexedDB =403        window.indexedDB ||404        window.mozIndexedDB ||405        window.webkitIndexedDB ||406        window.msIndexedDB;407 408      function storeFS(fname, buf) {409        // write to WASM file using FS_createDataFile410        // if the file exists, delete it411        try {412          Module.FS_unlink(fname);413        } catch (e) {414          // ignore415        }416 417        Module.FS_createDataFile("/", fname, buf, true, true);418 419        //model_whisper = fname;420 421        document.getElementById("model-whisper-status").innerHTML =422          'loaded "' + model_whisper + '"!';423 424        printTextarea(425          "storeFS: stored model: " + fname + " size: " + buf.length426        );427 428        document.getElementById("model").innerHTML =429          "Model fetched: " + model_whisper;430      }431 432      function loadFile(event, fname) {433        var file = event.target.files[0] || null;434        if (file == null) {435          return;436        }437 438        printTextarea(439          "loadFile: loading model: " +440            file.name +441            ", size: " +442            file.size +443            " bytes"444        );445        printTextarea("loadFile: please wait ...");446 447        var reader = new FileReader();448        reader.onload = function (event) {449          var buf = new Uint8Array(reader.result);450          storeFS(fname, buf);451        };452        reader.readAsArrayBuffer(file);453 454        document.getElementById("fetch-whisper-tiny-en").style.display = "none";455        document.getElementById("fetch-whisper-base-en").style.display = "none";456        document.getElementById("fetch-whisper-small-en").style.display =457          "none";458        document.getElementById("fetch-whisper-tiny").style.display = "none";459        document.getElementById("fetch-whisper-base").style.display = "none";460        document.getElementById("fetch-whisper-small").style.display = "none";461 462        document.getElementById("fetch-whisper-tiny-en-q5_1").style.display =463          "none";464        document.getElementById("fetch-whisper-tiny-q5_1").style.display =465          "none";466        document.getElementById("fetch-whisper-base-en-q5_1").style.display =467          "none";468        document.getElementById("fetch-whisper-base-q5_1").style.display =469          "none";470        document.getElementById("fetch-whisper-small-en-q5_1").style.display =471          "none";472        document.getElementById("fetch-whisper-small-q5_1").style.display =473          "none";474        document.getElementById("fetch-whisper-medium-en-q5_0").style.display =475          "none";476        document.getElementById("fetch-whisper-medium-q5_0").style.display =477          "none";478        document.getElementById("fetch-whisper-large-q5_0").style.display =479          "none";480 481        document.getElementById("whisper-file").style.display = "none";482        document.getElementById("model-whisper-status").innerHTML =483          "loaded model: " + file.name;484      }485 486      function loadWhisper(model) {487        const baseURL =488          "https://huggingface.co/ggerganov/whisper.cpp/resolve/main";489        let urls = {490          "tiny.en": `${baseURL}/ggml-tiny.en.bin`,491          tiny: `${baseURL}/ggml-tiny.bin`,492          "base.en": `${baseURL}/ggml-base.en.bin`,493          base: `${baseURL}/ggml-base.bin`,494          "small.en": `${baseURL}/ggml-small.en.bin`,495          small: `${baseURL}/ggml-small.bin`,496 497          "tiny-en-q5_1": `${baseURL}/ggml-model-whisper-tiny.en-q5_1.bin`,498          "tiny-q5_1": `${baseURL}/ggml-model-whisper-tiny-q5_1.bin`,499          "base-en-q5_1": `${baseURL}/ggml-model-whisper-base.en-q5_1.bin`,500          "base-q5_1": `${baseURL}/ggml-model-whisper-base-q5_1.bin`,501          "small-en-q5_1": `${baseURL}/ggml-model-whisper-small.en-q5_1.bin`,502          "small-q5_1": `${baseURL}/ggml-model-whisper-small-q5_1.bin`,503          "medium-en-q5_0": `${baseURL}/ggml-model-whisper-medium.en-q5_0.bin`,504          "medium-q5_0": `${baseURL}/ggml-model-whisper-medium-q5_0.bin`,505          "large-q5_0": `${baseURL}/ggml-model-whisper-large-q5_0.bin`,506        };507 508        let sizes = {509          "tiny.en": 75,510          tiny: 75,511          "base.en": 142,512          base: 142,513          "small.en": 466,514          small: 466,515 516          "tiny-en-q5_1": 31,517          "tiny-q5_1": 31,518          "base-en-q5_1": 57,519          "base-q5_1": 57,520          "small-en-q5_1": 182,521          "small-q5_1": 182,522          "medium-en-q5_0": 515,523          "medium-q5_0": 515,524          "large-q5_0": 1030,525        };526 527        let url = urls[model];528        let dst = "whisper.bin";529        let size_mb = sizes[model];530 531        model_whisper = model;532 533        document.getElementById("fetch-whisper-tiny-en").style.display = "none";534        document.getElementById("fetch-whisper-base-en").style.display = "none";535        document.getElementById("fetch-whisper-small-en").style.display =536          "none";537        document.getElementById("fetch-whisper-tiny").style.display = "none";538        document.getElementById("fetch-whisper-base").style.display = "none";539        document.getElementById("fetch-whisper-small").style.display = "none";540 541        document.getElementById("fetch-whisper-tiny-en-q5_1").style.display =542          "none";543        document.getElementById("fetch-whisper-tiny-q5_1").style.display =544          "none";545        document.getElementById("fetch-whisper-base-en-q5_1").style.display =546          "none";547        document.getElementById("fetch-whisper-base-q5_1").style.display =548          "none";549        document.getElementById("fetch-whisper-small-en-q5_1").style.display =550          "none";551        document.getElementById("fetch-whisper-small-q5_1").style.display =552          "none";553        document.getElementById("fetch-whisper-medium-en-q5_0").style.display =554          "none";555        document.getElementById("fetch-whisper-medium-q5_0").style.display =556          "none";557        document.getElementById("fetch-whisper-large-q5_0").style.display =558          "none";559 560        document.getElementById("whisper-file").style.display = "none";561        document.getElementById("model-whisper-status").innerHTML =562          "loading model: " + model;563 564        cbProgress = function (p) {565          let el = document.getElementById("fetch-whisper-progress");566          el.innerHTML = Math.round(100 * p) + "%";567        };568 569        cbCancel = function () {570          var el;571 572          el = document.getElementById("fetch-whisper-tiny-en");573          if (el) el.style.display = "inline-block";574          el = document.getElementById("fetch-whisper-base-en");575          if (el) el.style.display = "inline-block";576          el = document.getElementById("fetch-whisper-small-en");577          if (el) el.style.display = "inline-block";578          el = document.getElementById("fetch-whisper-tiny");579          if (el) el.style.display = "inline-block";580          el = document.getElementById("fetch-whisper-base");581          if (el) el.style.display = "inline-block";582          el = document.getElementById("fetch-whisper-small");583          if (el) el.style.display = "inline-block";584 585          el = document.getElementById("fetch-whisper-tiny-en-q5_1");586          if (el) el.style.display = "inline-block";587          el = document.getElementById("fetch-whisper-tiny-q5_1");588          if (el) el.style.display = "inline-block";589          el = document.getElementById("fetch-whisper-base-en-q5_1");590          if (el) el.style.display = "inline-block";591          el = document.getElementById("fetch-whisper-base-q5_1");592          if (el) el.style.display = "inline-block";593          el = document.getElementById("fetch-whisper-small-en-q5_1");594          if (el) el.style.display = "inline-block";595          el = document.getElementById("fetch-whisper-small-q5_1");596          if (el) el.style.display = "inline-block";597          el = document.getElementById("fetch-whisper-medium-en-q5_0");598          if (el) el.style.display = "inline-block";599          el = document.getElementById("fetch-whisper-medium-q5_0");600          if (el) el.style.display = "inline-block";601          el = document.getElementById("fetch-whisper-large-q5_0");602          if (el) el.style.display = "inline-block";603 604          el = document.getElementById("whisper-file");605          if (el) el.style.display = "inline-block";606          el = document.getElementById("model-whisper-status");607          if (el) el.innerHTML = "";608        };609 610        loadRemote(611          url,612          dst,613          size_mb,614          cbProgress,615          storeFS,616          cbCancel,617          printTextarea618        );619      }620 621      //622      // audio file623      //624 625      const kMaxAudio_s = 30 * 60;626      const kMaxRecording_s = 2 * 60;627      const kSampleRate = 16000;628 629      window.AudioContext = window.AudioContext || window.webkitAudioContext;630      window.OfflineAudioContext =631        window.OfflineAudioContext || window.webkitOfflineAudioContext;632 633      function loadAudio(event) {634        if (!context) {635          context = new AudioContext({636            sampleRate: kSampleRate,637            channelCount: 1,638            echoCancellation: false,639            autoGainControl: true,640            noiseSuppression: true,641          });642        }643 644        var file = event.target.files[0] || null;645        if (file == null) {646          return;647        }648 649        printTextarea(650          "js: loading audio: " + file.name + ", size: " + file.size + " bytes"651        );652        printTextarea("js: please wait ...");653 654        var reader = new FileReader();655        reader.onload = function (event) {656          var buf = new Uint8Array(reader.result);657 658          context.decodeAudioData(659            buf.buffer,660            function (audioBuffer) {661              var offlineContext = new OfflineAudioContext(662                audioBuffer.numberOfChannels,663                audioBuffer.length,664                audioBuffer.sampleRate665              );666              var source = offlineContext.createBufferSource();667              source.buffer = audioBuffer;668              source.connect(offlineContext.destination);669              source.start(0);670 671              offlineContext.startRendering().then(function (renderedBuffer) {672                audio = renderedBuffer.getChannelData(0);673                printTextarea("js: audio loaded, size: " + audio.length);674 675                // truncate to first 30 seconds676                if (audio.length > kMaxAudio_s * kSampleRate) {677                  audio = audio.slice(0, kMaxAudio_s * kSampleRate);678                  printTextarea(679                    "js: truncated audio to first " + kMaxAudio_s + " seconds"680                  );681                }682 683                setAudio(audio);684              });685            },686            function (e) {687              printTextarea("js: error decoding audio: " + e);688              audio = null;689              setAudio(audio);690            }691          );692        };693        reader.readAsArrayBuffer(file);694      }695 696      //697      // microphone698      //699 700      var mediaRecorder = null;701      var doRecording = false;702      var startTime = 0;703 704      function stopRecording() {705        doRecording = false;706      }707 708      // record up to kMaxRecording_s seconds of audio from the microphone709      // check if doRecording is false every 1000 ms and stop recording if so710      // update progress information711      function startRecording() {712        if (!context) {713          context = new AudioContext({714            sampleRate: kSampleRate,715            channelCount: 1,716            echoCancellation: false,717            autoGainControl: true,718            noiseSuppression: true,719          });720        }721 722        document.getElementById("start").disabled = true;723        document.getElementById("stop").disabled = false;724 725        document.getElementById("progress-bar").style.width = "0%";726        document.getElementById("progress-text").innerHTML = "0%";727 728        doRecording = true;729        startTime = Date.now();730 731        var chunks = [];732        var stream = null;733 734        navigator.mediaDevices735          .getUserMedia({ audio: true, video: false })736          .then(function (s) {737            stream = s;738            mediaRecorder = new MediaRecorder(stream);739            mediaRecorder.ondataavailable = function (e) {740              chunks.push(e.data);741            };742            mediaRecorder.onstop = function (e) {743              var blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" });744              chunks = [];745 746              document.getElementById("start").disabled = false;747              document.getElementById("stop").disabled = true;748 749              var reader = new FileReader();750              reader.onload = function (event) {751                var buf = new Uint8Array(reader.result);752 753                context.decodeAudioData(754                  buf.buffer,755                  function (audioBuffer) {756                    var offlineContext = new OfflineAudioContext(757                      audioBuffer.numberOfChannels,758                      audioBuffer.length,759                      audioBuffer.sampleRate760                    );761                    var source = offlineContext.createBufferSource();762                    source.buffer = audioBuffer;763                    source.connect(offlineContext.destination);764                    source.start(0);765 766                    offlineContext767                      .startRendering()768                      .then(function (renderedBuffer) {769                        audio = renderedBuffer.getChannelData(0);770                        printTextarea(771                          "js: audio recorded, size: " + audio.length772                        );773 774                        // truncate to first 30 seconds775                        if (audio.length > kMaxRecording_s * kSampleRate) {776                          audio = audio.slice(0, kMaxRecording_s * kSampleRate);777                          printTextarea(778                            "js: truncated audio to first " +779                              kMaxRecording_s +780                              " seconds"781                          );782                        }783                        setAudio(audio);784                      });785                  },786                  function (e) {787                    printTextarea("js: error decoding audio: " + e);788                    audio = null;789                    setAudio(audio);790                  }791                );792              };793 794              reader.readAsArrayBuffer(blob);795            };796            mediaRecorder.start();797          })798          .catch(function (err) {799            printTextarea("js: error getting audio stream: " + err);800          });801 802        var interval = setInterval(function () {803          if (!doRecording) {804            clearInterval(interval);805            mediaRecorder.stop();806            stream.getTracks().forEach(function (track) {807              track.stop();808            });809          }810 811          document.getElementById("progress-bar").style.width =812            (100 * (Date.now() - startTime)) / 1000 / kMaxRecording_s + "%";813          document.getElementById("progress-text").innerHTML =814            ((100 * (Date.now() - startTime)) / 1000 / kMaxRecording_s).toFixed(815              0816            ) + "%";817        }, 1000);818 819        printTextarea("js: recording ...");820 821        setTimeout(function () {822          if (doRecording) {823            printTextarea(824              "js: recording stopped after " + kMaxRecording_s + " seconds"825            );826            stopRecording();827          }828        }, kMaxRecording_s * 1000);829      }830 831      //832      // transcribe833      //834 835      var nthreads = 8;836 837      function changeThreads(value) {838        nthreads = value;839        document.getElementById("threads-value").innerHTML = nthreads;840      }841 842      function onProcess(translate) {843        if (!instance) {844          instance = Module.init("whisper.bin");845 846          if (instance) {847            printTextarea("js: whisper initialized, instance: " + instance);848            document.getElementById("model").innerHTML =849              "Model loaded: " + model_whisper;850          }851        }852 853        if (!instance) {854          printTextarea("js: failed to initialize whisper");855          return;856        }857 858        if (!audio) {859          printTextarea("js: no audio data");860          return;861        }862 863        if (instance) {864          printTextarea("");865          printTextarea("js: processing - this might take a while ...");866          printTextarea("");867 868          setTimeout(function () {869            var ret = Module.full_default(870              instance,871              audio,872              document.getElementById("language").value,873              nthreads,874              translate875            );876            console.log("js: full_default returned: " + ret);877            if (ret) {878              printTextarea("js: whisper returned: " + ret);879            }880          }, 100);881        }882      }883    </script>884    <script type="text/javascript" src="main.js"></script>885  </body>886</html>887