mirror of
https://github.com/kevin-DL/vue-audio-recorder.git
synced 2026-01-17 13:24:57 +00:00
Update version to 3.0.0
- use MP3 instead of WAV - new callbacks & properties - refactoring
This commit is contained in:
37
src/components/downloader.vue
Normal file
37
src/components/downloader.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<style lang="scss">
|
||||
@import '../scss/icons';
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<icon-button
|
||||
id="download"
|
||||
class="ar-icon ar-icon__xs ar-icon--no-border"
|
||||
name="download"
|
||||
@click.native="download"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './icon-button'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
record : { type: Object },
|
||||
filename : { type: String }
|
||||
},
|
||||
components: {
|
||||
IconButton
|
||||
},
|
||||
methods: {
|
||||
download () {
|
||||
if (!this.record.url) {
|
||||
return
|
||||
}
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = this.record.url
|
||||
link.download = `${this.filename}.mp3`
|
||||
link.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
methods: {
|
||||
onMouseDown (ev) {
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
const seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('change-linehead', seekPos)
|
||||
document.addEventListener('mousemove', this.onMouseMove)
|
||||
document.addEventListener('mouseup', this.onMouseUp)
|
||||
@@ -43,17 +43,17 @@
|
||||
onMouseUp (ev) {
|
||||
document.removeEventListener('mouseup', this.onMouseUp)
|
||||
document.removeEventListener('mousemove', this.onMouseMove)
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
const seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('change-linehead', seekPos)
|
||||
},
|
||||
onMouseMove (ev) {
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
const seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('change-linehead', seekPos)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
calculateSize () {
|
||||
let value = this.percentage < 1 ? this.percentage * 100 : this.percentage
|
||||
const value = this.percentage < 1 ? this.percentage * 100 : this.percentage
|
||||
return `${this.rowDirection ? 'width' : 'height'}: ${value}%`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
<style lang="scss">
|
||||
.ar-player {
|
||||
width: 380px;
|
||||
height: 120px;
|
||||
border: 1px solid #E8E8E8;
|
||||
border-radius: 24px;
|
||||
height: unset;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #FAFAFA;
|
||||
background-color: unset;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
||||
& > .ar-player-bar {
|
||||
border: 1px solid #E8E8E8;
|
||||
border-radius: 24px;
|
||||
margin: 0 0 0 5px;
|
||||
|
||||
& > .ar-player__progress {
|
||||
width: 125px;
|
||||
}
|
||||
}
|
||||
|
||||
&-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -26,33 +36,6 @@
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
height: unset;
|
||||
flex-direction: row;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background-color: unset;
|
||||
|
||||
& > .ar-player-actions {
|
||||
width: unset;
|
||||
|
||||
& > #download,
|
||||
& > #upload {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& > .ar-player-bar {
|
||||
border: 1px solid #E8E8E8;
|
||||
border-radius: 24px;
|
||||
margin: 0 0 0 5px;
|
||||
|
||||
& > .ar-player__progress {
|
||||
width: 125px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__progress {
|
||||
width: 160px;
|
||||
margin: 0 8px;
|
||||
@@ -81,27 +64,14 @@
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="ar-player" :class="{'ar-player--compact': compact}">
|
||||
|
||||
<div class="ar-player">
|
||||
<div class="ar-player-actions">
|
||||
<icon-button
|
||||
id="download"
|
||||
class="ar-icon ar-icon__sm"
|
||||
name="download"
|
||||
@click.native="decorator(download)"/>
|
||||
|
||||
<icon-button
|
||||
id="play"
|
||||
class="ar-icon ar-icon__lg ar-player__play"
|
||||
:name="playBtnIcon"
|
||||
:class="{'ar-player__play--active': isPlaying}"
|
||||
@click.native="decorator(playback)"/>
|
||||
|
||||
<uploader
|
||||
id="upload"
|
||||
class="ar-icon ar-icon__sm"
|
||||
:record="record"
|
||||
:options="uploaderOptions"/>
|
||||
@click.native="playback"/>
|
||||
</div>
|
||||
|
||||
<div class="ar-player-bar">
|
||||
@@ -122,16 +92,14 @@
|
||||
<script>
|
||||
import IconButton from './icon-button'
|
||||
import LineControl from './line-control'
|
||||
import Uploader from './uploader'
|
||||
import VolumeControl from './volume-control'
|
||||
import { convertTimeMMSS } from '@/lib/utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
src : { type: String },
|
||||
record : { type: Object },
|
||||
compact : { type: Boolean, default: true },
|
||||
uploaderOptions : { type: Object, default: () => new Object }
|
||||
src : { type: String },
|
||||
record : { type: Object },
|
||||
filename : { type: String }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -144,7 +112,6 @@
|
||||
components: {
|
||||
IconButton,
|
||||
LineControl,
|
||||
Uploader,
|
||||
VolumeControl
|
||||
},
|
||||
mounted: function() {
|
||||
@@ -167,7 +134,7 @@
|
||||
},
|
||||
computed: {
|
||||
audioSource () {
|
||||
let url = this.src || this.record.url
|
||||
const url = this.src || this.record.url
|
||||
if (url) {
|
||||
return url
|
||||
} else {
|
||||
@@ -183,6 +150,10 @@
|
||||
},
|
||||
methods: {
|
||||
playback () {
|
||||
if (!this.audioSource) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isPlaying) {
|
||||
this.player.pause()
|
||||
} else {
|
||||
@@ -191,18 +162,6 @@
|
||||
|
||||
this.isPlaying = !this.isPlaying
|
||||
},
|
||||
download () {
|
||||
let link = document.createElement('a')
|
||||
link.href = this.record.url
|
||||
link.download = 'record.wav'
|
||||
link.click()
|
||||
},
|
||||
decorator (func) {
|
||||
if (!this.audioSource) {
|
||||
return
|
||||
}
|
||||
func()
|
||||
},
|
||||
_resetProgress () {
|
||||
if (this.isPlaying) {
|
||||
this.player.pause()
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
&__records-limit {
|
||||
position: absolute;
|
||||
color: #AEAEAE;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
top: 78px;
|
||||
}
|
||||
}
|
||||
@@ -160,6 +160,22 @@
|
||||
top: 0;
|
||||
color: rgb(244, 120, 90);
|
||||
}
|
||||
|
||||
&__downloader,
|
||||
&__uploader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&__downloader {
|
||||
right: 115px;
|
||||
}
|
||||
|
||||
&__uploader {
|
||||
right: 85px;
|
||||
}
|
||||
}
|
||||
|
||||
@import '../scss/icons';
|
||||
@@ -168,7 +184,6 @@
|
||||
<template>
|
||||
<div class="ar">
|
||||
<div class="ar__overlay" v-if="isUploading"></div>
|
||||
|
||||
<div class="ar-spinner" v-if="isUploading">
|
||||
<div class="ar-spinner__dot"></div>
|
||||
<div class="ar-spinner__dot"></div>
|
||||
@@ -201,89 +216,91 @@
|
||||
:class="{'ar-records__record--selected': record.id === selected.id}"
|
||||
:key="record.id"
|
||||
v-for="(record, idx) in recordList"
|
||||
@click="selected = record">
|
||||
@click="choiceRecord(record)">
|
||||
<div
|
||||
class="ar__rm"
|
||||
v-if="record.id === selected.id"
|
||||
@click="removeRecord(idx)">×</div>
|
||||
<div class="ar__text">Record {{idx + 1}}</div>
|
||||
<div class="ar__text">{{record.duration}}</div>
|
||||
|
||||
<downloader
|
||||
v-if="record.id === selected.id && showDownloadButton"
|
||||
class="ar__downloader"
|
||||
:record="record"
|
||||
:filename="filename"/>
|
||||
|
||||
<uploader
|
||||
v-if="record.id === selected.id && showUploadButton"
|
||||
class="ar__uploader"
|
||||
:record="record"
|
||||
:filename="filename"
|
||||
:headers="headers"
|
||||
:upload-url="uploadUrl"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<audio-player :compact="compact" :record="selected" :uploader-options="uploaderOptions"/>
|
||||
|
||||
<div :class="uploadStatusClasses" v-if="uploadStatus">{{message}}</div>
|
||||
<audio-player :record="selected"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioPlayer from './player'
|
||||
import Downloader from './downloader'
|
||||
import IconButton from './icon-button'
|
||||
import Recorder from '@/lib/recorder'
|
||||
import Uploader from './uploader'
|
||||
import UploaderPropsMixin from '@/mixins/uploader-props'
|
||||
import { convertTimeMMSS } from '@/lib/utils'
|
||||
|
||||
export default {
|
||||
mixins: [UploaderPropsMixin],
|
||||
props: {
|
||||
attempts : { type: Number },
|
||||
compact : { type: Boolean, default: false },
|
||||
time : { type: Number },
|
||||
attempts : { type: Number },
|
||||
time : { type: Number },
|
||||
|
||||
attemptsLimit : { type: Function },
|
||||
micFailed : { type: Function },
|
||||
startRecord : { type: Function },
|
||||
stopRecord : { type: Function },
|
||||
showDownloadButton : { type: Boolean, default: true },
|
||||
showUploadButton : { type: Boolean, default: true },
|
||||
|
||||
micFailed : { type: Function },
|
||||
beforeRecording : { type: Function },
|
||||
pauseRecording : { type: Function },
|
||||
afterRecording : { type: Function },
|
||||
failedUpload : { type: Function },
|
||||
headers : { type: Object },
|
||||
startUpload : { type: Function },
|
||||
beforeUpload : { type: Function },
|
||||
successfulUpload : { type: Function },
|
||||
uploadUrl : { type: String },
|
||||
|
||||
successfulUploadMsg : { type: String, default: 'Upload successful' },
|
||||
failedUploadMsg : { type: String, default: 'Upload fail' }
|
||||
selectRecord : { type: Function }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isUploading : false,
|
||||
recorder : new Recorder({
|
||||
afterStop: () => {
|
||||
this.recordList = this.recorder.recordList()
|
||||
if (this.stopRecord) {
|
||||
this.stopRecord('stop record')
|
||||
}
|
||||
},
|
||||
attempts: this.attempts,
|
||||
time: this.time
|
||||
}),
|
||||
recordList : [],
|
||||
selected : {},
|
||||
uploadStatus : null,
|
||||
uploaderOptions : {}
|
||||
isUploading : false,
|
||||
recorder : this._initRecorder(),
|
||||
recordList : [],
|
||||
selected : {},
|
||||
uploadStatus : null,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AudioPlayer,
|
||||
IconButton
|
||||
Downloader,
|
||||
IconButton,
|
||||
Uploader
|
||||
},
|
||||
created () {
|
||||
this.uploaderOptions = {
|
||||
failedUpload : this.failedUpload,
|
||||
headers : this.headers,
|
||||
startUpload : this.startUpload,
|
||||
successfulUpload : this.successfulUpload,
|
||||
uploadUrl : this.uploadUrl
|
||||
}
|
||||
|
||||
mounted () {
|
||||
this.$eventBus.$on('start-upload', () => {
|
||||
this.isUploading = true
|
||||
this.beforeUpload && this.beforeUpload('before upload')
|
||||
})
|
||||
|
||||
this.$eventBus.$on('end-upload', (resp) => {
|
||||
this.$eventBus.$on('end-upload', (msg) => {
|
||||
this.isUploading = false
|
||||
this.uploadStatus = status
|
||||
setTimeout(() => {this.uploadStatus = null}, 1500)
|
||||
|
||||
if (msg.status === 'success') {
|
||||
this.successfulUpload && this.successfulUpload(msg.response)
|
||||
} else {
|
||||
this.failedUpload && this.failedUpload(msg.response)
|
||||
}
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
@@ -297,14 +314,8 @@
|
||||
|
||||
if (!this.isRecording || (this.isRecording && this.isPause)) {
|
||||
this.recorder.start()
|
||||
if (this.startRecord) {
|
||||
this.startRecord('start record')
|
||||
}
|
||||
} else {
|
||||
this.recorder.pause()
|
||||
if (this.startRecord) {
|
||||
this.startRecord('pause record')
|
||||
}
|
||||
}
|
||||
},
|
||||
stopRecorder () {
|
||||
@@ -313,16 +324,32 @@
|
||||
}
|
||||
|
||||
this.recorder.stop()
|
||||
this.recordList = this.recorder.recordList()
|
||||
},
|
||||
removeRecord (idx) {
|
||||
this.recordList.splice(idx, 1)
|
||||
this.$set(this.selected, 'url', null)
|
||||
this.$eventBus.$emit('remove-record')
|
||||
},
|
||||
choiceRecord (record) {
|
||||
if (this.selected === record) {
|
||||
return
|
||||
}
|
||||
this.selected = record
|
||||
this.selectRecord && this.selectRecord(record)
|
||||
},
|
||||
_initRecorder () {
|
||||
return new Recorder({
|
||||
beforeRecording : this.beforeRecording,
|
||||
afterRecording : this.afterRecording,
|
||||
pauseRecording : this.pauseRecording,
|
||||
micFailed : this.micFailed
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
attemptsLeft () {
|
||||
return this.attempts - this.recorder.records.length
|
||||
return this.attempts - this.recordList.length
|
||||
},
|
||||
iconButtonType () {
|
||||
return this.isRecording && this.isPause ? 'mic' : this.isRecording ? 'pause' : 'mic'
|
||||
@@ -333,20 +360,12 @@
|
||||
isRecording () {
|
||||
return this.recorder.isRecording
|
||||
},
|
||||
message () {
|
||||
return this.uploadStatus === 'success' ? this.successfulUploadMsg : this.failedUploadMsg
|
||||
},
|
||||
recordedTime () {
|
||||
if (this.time && this.recorder.duration >= this.time * 60) {
|
||||
this.stopRecorder()
|
||||
}
|
||||
return convertTimeMMSS(this.recorder.duration)
|
||||
},
|
||||
uploadStatusClasses () {
|
||||
let classes = ['ar__upload-status']
|
||||
classes.push(this.uploadStatus === 'success' ? 'ar__upload-status--success' : 'ar__upload-status--fail')
|
||||
return classes.join(' ')
|
||||
},
|
||||
volume () {
|
||||
return parseFloat(this.recorder.volume)
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<icon-button name="save" @click.native="upload"/>
|
||||
<icon-button name="save" class="ar-icon ar-icon__xs ar-icon--no-border" @click.native="upload"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './icon-button'
|
||||
import UploaderPropsMixin from '@/mixins/uploader-props'
|
||||
|
||||
export default {
|
||||
mixins: [UploaderPropsMixin],
|
||||
props: {
|
||||
options : { type: Object },
|
||||
record : { type: Object }
|
||||
record: { type: Object }
|
||||
},
|
||||
components: {
|
||||
IconButton
|
||||
@@ -25,28 +26,16 @@
|
||||
|
||||
this.$eventBus.$emit('start-upload')
|
||||
|
||||
if (this.options.startUpload) {
|
||||
this.options.startUpload()
|
||||
}
|
||||
const data = new FormData()
|
||||
data.append('audio', this.record.blob, `${this.filename}.mp3`)
|
||||
|
||||
let data = new FormData()
|
||||
data.append('audio', this.record.blob, 'my-record')
|
||||
|
||||
let headers = Object.assign(this.options.headers, {})
|
||||
const headers = Object.assign(this.headers, {})
|
||||
headers['Content-Type'] = `multipart/form-data; boundary=${data._boundary}`
|
||||
|
||||
this.$http.post(this.options.uploadUrl, data, { headers: headers }).then(resp => {
|
||||
this.$eventBus.$emit('end-upload', 'success')
|
||||
|
||||
if (this.options.successfulUpload) {
|
||||
this.options.successfulUpload(resp)
|
||||
}
|
||||
this.$http.post(this.uploadUrl, data, { headers: headers }).then(resp => {
|
||||
this.$eventBus.$emit('end-upload', { status: 'success', response: resp })
|
||||
}).catch(error => {
|
||||
this.$eventBus.$emit('end-upload', 'fail')
|
||||
|
||||
if (this.options.failedUpload) {
|
||||
this.options.failedUpload(error)
|
||||
}
|
||||
this.$eventBus.$emit('end-upload', { status: 'fail', response: error })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user