Skip to content

Instantly share code, notes, and snippets.

@mchome
Created April 8, 2018 06:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mchome/384be86a26f55f25e177a8b687281640 to your computer and use it in GitHub Desktop.
Save mchome/384be86a26f55f25e177a8b687281640 to your computer and use it in GitHub Desktop.
vue-audio
<template>
<div id="app">
<!-- <p>Vue音乐播放器</p> -->
<vueAudio :filepath="[file, file2]" :defaultvolume="`70`" :preload="`auto`"></vueAudio>
<vueAudio :filepath="[file, file2]" :defaultvolume="`70`" :preload="`auto`" :usewaveform="true">
<!-- <vueAudio :filepath="[file, file2, file3, file4, file5]" :defaultvolume="`70`" :preload="`auto`" :autoplay="true"> -->
<!-- <div slot="progressline" class="audio-progressline"></div> -->
<!-- <p slot="pausebtn">PAUSE</p>
<p slot="playbtn">PLAY</p>
<p slot="previewbtn">PREVIEW</p>
<p slot="nextbtn">NEXT</p> -->
</vueAudio>
</div>
</template>
<script>
import vueAudio from './components/vue-audio'
export default {
name: 'app',
components: {
vueAudio
},
data () {
return {
file: '/static/1.mp3',
file2: '/static/2.mp3'
}
}
}
</script>
<style>
@import './vue-audio.css';
body {
margin: 0;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
justify-content: space-around;
align-items: center;
height: 100vh;
width: 100%;
background-image: url(/static/background.jpg);
background-size: cover;
flex-direction: column;
}
</style>
<template>
<div class="audio-container">
<audio :src="currentFile" ref="audiotag"></audio>
<div class="audio-control">
<span v-show="playing" class="audio-pause-btn" @click="doPause">
<slot name="pausebtn">
<a class="_btn"></a>
</slot>
</span>
<span v-show="!playing" class="audio-play-btn" @click="doPlay">
<slot name="playbtn">
<a class="_btn"></a>
</slot>
</span>
<span class="audio-preview-btn" @click="selectPreview">
<slot name="previewbtn">
<a class="_btn"></a>
</slot>
</span>
<span class="audio-next-btn" @click="selectNext">
<slot name="nextbtn">
<a class="_btn"></a>
</slot>
</span>
</div>
<div class="audio-progress"
@mousedown="draggingSeek = true"
@mouseup="draggingSeek = false">
<div class="audio-progressbar">
<slot name="progressline">
<div class="audio-progressline">
<div class="audio-progressline-playing"></div>
<div class="audio-progressline-buffering"></div>
</div>
</slot>
<input class="audio-seek _seek" v-model="seek" type="range" min="0" :max="duration" @change="setSeek(seek)">
</div>
<slot name="time">
<p class="_text">{{ readableTime(seek) }}</p>
<p class="_text">/</p>
<p class="_text">{{ readableTime(duration) }}</p>
</slot>
</div>
<div class="audio-util">
<span class="audio-playmode-btn" v-show="playmodes[playmode] === 'fullloop'" @click="cyclePlaymode">
<slot name="playmodebtn">
<a class="_fullloop-btn"></a>
</slot>
</span>
<span class="audio-playmode-btn" v-show="playmodes[playmode] === 'singleloop'" @click="cyclePlaymode">
<slot name="playmodebtn">
<a class="_singleloop-btn"></a>
</slot>
</span>
<span class="audio-playmode-btn" v-show="playmodes[playmode] === 'random'" @click="cyclePlaymode">
<slot name="playmodebtn">
<a class="_random-btn"></a>
</slot>
</span>
<span class="audio-playmode-btn" v-show="playmodes[playmode] === 'noloop'" @click="cyclePlaymode">
<slot name="playmodebtn">
<a class="_noloop-btn"></a>
</slot>
</span>
</div>
<div class="audio-volume">
<div class="audio-volume-control" @mouseover="isVolumehover = true" @mouseout="isVolumehover = false">
<span class="audio-volume-mute" v-show="parseInt(volume)" @click="doMute">
<slot name="mutebtn">
<a class="_btn"></a>
</slot>
</span>
<span class="audio-volume-muted" v-show="!parseInt(volume)" @click="doUnmute">
<slot name="mutedbtn">
<a class="_btn"></a>
</slot>
</span>
<transition name="trackbar-fade">
<div class="audio-volume-slider" v-show="isVolumehover">
<input class="audio-volume-trackbar _seek" v-model="volume" type="range" min="0" max="100">
</div>
</transition>
</div>
</div>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'vue-audio',
props: {
filepath: {
type: [String, Array],
default: ''
},
playmodes: {
type: Array,
default: () => ['fullloop', 'singleloop', 'random', 'noloop']
},
defaultvolume: {
type: String,
default: '60'
}
},
data () {
return {
playing: false,
volume: this.defaultvolume,
mutedvolume: '0',
duration: 0,
seek: 0,
playmode: 0,
order: 0,
isVolumehover: false,
draggingSeek: false
}
},
methods: {
doPlay: function () {
if (this.currentFile.length) {
this.audioController.play()
this.playing = true
}
},
doPause: function () {
if (this.currentFile.length) {
this.audioController.pause()
this.playing = false
}
},
selectPreview: function () {
(this.order <= 0) ? this.order = this.file.length - 1 : Math.abs(this.order -= 1)
},
selectNext: function () {
(this.order >= (this.file.length - 1)) ? this.order = 0 : Math.abs(this.order += 1)
},
randomNext: function () {
if (this.file.length < 3) {
this.selectNext()
} else {
this.order = Math.floor(Math.random() * this.file.length)
}
},
doMute: function () {
this.mutedvolume = this.volume
this.volume = '0'
},
doUnmute: function () {
this.volume = this.mutedvolume
},
loadDuration: function () {
if (this.currentFile.length) {
this.audioController.addEventListener('loadedmetadata', function () {
this.duration = this.audioController.duration
}.bind(this))
}
},
loadCurrenttime: function () {
if (this.currentFile.length) {
this.audioController.addEventListener('timeupdate', function () {
if (!this.draggingSeek) {
this.seek = this.audioController.currentTime
}
}.bind(this))
}
},
setSeek: function (val) {
if (this.currentFile.length) {
this.audioController.currentTime = Math.abs(parseInt(val))
}
},
cyclePlaymode: function () {
(this.playmode < (this.playmodes.length - 1)) ? this.playmode += 1 : this.playmode = 0
},
readableTime: function (time) {
const min = padStart('' + Math.floor(time / 60), 2, '0')
const sec = padStart('' + Math.floor(time - min * 60), 2, '0')
return (min + ':' + sec)
},
bufferPercent: function () {
}
},
watch: {
order: function () {
this.$nextTick(function () {
this.playing ? this.doPlay() : this.doPause()
})
},
volume: function () {
this.audioController.volume = Math.abs(parseInt(this.volume) / 100)
},
seek: function () {
if (this.playingBar) {
this.playingBar.style.width = ((this.seek / this.duration) * 100).toFixed(2) + '%'
}
if (this.seek >= this.duration) {
switch (this.playmodes[this.playmode]) {
case 'fullloop':
this.selectNext()
break
case 'singleloop':
this.seek = 0
this.doPlay()
break
case 'random':
this.randomNext()
break
case 'noloop':
this.seek = 0
this.playing = false
break
default:
this.seek = 0
this.playing = false
break
}
}
}
},
computed: {
file: function () {
if (typeof this.filepath === 'string') {
return [this.filepath]
} else {
return this.filepath
}
},
audioController: function () {
return this.$refs.audiotag
},
// displayPreviewbtn: function () {
// return (this.file.length > 1 && this.order > 0)
// },
// displayNextbtn: function () {
// return (this.file.length > 1 && (this.file.length - 1) !== this.order)
// },
currentFile: function () {
return this.file[this.order]
},
playingBar: function () {
return document.querySelector('.audio-progressline-playing')
},
bufferingBar: function () {
return document.querySelector('.audio-progressline-buffering')
}
},
mounted () {
this.$nextTick(function () {
this.loadDuration()
this.loadCurrenttime()
})
}
}
function padStart (str, targetLength, padString) {
targetLength = targetLength >> 0
padString = String(padString || ' ')
if (str.length > targetLength) {
return String(str)
} else {
targetLength = targetLength - str.length
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length)
}
return padString.slice(0, targetLength) + String(str)
}
}
</script>
<style>
.audio-container {
user-select: none;
cursor: default;
}
.audio-pause-btn,
.audio-play-btn,
.audio-preview-btn,
.audio-next-btn,
.audio-volume-mute,
.audio-volume-muted,
.audio-playmode-btn {
cursor: pointer;
}
.trackbar-fade-enter-active {
transition: opacity .3s ease-in-out;
}
.trackbar-fade-enter {
opacity: 0;
}
</style>
function getCanvas (audioCtx, url) {
let req = new XMLHttpRequest()
req.open('GET', url, true)
req.responseType = 'arraybuffer'
req.onload = function () {
audioCtx.decodeAudioData(req.response)
.then(function (buffer) {
drawWaveform(buffer)
audioCtx.close()
})
}
req.send()
}
function drawWaveform (buffer) {
const canvasWidth = 360
const canvasHeight = 60
const canvas = document.querySelector('#waveform')
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
const drawLines = 300
let leftChannel = buffer.getChannelData(0)
ctx.save()
ctx.fillStyle = 'transparent'
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
ctx.strokeStyle = 'white'
ctx.globalCompositeOperation = 'lighter'
ctx.translate(0, 360 / 4)
ctx.lineWidth = 2
let totallength = leftChannel.length
let eachBlock = Math.floor(totallength / drawLines)
let lineGap = (canvasWidth / drawLines)
ctx.beginPath()
for (let i = 0; i <= drawLines; i++) {
let audioBuffKey = Math.floor(eachBlock * i)
let x = i * lineGap
let y = leftChannel[audioBuffKey] * canvasHeight / 2
ctx.moveTo(x, y)
ctx.lineTo(x, (y * -1))
}
ctx.stroke()
ctx.restore()
}
export default getCanvas
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment