Repository: Kagami/vmsg Branch: master Commit: 623b2940a37f Files: 20 Total size: 51.3 KB Directory structure: gitextract_wa9fkvv4/ ├── .babelrc ├── .browserslistrc ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .postcssrc ├── COPYING ├── Makefile ├── README.md ├── demo/ │ ├── index.css │ ├── index.html │ └── index.js ├── lame-svn.patch ├── package.json ├── vmsg.c ├── vmsg.css ├── vmsg.d.ts ├── vmsg.js └── vmsg.wasm ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-proposal-class-properties"] } ================================================ FILE: .browserslistrc ================================================ defaults ================================================ FILE: .github/FUNDING.yml ================================================ custom: https://www.blockchain.com/btc/payment_request?address=3LKKbbi34MHYRQSLV3ZiDGoKgUmCjhTumT&message=Kagami+open+source+projects+support ================================================ FILE: .gitignore ================================================ /node_modules/ /dist/ /.cache/ /_vmsg.* /vmsg.es5.js ================================================ FILE: .gitmodules ================================================ [submodule "lame-svn"] path = lame-svn url = https://github.com/Kagami/lame-svn.git ignore = dirty ================================================ FILE: .npmignore ================================================ * !/COPYING !/vmsg.css !/vmsg.js !/vmsg.es5.js !/vmsg.d.ts !/vmsg.wasm ================================================ FILE: .postcssrc ================================================ { "plugins": { "autoprefixer": true } } ================================================ FILE: COPYING ================================================ Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. ================================================ FILE: Makefile ================================================ export EMCC_WASM_BACKEND = 1 export EMCC_EXPERIMENTAL_USE_LLD = 1 all: vmsg.wasm lame-svn/lame/dist/lib/libmp3lame.so: cd lame-svn/lame && \ git reset --hard && \ patch -p2 < ../../lame-svn.patch && \ emconfigure ./configure \ CFLAGS="-DNDEBUG -Oz" \ --prefix="$$(pwd)/dist" \ --host=x86-none-linux \ --disable-static \ \ --disable-gtktest \ --disable-analyzer-hooks \ --disable-decoder \ --disable-frontend \ && \ emmake make -j8 && \ emmake make install # WASM backend doesn't support EMSCRIPTEN_KEEPALIVE, see: # https://github.com/kripken/emscripten/issues/6233 # Output to bare .wasm doesn't work properly so need to create # intermediate files. vmsg.wasm: lame-svn/lame/dist/lib/libmp3lame.so vmsg.c emcc $^ \ -DNDEBUG -Oz --llvm-lto 3 -Ilame-svn/lame/dist/include \ -s WASM=1 \ -s "EXPORTED_FUNCTIONS=['_vmsg_init','_vmsg_encode','_vmsg_flush','_vmsg_free']" \ -o _vmsg.js cp _vmsg.wasm $@ clean: clean-lame clean-wasm clean-lame: cd lame-svn && git clean -dfx clean-wasm: rm -f vmsg.wasm _vmsg.* ================================================ FILE: README.md ================================================ # vmsg [![npm](https://img.shields.io/npm/v/vmsg.svg)](https://www.npmjs.com/package/vmsg) vmsg is a small library for creating voice messages. While traditional way of communicating on the web is via text, sometimes it's easier or rather funnier to express your thoughts just by saying it. Of course it doesn't require any special support: record your voice with some standard program, upload to file hosting and share the link. But why bother with all of that tedious stuff if you can do the same in browser with a few clicks. :confetti_ball: :tada: **[DEMO](https://kagami.github.io/vmsg/)** :tada: :confetti_ball: ## Features * No dependencies, framework-agnostic, can be easily added to any site * Small: ~73kb gzipped WASM module and ~3kb gzipped JS + CSS * Uses MP3 format which is widely supported * Works in all latest browsers ## Supported browsers * Chrome 32+ * Firefox 27+ * Safari 11+ * Edge 12+ ## Usage ``` npm install vmsg --save ``` ```js import { record } from "vmsg"; someButton.onclick = function() { record(/* {wasmURL: "/static/js/vmsg.wasm"} */).then(blob => { console.log("Recorded MP3", blob); // Can be used like this: // // const form = new FormData(); // form.append("file[]", blob, "record.mp3"); // fetch("/upload.php", { // credentials: "include", // method: "POST", // body: form, // }).then(resp => { // }); }); }; ``` That's it! Don't forget to include [vmsg.css](vmsg.css) and [vmsg.wasm](vmsg.wasm) in your project. For browsers without WebAssembly support you need to also include [wasm-polyfill.js](https://github.com/Kagami/wasm-polyfill.js). See [demo](demo) directory for a more feasible example. A minimal React example for using Recorder with your own UI can be found [here](https://codesandbox.io/s/v67oz43lm7). See also [non React demo](https://github.com/addpipe/simple-vmsg-demo) and [Recording mp3 audio in HTML5 using vmsg](https://addpipe.com/blog/recording-mp3-audio-in-html5-using-vmsg-a-webassembly-library-based-on-lame/) article. ## Development 1. Install [Emscripten SDK](https://github.com/juj/emsdk). 2. Install latest LLVM, Clang and LLD with WebAssembly backend, fix `LLVM_ROOT` variable of Emscripten config. 3. Make sure you have a standard GNU development environment. 4. Activate emsdk environment. 5. ```bash git clone --recurse-submodules https://github.com/Kagami/vmsg.git && cd vmsg make clean all npm install npm start ``` These instructions are very basic because there're a lot of systems with different conventions. Docker image would probably be provided to fix it. ## Technical details for nerds vmsg uses LAME encoder underneath compiled with Emscripten to WebAssembly module. LAME build is optimized for size, weights only little more than 70kb gzipped and can be super-efficiently fetched and parsed by browser. [It's like a small image.](https://twitter.com/wycats/status/942908325775077376) Access to microphone is implemented with Web Audio API, data samples sent to Web Worker which is responsibe for loading WebAssembly module and calling LAME API. Module is produced with modern LLVM WASM backend and LLD linker which should become standard soon, also vmsg has own tiny WASM runtime instead of Emscripten's to decrease overall size and simplify architecture. Worker code is included in the main JS module so end-user has to care only about 3 files: `vmsg.js`, `vmsg.css` and `vmsg.wasm`. CSS can be inlined too but IMO that would be ugly. In order to support browsers without WebAssembly, [WebAssembly polyfill](https://github.com/Kagami/wasm-polyfill.js) is being used. It translates binary module into semantically-equivalent JavaScript on the fly (almost asm.js compatible but doesn't fully validate yet) so we don't need separate asm.js build and can use standard WebAssembly API. It's not as effecient but for audio encoding should be enough. **See also:** [Creating WebAssembly-powered library for modern web](https://hackernoon.com/creating-webassembly-powered-library-for-modern-web-846da334f8fc) article. ## Why not MediaRecorder? [MediaStream Recording API](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API) is great but: * Works only in Firefox and Chrome * Provides little to no options, e.g. VBR quality can't be specified * Firefox/Chrome encode only to Opus which can't be natively played in Safari and Edge ## But you can use e.g. ogv.js polyfill! * It make things more complicated, now you need both encoder and decoder * Opus gives you ~2x bitrate win but for 500kb per minute files it's not that much * MP3 is much more widespread, so even while compression is not best compatibility matters ## License vmsg is licensed under [CC0](COPYING). LAME is licensed under [LGPL](https://github.com/Kagami/lame-svn/blob/master/lame/COPYING). MP3 patents seems to [have expired since April 23, 2017](https://en.wikipedia.org/wiki/LAME#Patents_and_legal_issues). ================================================ FILE: demo/index.css ================================================ body { margin: 20px; background: #ebeaeb; color: #0a0a0a; font-size: 16px; } body, textarea { font-family: Helvetica,sans-serif; line-height: 1.4; } textarea { font-size: 15px; } a { text-decoration: none; } .ribbon { position: absolute; top: 0; right: 0; border: 0; } .app { margin: 0 auto; max-width: 700px; } .post__header { margin: 0 0 20px 0; text-align: center; } .post__body { margin-bottom: 20px; } .comments { font-size: 15px; } .comment { margin-bottom: 20px; padding: 6px 10px; background: #e4e1e5; border-radius: 4px; box-shadow: 1px 1px 4px 0 rgba(59,26,84,.6); } .comment__header { margin-bottom: 3px; } .comment__id { display: inline-block; margin: 0; margin-right: 5px; cursor: default; user-select: none; } .comment__record { font-size: 20px; line-height: 17px; color: #00f; cursor: pointer; } .comment__record:hover { color: #f00; } .comment__body { margin: 0; white-space: pre; } .reply { padding: 6px 10px; background: #e4e1e5; border-radius: 4px; box-shadow: 1px 1px 4px 0 rgba(59,26,84,.6); } .reply__body { width: 100%; height: 80px; background: none; border: none; padding: 0; resize: none; outline: none; } .reply-control { margin-right: 5px; cursor: pointer; } .reply-control:disabled { cursor: default; } .reply-record { cursor: default; user-select: none; } ================================================ FILE: demo/index.html ================================================ vmsg demo Fork me on GitHub
================================================ FILE: demo/index.js ================================================ import React from "react"; import ReactDOM from "react-dom"; import vmsg from ".."; // https://github.com/parcel-bundler/parcel/issues/289 if (module.hot) { module.hot.dispose(() => { location.reload(); }); } class Post extends React.Component { render() { return (

Example post

So here is a simple demo of vmsg library. Imagine this is a blog post or forum thread. Below you can leave text comments, as usual. But there is one more button: “Record”. If you press it vmsg library will open you a microphone recording form. Resulting record will automatically be encoded to MP3 so file won't weight too much. So you can easily share your voice messages even on mobile network and server needs to neither waste CPU time by encoding to MP3 by itself nor using a lot of disk space to store records.
); } } class Comments extends React.Component { constructor(props) { super(props); this.state = { comments: [], }; this.idCounter = 0; } handleReplySend = (comment) => { const { comments } = this.state; comment = { ...comment, id: this.idCounter++ }; this.setState({comments: comments.concat(comment)}); }; render() { const { comments } = this.state; return ( ); } } class Comment extends React.Component { constructor(props) { super(props); if (props.record) { this.audio = new Audio(); this.audio.src = URL.createObjectURL(props.record); } } handleRecordOver = () => { this.audio.currentTime = 0; this.audio.play(); }; handleRecordOut = () => { this.audio.pause(); }; handleRecordClick = () => { const a = document.createElement("a"); if (!("download" in a)) { window.open(this.audio.src); return; } a.href = this.audio.src; a.download = "record.mp3"; a.style.display = "none"; document.body.appendChild(a); a.click(); a.remove(); }; render() { const { id, body } = this.props; return (
Comment #{id + 1}
{this.renderRecord()}
{body}
); } renderRecord() { const { record } = this.props; if (!record) return null; return ( ) } } class Reply extends React.Component { constructor(props) { super(props); this.state = { body: "", record: null, }; } handleBodyChange = (e) => { this.setState({body: e.target.value}); }; handleRecord = () => { vmsg.record({ wasmURL: require("../vmsg.wasm"), shimURL: "https://unpkg.com/wasm-polyfill.js@0.2.0/wasm-polyfill.js", }).then(record => { this.setState({record}); }); }; handleSend = () => { const { body, record } = this.state; this.setState({body: "", record: null}); this.props.onSend({body, record}); }; render() { const { body, record } = this.state; return (