mirror of
https://github.com/kevin-DL/vue-audio-recorder.git
synced 2026-01-22 23:35:27 +00:00
Add existing code
This commit is contained in:
84
src/components/icon-button.vue
Normal file
84
src/components/icon-button.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<style lang="scss">
|
||||
.ar-icon-button {
|
||||
fill: #747474;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #05CBCD;
|
||||
background-color: #FFFFFF;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
transition: .2s;
|
||||
|
||||
&--rec {
|
||||
fill: white;
|
||||
background-color: #FF6B64;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&--pulse {
|
||||
animation: ripple .5s linear infinite;
|
||||
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
box-shadow:
|
||||
0 0 0 0 rgba(red, 0.1),
|
||||
0 0 0 1px rgba(red, 0.1),
|
||||
0 0 0 5px rgba(red, 0.1);
|
||||
}
|
||||
100% {
|
||||
box-shadow:
|
||||
0 0 0 0 rgba(red, 0.1),
|
||||
0 0 0 10px rgba(red, 0.1),
|
||||
0 0 0 20px rgba(red, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--clicked {
|
||||
fill: white;
|
||||
background-color: #05CBCD;
|
||||
}
|
||||
|
||||
&__sm {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
&__lg {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
box-shadow: 0 2px 5px 1px rgba(158,158,158,0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div :class="iconClasses" v-html="icons[name]"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: { type: String },
|
||||
size: { type: String, default: 'sm' }
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
icons: {
|
||||
download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z"/><path fill="none" d="M0 0h24v24H0z"/></svg>',
|
||||
mic: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
|
||||
pause: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
|
||||
play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
|
||||
save: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>',
|
||||
stop: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M6 6h12v12H6z"/></svg>',
|
||||
volume: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconClasses () {
|
||||
return ['ar-icon-button', `ar-icon-button__${this.size}`]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
54
src/components/line-control.vue
Normal file
54
src/components/line-control.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<style lang="scss">
|
||||
.ar-line-control {
|
||||
position: relative;
|
||||
|
||||
&__head {
|
||||
position: absolute;
|
||||
height: inherit;
|
||||
background-color: #616161;
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div :ref="refId" class="ar-line-control" @mousedown="onMouseDown">
|
||||
<div class="ar-line-control__head" :style="{width: percentageWidth}"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { calculateLineHeadPosition } from '../lib/utils.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
refId: { type: String },
|
||||
eventName: { type: String },
|
||||
percentage: { type: Number, default: 0 }
|
||||
},
|
||||
methods: {
|
||||
onMouseDown (ev) {
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('changeLineHead', seekPos)
|
||||
document.addEventListener('mousemove', this.onMouseMove)
|
||||
document.addEventListener('mouseup', this.onMouseUp)
|
||||
},
|
||||
onMouseUp (ev) {
|
||||
document.removeEventListener('mouseup', this.onMouseUp)
|
||||
document.removeEventListener('mousemove', this.onMouseMove)
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('changeLineHead', seekPos)
|
||||
},
|
||||
onMouseMove (ev) {
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('changeLineHead', seekPos)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
percentageWidth () {
|
||||
let width = this.percentage < 1 ? this.percentage * 100 : this.percentage
|
||||
return `${width}%`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
227
src/components/record-player.vue
Normal file
227
src/components/record-player.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<style lang="scss">
|
||||
.ar-player {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border: 1px solid #E8E8E8;
|
||||
border-radius: 24px;
|
||||
background-color: #FAFAFA;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&-actions {
|
||||
width: 55%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&-volume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 10px;
|
||||
|
||||
&-bar {
|
||||
width: 50px;
|
||||
height: 8px;
|
||||
background: #E6E6E6;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
fill: #747474;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
background-color: unset;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
width: 160px;
|
||||
height: 8px;
|
||||
border-radius: 5px;
|
||||
background-color: #E6E6E6;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
&__time {
|
||||
color: rgba(84,84,84,0.5);
|
||||
font-size: 16px;
|
||||
width: 41px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="ar-player" :class="{'ar-player--active': record.url}">
|
||||
<div class="ar-player-bar">
|
||||
|
||||
<div class="ar-player__time">{{playedTime}}</div>
|
||||
|
||||
<line-control
|
||||
class="ar-player__progress"
|
||||
ref-id="progress"
|
||||
:percentage="progress"
|
||||
@changeLineHead="_onUpdateProgress"/>
|
||||
|
||||
<div class="ar-player__time">{{duration}}</div>
|
||||
|
||||
<div class="ar-player-volume">
|
||||
<icon-button class="ar-player-volume__icon" name="volume"/>
|
||||
<line-control
|
||||
class="ar-player-volume-bar"
|
||||
ref-id="volume"
|
||||
:percentage="volume"
|
||||
@changeLineHead="_onUpdateVolume"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ar-player-actions">
|
||||
<icon-button name="download" @click.native="download"/>
|
||||
<icon-button
|
||||
size="lg"
|
||||
:name="playBtnIcon"
|
||||
:class="{'ar-icon-button--clicked': isPlaying}"
|
||||
@click.native="playback"/>
|
||||
<icon-button name="save" @click.native="upload"/>
|
||||
</div>
|
||||
|
||||
<audio id="audio-recorder-player" :src="record.url"></audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './icon-button.vue'
|
||||
import LineControl from './line-control.vue'
|
||||
import { convertTimeMMSS } from '../lib/utils.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
uploadUrl: { type: String },
|
||||
record: { type: Object },
|
||||
startUpload: { type: Function },
|
||||
successfulUpload: { type: Function },
|
||||
failedUpload: { type: Function }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isPlaying: false,
|
||||
duration: convertTimeMMSS(0),
|
||||
playedTime: convertTimeMMSS(0),
|
||||
progress: 0,
|
||||
volume: 0.8
|
||||
}
|
||||
},
|
||||
components: {
|
||||
IconButton,
|
||||
LineControl
|
||||
},
|
||||
mounted: function() {
|
||||
this.player = document.getElementById('audio-recorder-player')
|
||||
|
||||
this.player.addEventListener('ended', () => {
|
||||
this.isPlaying = false
|
||||
})
|
||||
|
||||
this.player.addEventListener('loadeddata', (ev) => {
|
||||
this._resetProgress()
|
||||
this.duration = convertTimeMMSS(this.player.duration)
|
||||
})
|
||||
|
||||
this.player.addEventListener('timeupdate', this._onTimeUpdate)
|
||||
},
|
||||
computed: {
|
||||
playBtnIcon () {
|
||||
return this.isPlaying ? 'pause' : 'play'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
playback () {
|
||||
if (!this.record.url) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isPlaying) {
|
||||
this.player.pause()
|
||||
} else {
|
||||
setTimeout(() => { this.player.play() }, 0)
|
||||
}
|
||||
|
||||
this.isPlaying = !this.isPlaying
|
||||
},
|
||||
upload () {
|
||||
if (!this.record.url) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.startUpload) {
|
||||
this.startUpload()
|
||||
}
|
||||
|
||||
this.$emit('on-start-upload')
|
||||
|
||||
let data = new FormData()
|
||||
data.append('audio', this.record.blob, 'my-record')
|
||||
|
||||
this.$http.post(this.uploadUrl, data, {
|
||||
headers: {'Content-Type': `multipart/form-data; boundary=${data._boundary}`}
|
||||
}).then(resp => {
|
||||
this.$emit('on-end-upload', 'success')
|
||||
if (this.successfulUpload) {
|
||||
this.successfulUpload(resp)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$emit('on-end-upload', 'fail')
|
||||
if (this.failedUpload) {
|
||||
this.failedUpload(error)
|
||||
}
|
||||
})
|
||||
},
|
||||
download () {
|
||||
if (!this.record.url) {
|
||||
return
|
||||
}
|
||||
|
||||
let link = document.createElement('a')
|
||||
link.href = this.record.url
|
||||
link.download = 'record.wav'
|
||||
link.click()
|
||||
},
|
||||
_resetProgress () {
|
||||
this.isPlaying = false
|
||||
this.progress = 0
|
||||
},
|
||||
_onTimeUpdate () {
|
||||
this.playedTime = convertTimeMMSS(this.player.currentTime)
|
||||
this.progress = (this.player.currentTime / this.player.duration) * 100
|
||||
},
|
||||
_onUpdateProgress (pos) {
|
||||
if (pos) {
|
||||
this.player.currentTime = pos * this.player.duration
|
||||
}
|
||||
},
|
||||
_onUpdateVolume (val) {
|
||||
if (val) {
|
||||
this.player.volume = val
|
||||
this.volume = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user