├── .babelrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dist ├── fonts_ │ ├── iconfont.169d273.ttf │ ├── iconfont.4175e88.woff │ └── iconfont.c27c4ff.eot ├── iconfont.svg ├── v-audio.js └── v-audio.js.map ├── example.png ├── example2.png ├── package-lock.json ├── package.json ├── src ├── MiniAudio.vue ├── VueAudio.vue ├── audio.js ├── font │ ├── iconfont.css │ ├── iconfont.eot │ ├── iconfont.svg │ ├── iconfont.ttf │ └── iconfont.woff └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=vue 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | yarn-error.log 5 | 6 | # Editor directories and files 7 | .idea 8 | *.suo 9 | *.ntvs* 10 | *.njsproj 11 | *.sln 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | yarn-debug.log* 4 | yarn-error.log* 5 | node_modules/ 6 | .* 7 | /src 8 | package-lock.json 9 | webpack.config.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 forijk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-audio-better 2 | 3 | [](https://www.npmjs.com/package/vue-audio-better) [](https://vuejs.org/) 4 | 5 | > Easy to create custom audio player components for Vue.js. 6 | 7 | > 一个有灵魂的进度条。 8 | 9 | > A progress bar with soul. 10 | 11 | > 简单、有趣的 audio 组件,非常感谢您的 star! 12 | 13 | > Simple, fun audio components, Thank you for your star! 14 | 15 | ## Overview 16 | 17 | ### Normal 18 | 19 |  20 | 21 | ### Mini 22 | 23 |  24 | 25 | ## Installation 26 | 27 | ```bash 28 | npm install vue-audio-better --save 29 | ``` 30 | 31 | ## Update 32 | 33 | Add a mini audio component. 34 | 35 | ## Setup 36 | 37 | ### Bundler (Webpack, Rollup) 38 | 39 | ```js 40 | // in your entrypoint 41 | import Vue from 'vue' 42 | import VueAudio from 'vue-audio-better' 43 | 44 | Vue.use(VueAudio) 45 | ``` 46 | 47 | ## Usage 48 | 49 | ### Required Markup 50 | 51 | ```vue 52 | 53 | 56 | 57 | ``` 58 | 59 | ```vue 60 | 61 | 64 | 65 | ``` 66 | 67 | ## Props 68 | 69 | ### `width` 70 | 71 | Type: `Number` - Default: `500` 72 | 73 | Audio width 74 | 75 | ### `audio-source` 76 | 77 | Type: `String` - Required 78 | 79 | A string of audio file URL 80 | 81 | ### `html5` 82 | 83 | Type: `Boolean` - Default: `false` 84 | 85 | Whether to force HTML5 Audio 86 | 87 | ### `loop` 88 | 89 | Type: `Boolean` - Default: `false` 90 | 91 | Whether to start the playback again 92 | automatically after it is done playing 93 | 94 | ### `preload` 95 | 96 | Type: `Boolean` - Default: `true` 97 | 98 | Whether to start downloading the audio 99 | file when the component is mounted 100 | 101 | ### `autoplay` 102 | 103 | Type: `Boolean` - Default: `false` 104 | 105 | Whether to start the playback 106 | when the component is mounted 107 | 108 | ### `formats` 109 | 110 | Type: `String[]` - Default: `[]` 111 | 112 | Howler.js automatically detects your file format from the extension, 113 | but you may also specify a format in situations where extraction won't work 114 | (such as with a SoundCloud stream) 115 | 116 | ### `xhrWithCredentials` 117 | 118 | Type: `Boolean` - Default: `false` 119 | 120 | Whether to enable the `withCredentials` flag on XHR requests 121 | used to fetch audio files when using Web Audio API ([see reference](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)) 122 | 123 | ## Data 124 | 125 | ### `playing` 126 | 127 | Type: `Boolean` 128 | 129 | Whether audio is currently playing 130 | 131 | ### `muted` 132 | 133 | Type: `Boolean` 134 | 135 | Whether the audio playback is muted 136 | 137 | ### `volume` 138 | 139 | Type: `Number` 140 | 141 | The volume of the playback on a scale of `0` to `1` 142 | 143 | ### `rate` 144 | 145 | Type: `Number` 146 | 147 | The rate (speed) of the playback on a scale of `0.5` to `4` 148 | 149 | ### `seek` 150 | 151 | Type: `Number` 152 | 153 | The position of the playback in seconds 154 | 155 | ### `duration` 156 | 157 | Type: `Number` 158 | 159 | The duration of the audio in seconds 160 | 161 | ### `progress` 162 | 163 | Type: `Number` 164 | 165 | The progress of the playback on a scale of `0` to `1` 166 | 167 | ## Methods 168 | 169 | ### `play()` 170 | 171 | Start the playback 172 | 173 | ### `pause()` 174 | 175 | Pause the playback 176 | 177 | ### `togglePlayback()` 178 | 179 | Toggle playing or pausing the playback 180 | 181 | ### `stop()` 182 | 183 | Stop the playback (also resets the `seek` to `0`) 184 | 185 | ### `mute()` 186 | 187 | Mute the playback 188 | 189 | ### `unmute()` 190 | 191 | Unmute the playback 192 | 193 | ### `toggleMute()` 194 | 195 | Toggle muting and unmuting the playback 196 | 197 | ### `setVolume(volume)` 198 | 199 | Set the volume of the playback (value is clamped between `0` and `1`) 200 | 201 | ### `setRate(rate)` 202 | 203 | Set the rate (speed) of the playback (value is clamped between `0.5` and `4`) 204 | 205 | ### `setSeek(seek)` 206 | 207 | Set the position of the playback (value is clamped between `0` and `duration`) 208 | 209 | ### `setProgress(progress)` 210 | 211 | Set the progress of the playback (value is clamped between `0` and `1`) 212 | 213 | ## Development 214 | 215 | ### Build 216 | 217 | Bundle the js to the `dist` folder: 218 | 219 | ```bash 220 | npm run build 221 | ``` 222 | 223 | ## Acknowledgements 224 | 225 | [howler.js](https://github.com/goldfire/howler.js) 226 | [vue-howler](https://github.com/mickdekkers/vue-howler) 227 | 228 | ## License 229 | 230 | [MIT](http://opensource.org/licenses/MIT) 231 | -------------------------------------------------------------------------------- /dist/fonts_/iconfont.169d273.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forijk/vue-audio-better/3cada27d313d43d1553c58a48816c5a319f0e775/dist/fonts_/iconfont.169d273.ttf -------------------------------------------------------------------------------- /dist/fonts_/iconfont.4175e88.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forijk/vue-audio-better/3cada27d313d43d1553c58a48816c5a319f0e775/dist/fonts_/iconfont.4175e88.woff -------------------------------------------------------------------------------- /dist/fonts_/iconfont.c27c4ff.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forijk/vue-audio-better/3cada27d313d43d1553c58a48816c5a319f0e775/dist/fonts_/iconfont.c27c4ff.eot -------------------------------------------------------------------------------- /dist/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /dist/v-audio.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("vAudio",[],t):"object"==typeof exports?exports.vAudio=t():e.vAudio=t()}("undefined"!=typeof self?self:this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="dist/",t(t.s=8)}([function(e,t){function n(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var i=o(r);return[n].concat(r.sources.map(function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"})).concat([i]).join("\n")}return[n].join("\n")}function o(e){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e))))+" */"}e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var o=n(t,e);return t[2]?"@media "+t[2]+"{"+o+"}":o}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var o={},r=0;rn.parts.length&&(o.parts.length=n.parts.length)}else{for(var a=[],r=0;r1?this.curVolume=1:this.curVolume<0&&(this.curVolume=0),this.setVolume(this.curVolume)},_sToMs:function(e){if("number"!=typeof e)return"00:00";e=parseInt(e);var t=void 0;return t=Math.floor(e/60),e%=60,t+="",e+="",t=1==t.length?"0"+t:t,e=1==e.length?"0"+e:e,t+":"+e}},mounted:function(){var e=this;this.width&&"number"==typeof this.width&&(this.totalWidth=this.width+"px"),this.setVolume(this.curVolume),this.slider=this.$refs.slider,this.thunk=this.$refs.trunk,this.thunk.onmousedown=function(t){var n=parseInt(e.pWidth),o=t.clientX;return document.onmousemove=function(t){var r=t.clientX-o+n,i=r/e.slider.offsetWidth;e.per=Math.ceil((e.max-e.min)*i+e.min),e.per=Math.max(e.per,e.min),e.per=Math.min(e.per,e.max)},document.onmouseup=function(){document.onmousemove=document.onmouseup=null,e.setProgress(e.scale)},!1}}}},function(e,t,n){"use strict";var o=n(13),r=(n.n(o),n(15)),i=n.n(r),a=n(16),s=n.n(a),u=n(17),d=n.n(u),l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.a={props:{audioSource:{type:String,required:!0,validator:function(e){return"string"==typeof e&&e.length>0}},autoplay:{type:Boolean,default:!1},loop:{type:Boolean,default:!1},preload:{type:Boolean,default:!0},html5:{type:Boolean,default:!1},formats:{type:Array,default:function(){return[]}},xhrWithCredentials:{type:Boolean,default:!1}},data:function(){var e=this;return{_howl:null,playing:!1,muted:!1,volume:1,rate:1,seek:0,duration:0,_polls:{seek:{id:null,interval:250,hook:function(){e.seek=e.$data._howl.seek()}}},_howlEvents:[{name:"load",hook:function(){e.duration=e.$data._howl.duration()}},"loaderror","playerror",{name:"play",hook:function(){e.playing=!0}},{name:"end",hook:function(){e.playing=!1}},{name:"pause",hook:function(){e.playing=!1}},{name:"stop",hook:function(){e.playing=!1,null!=e.$data._howl&&(e.seek=e.$data._howl.seek())}},"mute",{name:"volume",hook:function(){e.volume=e.$data._howl.volume()}},{name:"rate",hook:function(){e.rate=e.$data._howl.rate()}},{name:"seek",hook:function(){e.playing||(e.seek=e.$data._howl.seek())}},"fade"]}},computed:{progress:function(){return 0===this.duration?0:this.seek/this.duration}},created:function(){this._initialize()},beforeDestroy:function(){this._cleanup()},watch:{playing:function(e){this.seek=this.$data._howl.seek(),e?this.$data._polls.seek.id=setInterval(this.$data._polls.seek.hook,this.$data._polls.seek.interval):clearInterval(this.$data._polls.seek.id)},audioSource:function(e){this._reinitialize()}},methods:{_reinitialize:function(){this._cleanup(!1),this._initialize()},_initialize:function(){var e=this;this.$data._howl=new o.Howl({src:this.audioSource,volume:this.volume,html5:this.html5,loop:this.loop,preload:this.preload,autoplay:this.autoplay,mute:this.muted,rate:this.rate,format:this.formats,xhrWithCredentials:this.xhrWithCredentials});var t=this.$data._howl.duration();this.duration=t,t>0&&this.$emit("load"),this.$data._howlEvents=this.$data._howlEvents.map(function(t){"string"==typeof t&&(t={name:t});var n=function(n,o){"function"==typeof t.hook&&t.hook(n,o),e.$emit(t.name,n,o)};return e.$data._howl.on(t.name,n),d()({},t,{handler:n})})},_cleanup:function(){var e=this,t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.$data._howl&&this.stop(),s()(this.$data._polls).forEach(function(e){null!=e.id&&clearInterval(e.id)}),this.$data._howlEvents.map(function(t){if(t.handler){e.$data._howl&&e.$data._howl.off(t.name,t.handler);var n=d()({},t);return delete n.handler,n}return t}),this.$data._howl=null,this.duration=0,t&&(this.muted=!1,this.volume=1,this.rate=1)},play:function(){this.playing||this.$data._howl.play()},pause:function(){this.playing&&this.$data._howl.pause()},togglePlayback:function(){this.playing?this.$data._howl.pause():this.$data._howl.play()},stop:function(){this.$data._howl.stop()},mute:function(){this.$data._howl.mute(!0),this.muted=!0},unmute:function(){this.$data._howl.mute(!1),this.muted=!1},toggleMute:function(){this.$data._howl.mute(!this.muted),this.muted=!this.muted},setVolume:function(e){if("number"!=typeof e)throw new Error("volume must be a number, got a "+(void 0===e?"undefined":l(e))+" instead");this.$data._howl.volume(i()(e,0,1))},setRate:function(e){if("number"!=typeof e)throw new Error("rate must be a number, got a "+(void 0===e?"undefined":l(e))+" instead");this.$data._howl.rate(i()(e,.5,4))},setSeek:function(e){if("number"!=typeof e)throw new Error("seek must be a number, got a "+(void 0===e?"undefined":l(e))+" instead");this.$data._howl.seek(i()(e,0,this.duration))},setProgress:function(e){if("number"!=typeof e)throw new Error("progress must be a number, got a "+(void 0===e?"undefined":l(e))+" instead");this.setSeek(i()(e,0,1)*this.duration)}}}},function(e,t,n){var o=n(18);"string"==typeof o&&(o=[[e.i,o,""]]),o.locals&&(e.exports=o.locals);n(1)("53c61571",o,!0,{})},function(e,t,n){e.exports=n.p+"fonts_/iconfont.c27c4ff.eot"},function(e,t,n){"use strict";var o=n(4),r=n(5);n.n(r);t.a={name:"MiniAudio",mixins:[o.a],props:["width"],data:function(){return{min:0,max:100,slider:null,thunk:null,per:0,rate:1,isMute:!0,curVolume:.5,totalWidth:500}},watch:{curProgress:function(e){document.onmouseup||(this.per=e)}},computed:{curProgress:function(){return(Math.round(1e4*this.progress)/100).toFixed(2)},scale:function(){return(this.per-this.min)/(this.max-this.min)},pWidth:function(){return this.slider?this.slider.offsetWidth*this.scale+"px":"0px"},left:function(){return this.slider?this.slider.offsetWidth*this.scale-this.thunk.offsetWidth/2+"px":"0px"}},methods:{handleModifyProgress:function(e){if("slider"===e.target.className||"process"===e.target.className){var t=e.offsetX/this.slider.offsetWidth;this.setProgress(t)}},handleToggleMute:function(){this.isMute?this.isMute=!1:this.isMute=!0,this.toggleMute()},_sToMs:function(e){if("number"!=typeof e)return"00:00";e=parseInt(e);var t=void 0;return t=Math.floor(e/60),e%=60,t+="",e+="",t=1==t.length?"0"+t:t,e=1==e.length?"0"+e:e,t+":"+e}},mounted:function(){var e=this;this.width&&"number"==typeof this.width&&(this.totalWidth=this.width+"px"),this.setVolume(this.curVolume),this.slider=this.$refs.slider,this.thunk=this.$refs.trunk,this.thunk.onmousedown=function(t){var n=parseInt(e.pWidth),o=t.clientX;return document.onmousemove=function(t){var r=t.clientX-o+n,i=r/e.slider.offsetWidth;e.per=Math.ceil((e.max-e.min)*i+e.min),e.per=Math.max(e.per,e.min),e.per=Math.min(e.per,e.max)},document.onmouseup=function(){document.onmousemove=document.onmouseup=null,e.setProgress(e.scale)},!1}}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n.d(t,"VueAudioPlugin",function(){return a});var o=n(9),r=n(24);n.d(t,"VueAudio",function(){return o.a});var i={VueAudio:o.a,MiniAudio:r.a},a={install:function(e,t){Object.keys(i).forEach(function(t){e.component(i[t].name,i[t])})}};"undefined"!=typeof window&&window.Vue&&window.Vue.use(a),t.default=a},function(e,t,n){"use strict";function o(e){n(10)}var r=n(3),i=n(23),a=n(2),s=o,u=a(r.a,i.a,!1,s,"data-v-14400722",null);t.a=u.exports},function(e,t,n){var o=n(11);"string"==typeof o&&(o=[[e.i,o,""]]),o.locals&&(e.exports=o.locals);n(1)("26d24087",o,!0,{})},function(e,t,n){t=e.exports=n(0)(!1),t.push([e.i,".vueAudioBetter[data-v-14400722]{overflow:hidden;width:500px;margin:0 auto;background-color:#f3f2bd;border-radius:8px;box-shadow:5px 5px 10px -4px #63645e;background-image:linear-gradient(90deg,#9ca5f5,#7ff5ae)}.vueAudioBetter .total[data-v-14400722]{margin:20px auto;display:flex;align-items:center;justify-content:space-around;width:80%}.vueAudioBetter .operatorButton[data-v-14400722]{margin:0 auto;display:flex;align-items:center;justify-content:space-around;width:80%;height:38px}.operatorButton .rate[data-v-14400722]{font-size:32px}.operatorButton span[data-v-14400722]{font-size:24px;color:#0c0c0cb8;cursor:pointer}.operatorButton span[data-v-14400722]:first-child{color:#4a3535;font-size:28px}.operatorButton span[data-v-14400722]:hover{font-size:30px}.operatorButton span[data-v-14400722]:last-child:hover{font-size:36px}.vueAudioBetter .slider[data-v-14400722]{position:relative;margin:26px auto;width:80%;height:10px;background:#f8f7f7;border-radius:5px;cursor:pointer}.slider .process[data-v-14400722]{position:absolute;left:0;top:0;width:112px;height:10px;border-radius:5px;background:#409eff}.slider .thunk[data-v-14400722]{position:absolute;left:100px;top:-7px;width:20px;height:20px}.slider .block[data-v-14400722]{width:20px;height:20px;border-radius:50%;border:2px solid #409eff;background:#fff;transition:all .2s}.slider .block[data-v-14400722]:hover{transform:scale(1.1);opacity:.6}.slider .progressInfo[data-v-14400722]{position:absolute;top:-28px;color:#117eeb;font-weight:600}.operatorButton .iconfont[data-v-14400722]:active{position:relative;top:2px;left:2px}",""])},function(e,t){e.exports=function(e,t){for(var n=[],o={},r=0;r=0&&e<=1){if(t._volume=e,t._muted)return t;t.usingWebAudio&&t.masterGain.gain.setValueAtTime(e,a.ctx.currentTime);for(var n=0;n=0;t--)e._howls[t].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||a)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||a;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var t=new Audio;void 0===t.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(t){e.noAudio=!0}else e.noAudio=!0;try{var t=new Audio;t.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||a,t=null;try{t="undefined"!=typeof Audio?new Audio:null}catch(t){return e}if(!t||"function"!=typeof t.canPlayType)return e;var n=t.canPlayType("audio/mpeg;").replace(/^no$/,""),o=e._navigator&&e._navigator.userAgent.match(/OPR\/([0-6].)/g),r=o&&parseInt(o[0].split("/")[1],10)<33;return e._codecs={mp3:!(r||!n&&!t.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!n,opus:!!t.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!t.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!t.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!t.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),aac:!!t.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!t.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(t.canPlayType("audio/x-m4a;")||t.canPlayType("audio/m4a;")||t.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(t.canPlayType("audio/x-mp4;")||t.canPlayType("audio/mp4;")||t.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!!t.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),webm:!!t.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),dolby:!!t.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(t.canPlayType("audio/x-flac;")||t.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||a;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var t=function(n){for(var o=0;o0?s._seek:n._sprite[e][0]/1e3),l=Math.max(0,(n._sprite[e][0]+n._sprite[e][1])/1e3-d),c=1e3*l/Math.abs(s._rate),f=n._sprite[e][0]/1e3,p=(n._sprite[e][0]+n._sprite[e][1])/1e3,_=!(!s._loop&&!n._sprite[e][2]);s._sprite=e,s._ended=!1;var h=function(){s._paused=!1,s._seek=d,s._start=f,s._stop=p,s._loop=_};if(d>=p)return void n._ended(s);var m=s._node;if(n._webAudio){var v=function(){n._playLock=!1,h(),n._refreshBuffer(s);var e=s._muted||n._muted?0:s._volume;m.gain.setValueAtTime(e,a.ctx.currentTime),s._playStart=a.ctx.currentTime,void 0===m.bufferSource.start?s._loop?m.bufferSource.noteGrainOn(0,d,86400):m.bufferSource.noteGrainOn(0,d,l):s._loop?m.bufferSource.start(0,d,86400):m.bufferSource.start(0,d,l),c!==1/0&&(n._endTimers[s._id]=setTimeout(n._ended.bind(n,s),c)),t||setTimeout(function(){n._emit("play",s._id),n._loadQueue()},0)};"running"===a.state?v():(n._playLock=!0,n.once("resume",v),n._clearTimer(s._id))}else{var g=function(){m.currentTime=d,m.muted=s._muted||n._muted||a._muted||m.muted,m.volume=s._volume*a.volume(),m.playbackRate=s._rate;try{var o=m.play();if(o&&"undefined"!=typeof Promise&&(o instanceof Promise||"function"==typeof o.then)?(n._playLock=!0,h(),o.then(function(){n._playLock=!1,m._unlocked=!0,t||(n._emit("play",s._id),n._loadQueue())}).catch(function(){n._playLock=!1,n._emit("playerror",s._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),s._ended=!0,s._paused=!0})):t||(n._playLock=!1,h(),n._emit("play",s._id),n._loadQueue()),m.playbackRate=s._rate,m.paused)return void n._emit("playerror",s._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||s._loop?n._endTimers[s._id]=setTimeout(n._ended.bind(n,s),c):(n._endTimers[s._id]=function(){n._ended(s),m.removeEventListener("ended",n._endTimers[s._id],!1)},m.addEventListener("ended",n._endTimers[s._id],!1))}catch(e){n._emit("playerror",s._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=n._src,m.load());var y=window&&window.ejecta||!m.readyState&&a._navigator.isCocoonJS;if(m.readyState>=3||y)g();else{n._playLock=!0;var A=function(){g(),m.removeEventListener(a._canPlayEvent,A,!1)};m.addEventListener(a._canPlayEvent,A,!1),n._clearTimer(s._id)}}return s._id},pause:function(e){var t=this;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"pause",action:function(){t.pause(e)}}),t;for(var n=t._getSoundIds(e),o=0;o=0?t=parseInt(o[0],10):e=parseFloat(o[0])}else o.length>=2&&(e=parseFloat(o[0]),t=parseInt(o[1],10));var r;if(!(void 0!==e&&e>=0&&e<=1))return r=t?n._soundById(t):n._sounds[0],r?r._volume:0;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"volume",action:function(){n.volume.apply(n,o)}}),n;void 0===t&&(n._volume=e),t=n._getSoundIds(t);for(var i=0;i0?o/d:o),c=Date.now();e._fadeTo=n,e._interval=setInterval(function(){var r=(Date.now()-c)/o;c=Date.now(),s+=u*r,s=Math.max(0,s),s=Math.min(1,s),s=Math.round(100*s)/100,a._webAudio?e._volume=s:a.volume(s,e._id,!0),i&&(a._volume=s),(nt&&s>=n)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,a.volume(n,e._id),a._emit("fade",e._id))},l)},_stopFade:function(e){var t=this,n=t._soundById(e);return n&&n._interval&&(t._webAudio&&n._node.gain.cancelScheduledValues(a.ctx.currentTime),clearInterval(n._interval),n._interval=null,t.volume(n._fadeTo,e),n._fadeTo=null,t._emit("fade",e)),t},loop:function(){var e,t,n,o=this,r=arguments;if(0===r.length)return o._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(n=o._soundById(parseInt(r[0],10)))&&n._loop;e=r[0],o._loop=e}else 2===r.length&&(e=r[0],t=parseInt(r[1],10));for(var i=o._getSoundIds(t),a=0;a=0?t=parseInt(o[0],10):e=parseFloat(o[0])}else 2===o.length&&(e=parseFloat(o[0]),t=parseInt(o[1],10));var s;if("number"!=typeof e)return s=n._soundById(t),s?s._rate:n._rate;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"rate",action:function(){n.rate.apply(n,o)}}),n;void 0===t&&(n._rate=e),t=n._getSoundIds(t);for(var u=0;u=0?t=parseInt(o[0],10):n._sounds.length&&(t=n._sounds[0]._id,e=parseFloat(o[0]))}else 2===o.length&&(e=parseFloat(o[0]),t=parseInt(o[1],10));if(void 0===t)return n;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"seek",action:function(){n.seek.apply(n,o)}}),n;var s=n._soundById(t);if(s){if(!("number"==typeof e&&e>=0)){if(n._webAudio){var u=n.playing(t)?a.ctx.currentTime-s._playStart:0,d=s._rateSeek?s._rateSeek-s._seek:0;return s._seek+(d+u*Math.abs(s._rate))}return s._node.currentTime}var l=n.playing(t);l&&n.pause(t,!0),s._seek=e,s._ended=!1,n._clearTimer(t),n._webAudio||!s._node||isNaN(s._node.duration)||(s._node.currentTime=e);var c=function(){n._emit("seek",t),l&&n.play(t,!0)};if(l&&!n._webAudio){var f=function(){n._playLock?setTimeout(f,0):c()};setTimeout(f,0)}else c()}return n},playing:function(e){var t=this;if("number"==typeof e){var n=t._soundById(e);return!!n&&!n._paused}for(var o=0;o=0&&a._howls.splice(o,1);var r=!0;for(n=0;n=0){r=!1;break}return d&&r&&delete d[e._src],a.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,t,n,o){var r=this,i=r["_on"+e];return"function"==typeof t&&i.push(o?{id:n,fn:t,once:o}:{id:n,fn:t}),r},off:function(e,t,n){var o=this,r=o["_on"+e],i=0;if("number"==typeof t&&(n=t,t=null),t||n)for(i=0;i=0;i--)r[i].id&&r[i].id!==t&&"load"!==e||(setTimeout(function(e){e.call(this,t,n)}.bind(o,r[i].fn),0),r[i].once&&o.off(e,r[i].fn,r[i].id));return o._loadQueue(e),o},_loadQueue:function(e){var t=this;if(t._queue.length>0){var n=t._queue[0];n.event===e&&(t._queue.shift(),t._loadQueue()),e||n.action()}return t},_ended:function(e){var t=this,n=e._sprite;if(!t._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;o--){if(n<=t)return;e._sounds[o]._ended&&(e._webAudio&&e._sounds[o]._node&&e._sounds[o]._node.disconnect(0),e._sounds.splice(o,1),n--)}}},_getSoundIds:function(e){var t=this;if(void 0===e){for(var n=[],o=0;o=0;if(a._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),n))try{e.bufferSource.buffer=a._scratchBuffer}catch(e){}return e.bufferSource=null,t},_clearSound:function(e){/MSIE |Trident\//.test(a._navigator&&a._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var u=function(e){this._parent=e,this.init()};u.prototype={init:function(){var e=this,t=e._parent;return e._muted=t._muted,e._loop=t._loop,e._volume=t._volume,e._rate=t._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++a._counter,t._sounds.push(e),e.create(),e},create:function(){var e=this,t=e._parent,n=a._muted||e._muted||e._parent._muted?0:e._volume;return t._webAudio?(e._node=void 0===a.ctx.createGain?a.ctx.createGainNode():a.ctx.createGain(),e._node.gain.setValueAtTime(n,a.ctx.currentTime),e._node.paused=!0,e._node.connect(a.masterGain)):(e._node=a._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(a._canPlayEvent,e._loadFn,!1),e._node.src=t._src,e._node.preload="auto",e._node.volume=n*a.volume(),e._node.load()),e},reset:function(){var e=this,t=e._parent;return e._muted=t._muted,e._loop=t._loop,e._volume=t._volume,e._rate=t._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++a._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,t=e._parent;t._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(t._sprite).length&&(t._sprite={__default:[0,1e3*t._duration]}),"loaded"!==t._state&&(t._state="loaded",t._emit("load"),t._loadQueue()),e._node.removeEventListener(a._canPlayEvent,e._loadFn,!1)}};var d={},l=function(e){var t=e._src;if(d[t])return e._duration=d[t].duration,void p(e);if(/^data:[^;]+;base64,/.test(t)){for(var n=atob(t.split(",")[1]),o=new Uint8Array(n.length),r=0;r0?(d[t._src]=e,p(t,e)):n()};"undefined"!=typeof Promise&&1===a.ctx.decodeAudioData.length?a.ctx.decodeAudioData(e).then(o).catch(n):a.ctx.decodeAudioData(e,o,n)},p=function(e,t){t&&!e._duration&&(e._duration=t.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(a.usingWebAudio){try{"undefined"!=typeof AudioContext?a.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?a.ctx=new webkitAudioContext:a.usingWebAudio=!1}catch(e){a.usingWebAudio=!1}a.ctx||(a.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(a._navigator&&a._navigator.platform),t=a._navigator&&a._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),n=t?parseInt(t[1],10):null;if(e&&n&&n<9){var o=/safari/.test(a._navigator&&a._navigator.userAgent.toLowerCase());(a._navigator&&a._navigator.standalone&&!o||a._navigator&&!a._navigator.standalone&&!o)&&(a.usingWebAudio=!1)}a.usingWebAudio&&(a.masterGain=void 0===a.ctx.createGain?a.ctx.createGainNode():a.ctx.createGain(),a.masterGain.gain.setValueAtTime(a._muted?0:1,a.ctx.currentTime),a.masterGain.connect(a.ctx.destination)),a._setup()}};o=[],void 0!==(r=function(){return{Howler:a,Howl:s}}.apply(t,o))&&(e.exports=r),t.Howler=a,t.Howl=s,"undefined"!=typeof window?(window.HowlerGlobal=i,window.Howler=a,window.Howl=s,window.Sound=u):void 0!==n&&(n.HowlerGlobal=i,n.Howler=a,n.Howl=s,n.Sound=u)}(),/*! 11 | * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported. 12 | * 13 | * howler.js v2.1.2 14 | * howlerjs.com 15 | * 16 | * (c) 2013-2019, James Simpson of GoldFire Studios 17 | * goldfirestudios.com 18 | * 19 | * MIT License 20 | */ 21 | function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var t=this;if(!t.ctx||!t.ctx.listener)return t;for(var n=t._howls.length-1;n>=0;n--)t._howls[n].stereo(e);return t},HowlerGlobal.prototype.pos=function(e,t,n){var o=this;return o.ctx&&o.ctx.listener?(t="number"!=typeof t?o._pos[1]:t,n="number"!=typeof n?o._pos[2]:n,"number"!=typeof e?o._pos:(o._pos=[e,t,n],void 0!==o.ctx.listener.positionX?(o.ctx.listener.positionX.setTargetAtTime(o._pos[0],Howler.ctx.currentTime,.1),o.ctx.listener.positionY.setTargetAtTime(o._pos[1],Howler.ctx.currentTime,.1),o.ctx.listener.positionZ.setTargetAtTime(o._pos[2],Howler.ctx.currentTime,.1)):o.ctx.listener.setPosition(o._pos[0],o._pos[1],o._pos[2]),o)):o},HowlerGlobal.prototype.orientation=function(e,t,n,o,r,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var s=a._orientation;return t="number"!=typeof t?s[1]:t,n="number"!=typeof n?s[2]:n,o="number"!=typeof o?s[3]:o,r="number"!=typeof r?s[4]:r,i="number"!=typeof i?s[5]:i,"number"!=typeof e?s:(a._orientation=[e,t,n,o,r,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(n,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,t,n,o,r,i),a)},Howl.prototype.init=function(e){return function(t){var n=this;return n._orientation=t.orientation||[1,0,0],n._stereo=t.stereo||null,n._pos=t.pos||null,n._pannerAttr={coneInnerAngle:void 0!==t.coneInnerAngle?t.coneInnerAngle:360,coneOuterAngle:void 0!==t.coneOuterAngle?t.coneOuterAngle:360,coneOuterGain:void 0!==t.coneOuterGain?t.coneOuterGain:0,distanceModel:void 0!==t.distanceModel?t.distanceModel:"inverse",maxDistance:void 0!==t.maxDistance?t.maxDistance:1e4,panningModel:void 0!==t.panningModel?t.panningModel:"HRTF",refDistance:void 0!==t.refDistance?t.refDistance:1,rolloffFactor:void 0!==t.rolloffFactor?t.rolloffFactor:1},n._onstereo=t.onstereo?[{fn:t.onstereo}]:[],n._onpos=t.onpos?[{fn:t.onpos}]:[],n._onorientation=t.onorientation?[{fn:t.onorientation}]:[],e.call(this,t)}}(Howl.prototype.init),Howl.prototype.stereo=function(t,n){var o=this;if(!o._webAudio)return o;if("loaded"!==o._state)return o._queue.push({event:"stereo",action:function(){o.stereo(t,n)}}),o;var r=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===n){if("number"!=typeof t)return o._stereo;o._stereo=t,o._pos=[t,0,0]}for(var i=o._getSoundIds(n),a=0;an)throw new RangeError("`min` should be lower than `max`");return en?n:e}},function(e,t,n){"use strict";e.exports=function(e){return Object.keys(e).map(function(t){return e[t]})}},function(e,t,n){"use strict";function o(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}/* 22 | object-assign 23 | (c) Sindre Sorhus 24 | @license MIT 25 | */ 26 | var r=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var o={};return"abcdefghijklmnopqrst".split("").forEach(function(e){o[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},o)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,s,u=o(e),d=1;d", 6 | "license": "MIT", 7 | "main": "dist/v-audio.js", 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "howler": "^2.1.2", 14 | "vue": "^2.6.10" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/forijk/vue-audio-better.git" 19 | }, 20 | "homepage": "https://github.com/forijk/vue-audio-better/blob/master/README.md", 21 | "keywords": [ 22 | "front-end", 23 | "javascript", 24 | "web", 25 | "vue", 26 | "sound", 27 | "mp3", 28 | "vueaudio", 29 | "vue audio", 30 | "vue-audio", 31 | "vuejs", 32 | "audio", 33 | "audio.js", 34 | "player" 35 | ], 36 | "browserslist": [ 37 | "> 1%", 38 | "last 2 versions", 39 | "not ie <= 8" 40 | ], 41 | "devDependencies": { 42 | "babel-core": "^6.26.0", 43 | "babel-loader": "^7.1.2", 44 | "babel-preset-env": "^1.6.0", 45 | "babel-preset-stage-3": "^6.24.1", 46 | "cross-env": "^5.0.5", 47 | "css-loader": "^0.28.7", 48 | "file-loader": "^1.1.4", 49 | "imports-loader": "^0.8.0", 50 | "math-clamp": "^1.0.0", 51 | "object-values": "^2.0.0", 52 | "vue-loader": "^13.0.5", 53 | "vue-template-compiler": "^2.6.10", 54 | "webpack": "^3.6.0", 55 | "webpack-dev-server": "^2.9.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/MiniAudio.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | {{ _sToMs(seek) }} / {{ _sToMs(duration) }} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 36 | 37 | 38 | 149 | 150 | 225 | -------------------------------------------------------------------------------- /src/VueAudio.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ _sToMs(seek) }} / {{ _sToMs(duration) }} 5 | {{ curProgress }}% 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 153 | 154 | 246 | -------------------------------------------------------------------------------- /src/audio.js: -------------------------------------------------------------------------------- 1 | import { Howl } from "howler"; 2 | import clamp from "math-clamp"; 3 | import values from "object-values"; 4 | import assign from "object-assign"; 5 | 6 | export default { 7 | props: { 8 | /** 9 | * A string type url of audio file 10 | */ 11 | audioSource: { 12 | type: String, 13 | required: true, 14 | validator(audioSource) { 15 | // Every audioSource must be a non-empty string 16 | return typeof audioSource === "string" && audioSource.length > 0 17 | } 18 | }, 19 | /** 20 | * Whether to start the playback 21 | * when the component is mounted 22 | */ 23 | autoplay: { 24 | type: Boolean, 25 | default: false 26 | }, 27 | /** 28 | * Whether to start the playback again 29 | * automatically after it is done playing 30 | */ 31 | loop: { 32 | type: Boolean, 33 | default: false 34 | }, 35 | /** 36 | * Whether to start downloading the audio 37 | * file when the component is mounted 38 | */ 39 | preload: { 40 | type: Boolean, 41 | default: true 42 | }, 43 | /** 44 | * Whether to force HTML5 Audio 45 | */ 46 | html5: { 47 | type: Boolean, 48 | default: false 49 | }, 50 | /** 51 | * An array of audio file types 52 | */ 53 | formats: { 54 | type: Array, 55 | default() { 56 | return []; 57 | } 58 | }, 59 | /** 60 | * Whether to enable the withCredentials flag on XHR 61 | * requests used to fetch audio files when using Web Audio API 62 | */ 63 | xhrWithCredentials: { 64 | type: Boolean, 65 | default: false 66 | } 67 | }, 68 | 69 | data() { 70 | return { 71 | /** 72 | * The Howl instance used for playback 73 | */ 74 | _howl: null, 75 | /** 76 | * Whether audio is currently playing 77 | */ 78 | playing: false, 79 | /** 80 | * Whether the audio playback is muted 81 | */ 82 | muted: false, 83 | /** 84 | * The volume of the playback on a scale of 0 to 1 85 | */ 86 | volume: 1.0, 87 | /** 88 | * The rate (speed) of the playback on a scale of 0.5 to 4 89 | */ 90 | rate: 1.0, 91 | /** 92 | * The position of playback in seconds 93 | */ 94 | seek: 0, 95 | /** 96 | * The duration of the audio in seconds 97 | */ 98 | duration: 0, 99 | /** 100 | * Functions that poll the Howl instance 101 | * to update various data 102 | */ 103 | _polls: { 104 | seek: { 105 | id: null, 106 | interval: 1000 / 4, // 4 times per second (4Hz) 107 | hook: () => { 108 | this.seek = this.$data._howl.seek(); 109 | } 110 | } 111 | }, 112 | /** 113 | * A list of howl events to listen to and 114 | * functions to call when they are triggered 115 | */ 116 | _howlEvents: [ 117 | { 118 | name: "load", 119 | hook: () => { 120 | this.duration = this.$data._howl.duration(); 121 | } 122 | }, 123 | "loaderror", 124 | "playerror", 125 | { 126 | name: "play", 127 | hook: () => { 128 | this.playing = true; 129 | } 130 | }, 131 | { 132 | name: "end", 133 | hook: () => { 134 | this.playing = false; 135 | } 136 | }, 137 | { 138 | name: "pause", 139 | hook: () => { 140 | this.playing = false; 141 | } 142 | }, 143 | { 144 | name: "stop", 145 | hook: () => { 146 | this.playing = false; 147 | if (this.$data._howl != null) { 148 | this.seek = this.$data._howl.seek(); 149 | } 150 | } 151 | }, 152 | "mute", 153 | { 154 | name: "volume", 155 | hook: () => { 156 | this.volume = this.$data._howl.volume(); 157 | } 158 | }, 159 | { 160 | name: "rate", 161 | hook: () => { 162 | this.rate = this.$data._howl.rate(); 163 | } 164 | }, 165 | { 166 | name: "seek", 167 | hook: () => { 168 | if(!this.playing) this.seek = this.$data._howl.seek(); 169 | } 170 | }, 171 | "fade" 172 | ] 173 | }; 174 | }, 175 | 176 | computed: { 177 | /** 178 | * The progress of the playback on a scale of 0 to 1 179 | */ 180 | progress() { 181 | if (this.duration === 0) return 0; 182 | return this.seek / this.duration; 183 | } 184 | }, 185 | 186 | created() { 187 | this._initialize(); 188 | }, 189 | 190 | beforeDestroy() { 191 | this._cleanup(); 192 | }, 193 | 194 | watch: { 195 | playing(playing) { 196 | // Update the seek 197 | this.seek = this.$data._howl.seek(); 198 | 199 | if (playing) { 200 | // Start the seek poll 201 | this.$data._polls.seek.id = setInterval( 202 | this.$data._polls.seek.hook, 203 | this.$data._polls.seek.interval 204 | ); 205 | } else { 206 | // Stop the seek poll 207 | clearInterval(this.$data._polls.seek.id); 208 | } 209 | }, 210 | 211 | audioSource(audioSource) { 212 | this._reinitialize(); 213 | } 214 | }, 215 | 216 | methods: { 217 | /** 218 | * Reinitialize the Howler player 219 | */ 220 | _reinitialize() { 221 | this._cleanup(false); 222 | this._initialize(); 223 | }, 224 | /** 225 | * Initialize the Howler player 226 | */ 227 | _initialize() { 228 | this.$data._howl = new Howl({ 229 | src: this.audioSource, 230 | volume: this.volume, 231 | html5: this.html5, 232 | loop: this.loop, 233 | preload: this.preload, 234 | autoplay: this.autoplay, 235 | mute: this.muted, 236 | rate: this.rate, 237 | format: this.formats, 238 | xhrWithCredentials: this.xhrWithCredentials 239 | }); 240 | 241 | const duration = this.$data._howl.duration(); 242 | this.duration = duration; 243 | 244 | if (duration > 0) { 245 | // The audio file(s) have been cached. Howler won't 246 | // emit a load event, so we will do this manually 247 | this.$emit("load"); 248 | } 249 | 250 | // Bind to all Howl events 251 | this.$data._howlEvents = this.$data._howlEvents.map(event => { 252 | // Normalize string shorthands to objects 253 | if (typeof event === "string") { 254 | event = { name: event }; 255 | } 256 | 257 | // Create a handler 258 | const handler = (id, details) => { 259 | if (typeof event.hook === "function") event.hook(id, details); 260 | this.$emit(event.name, id, details); 261 | }; 262 | 263 | // Bind the handler 264 | this.$data._howl.on(event.name, handler); 265 | 266 | // Return the name and handler to unbind later 267 | return assign({}, event, { handler }); 268 | }); 269 | }, 270 | /** 271 | * Clean up the Howler player 272 | */ 273 | _cleanup(resetSettings = true) { 274 | // Stop all playback 275 | if (this.$data._howl) { 276 | this.stop(); 277 | } 278 | 279 | // Stop all polls 280 | values(this.$data._polls).forEach(poll => { 281 | if (poll.id != null) clearInterval(poll.id); 282 | }); 283 | 284 | // Clear all event listeners 285 | this.$data._howlEvents.map(event => { 286 | if (event.handler) { 287 | if (this.$data._howl) { 288 | this.$data._howl.off(event.name, event.handler); 289 | } 290 | 291 | const _event = assign({}, event); 292 | delete _event.handler; 293 | return _event; 294 | } 295 | 296 | return event; 297 | }); 298 | 299 | // Destroy the Howl instance 300 | this.$data._howl = null; 301 | 302 | this.duration = 0; 303 | 304 | if (resetSettings) { 305 | this.muted = false; 306 | this.volume = 1.0; 307 | this.rate = 1.0; 308 | } 309 | }, 310 | /** 311 | * Start the playback 312 | */ 313 | play() { 314 | if (!this.playing) this.$data._howl.play(); 315 | }, 316 | /** 317 | * Pause the playback 318 | */ 319 | pause() { 320 | if (this.playing) this.$data._howl.pause(); 321 | }, 322 | /** 323 | * Toggle playing or pausing the playback 324 | */ 325 | togglePlayback() { 326 | if (!this.playing) { 327 | this.$data._howl.play(); 328 | } else { 329 | this.$data._howl.pause(); 330 | } 331 | }, 332 | /** 333 | * Stop the playback (also resets the seek to 0) 334 | */ 335 | stop() { 336 | this.$data._howl.stop(); 337 | }, 338 | /** 339 | * Mute the playback 340 | */ 341 | mute() { 342 | this.$data._howl.mute(true); 343 | this.muted = true; 344 | }, 345 | /** 346 | * Unmute the playback 347 | */ 348 | unmute() { 349 | this.$data._howl.mute(false); 350 | this.muted = false; 351 | }, 352 | /** 353 | * Toggle muting and unmuting the playback 354 | */ 355 | toggleMute() { 356 | this.$data._howl.mute(!this.muted); 357 | this.muted = !this.muted; 358 | }, 359 | /** 360 | * Set the volume of the playback 361 | * @param {Number} volume - The new volume. 362 | * The value is clamped between 0 and 1 363 | */ 364 | setVolume(volume) { 365 | if (typeof volume !== "number") { 366 | throw new Error( 367 | `volume must be a number, got a ${typeof volume} instead` 368 | ); 369 | } 370 | 371 | this.$data._howl.volume(clamp(volume, 0, 1)); 372 | }, 373 | /** 374 | * Set the rate (speed) of the playback 375 | * @param {Number} rate - The new rate. 376 | * The value is clamped between 0.5 and 4 377 | */ 378 | setRate(rate) { 379 | if (typeof rate !== "number") { 380 | throw new Error(`rate must be a number, got a ${typeof rate} instead`); 381 | } 382 | 383 | this.$data._howl.rate(clamp(rate, 0.5, 4)); 384 | }, 385 | /** 386 | * Set the position of the playback 387 | * @param {Number} seek - The new position in seconds. 388 | * The value is clamped between 0 and the duration 389 | */ 390 | setSeek(seek) { 391 | if (typeof seek !== "number") { 392 | throw new Error(`seek must be a number, got a ${typeof seek} instead`); 393 | } 394 | 395 | this.$data._howl.seek(clamp(seek, 0, this.duration)); 396 | }, 397 | /** 398 | * Set the progress of the playback 399 | * @param {Number} progress - The new progress. 400 | * The value is clamped between 0 and 1 401 | */ 402 | setProgress(progress) { 403 | if (typeof progress !== "number") { 404 | throw new Error( 405 | `progress must be a number, got a ${typeof progress} instead` 406 | ); 407 | } 408 | 409 | this.setSeek(clamp(progress, 0, 1) * this.duration); 410 | } 411 | } 412 | }; 413 | -------------------------------------------------------------------------------- /src/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1573030555456'); /* IE9 */ 3 | src: url('iconfont.eot?t=1573030555456#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1573030555456') format('woff'), 6 | url('iconfont.ttf?t=1573030555456') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1573030555456#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-notificationfill:before { 19 | content: "\e66a"; 20 | } 21 | 22 | .icon-roundaddfill:before { 23 | content: "\e6d8"; 24 | } 25 | 26 | .icon-notificationforbidfill:before { 27 | content: "\e6db"; 28 | } 29 | 30 | .icon-speed-:before { 31 | content: "\e6f8"; 32 | } 33 | 34 | .icon-speed-1:before { 35 | content: "\e6f9"; 36 | } 37 | 38 | .icon-speed-2:before { 39 | content: "\e6fa"; 40 | } 41 | 42 | .icon-subtract_fill:before { 43 | content: "\e808"; 44 | } 45 | 46 | .icon-pausecircle-fill:before { 47 | content: "\e7cb"; 48 | } 49 | 50 | .icon-stopcircle-fill:before { 51 | content: "\e7cc"; 52 | } 53 | 54 | .icon-playcircle-fill:before { 55 | content: "\e7cd"; 56 | } 57 | 58 | .icon-pausecircle:before { 59 | content: "\e80d"; 60 | } 61 | 62 | .icon-stopcircle:before { 63 | content: "\e80e"; 64 | } 65 | 66 | .icon-playcircle:before { 67 | content: "\e80f"; 68 | } 69 | 70 | .icon-minus:before { 71 | content: "\e828"; 72 | } 73 | 74 | .icon-plus:before { 75 | content: "\e829"; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forijk/vue-audio-better/3cada27d313d43d1553c58a48816c5a319f0e775/src/font/iconfont.eot -------------------------------------------------------------------------------- /src/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forijk/vue-audio-better/3cada27d313d43d1553c58a48816c5a319f0e775/src/font/iconfont.ttf -------------------------------------------------------------------------------- /src/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forijk/vue-audio-better/3cada27d313d43d1553c58a48816c5a319f0e775/src/font/iconfont.woff -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import VueAudio from "./VueAudio.vue"; 2 | import MiniAudio from "./MiniAudio.vue"; 3 | 4 | const Components = { 5 | VueAudio, 6 | MiniAudio 7 | }; 8 | 9 | const VueAudioPlugin = { 10 | install(Vue, options) { 11 | Object.keys(Components).forEach(component => { 12 | Vue.component(Components[component].name, Components[component]); 13 | }); 14 | } 15 | }; 16 | 17 | if (typeof window !== "undefined" && window.Vue) { 18 | window.Vue.use(VueAudioPlugin); 19 | } 20 | 21 | export { VueAudio, VueAudioPlugin }; 22 | 23 | export default VueAudioPlugin; 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | module.exports = { 5 | entry: "./src/index.js", 6 | output: { 7 | path: path.resolve(__dirname, "./dist"), 8 | publicPath: "dist/", 9 | filename: "v-audio.js", 10 | library: "vAudio", 11 | libraryTarget: "umd", 12 | umdNamedDefine: true 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/, 18 | use: ["vue-style-loader", "css-loader"] 19 | }, 20 | { 21 | test: /\.vue$/, 22 | loader: "vue-loader" 23 | }, 24 | { 25 | test: /\.js$/, 26 | loader: "babel-loader", 27 | include: [ 28 | path.join(__dirname, "./src"), 29 | path.join(__dirname, "./node_modules/object-values/index.js") 30 | ] 31 | }, 32 | { 33 | test: /\.(png|jpg|gif|svg)$/, 34 | loader: "file-loader", 35 | options: { 36 | name: "[name].[ext]?[hash]" 37 | } 38 | }, 39 | { 40 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 41 | use: [{ 42 | loader: 'file-loader', 43 | options: { 44 | limit: 10000, 45 | name: 'fonts_/[name].[hash:7].[ext]' 46 | } 47 | }] 48 | } 49 | ] 50 | }, 51 | resolve: { 52 | alias: { 53 | vue$: "vue/dist/vue.esm.js" 54 | }, 55 | extensions: ["*", ".js", ".vue", ".json"] 56 | }, 57 | devServer: { 58 | historyApiFallback: true, 59 | noInfo: true, 60 | overlay: true 61 | }, 62 | performance: { 63 | hints: false 64 | }, 65 | devtool: "#eval-source-map" 66 | }; 67 | 68 | if (process.env.NODE_ENV === "production") { 69 | module.exports.devtool = "#source-map"; 70 | module.exports.plugins = (module.exports.plugins || []).concat([ 71 | new webpack.DefinePlugin({ 72 | "process.env": { 73 | NODE_ENV: '"production"' 74 | } 75 | }), 76 | new webpack.optimize.UglifyJsPlugin({ 77 | sourceMap: true, 78 | compress: { 79 | warnings: false 80 | } 81 | }), 82 | new webpack.LoaderOptionsPlugin({ 83 | minimize: true 84 | }) 85 | ]); 86 | } 87 | --------------------------------------------------------------------------------