mirror of
https://github.com/kevin-DL/vue-audio-recorder.git
synced 2026-01-17 05:14:52 +00:00
Refactor components
This commit is contained in:
@@ -1,65 +1,11 @@
|
||||
<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>
|
||||
<div v-html="icons[name]"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: { type: String },
|
||||
size: { type: String, default: 'sm' }
|
||||
name: { type: String }
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@@ -73,11 +19,6 @@
|
||||
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>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<style lang="scss">
|
||||
.ar-line-control {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
border-radius: 5px;
|
||||
background-color: #E6E6E6;
|
||||
|
||||
&__head {
|
||||
position: absolute;
|
||||
@@ -12,24 +15,28 @@
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div :ref="refId" class="ar-line-control" @mousedown="onMouseDown">
|
||||
<div class="ar-line-control__head" :style="{width: percentageWidth}"></div>
|
||||
<div
|
||||
:ref="refId"
|
||||
class="ar-line-control"
|
||||
@mousedown="onMouseDown">
|
||||
<div class="ar-line-control__head" :style="calculateSize"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { calculateLineHeadPosition } from '../lib/utils.js'
|
||||
import { calculateLineHeadPosition } from '@/lib/utils.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
refId: { type: String },
|
||||
eventName: { type: String },
|
||||
percentage: { type: Number, default: 0 }
|
||||
refId : { type: String },
|
||||
eventName : { type: String },
|
||||
percentage : { type: Number, default: 0 },
|
||||
rowDirection : { type: Boolean, default: true}
|
||||
},
|
||||
methods: {
|
||||
onMouseDown (ev) {
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('changeLineHead', seekPos)
|
||||
this.$emit('change-linehead', seekPos)
|
||||
document.addEventListener('mousemove', this.onMouseMove)
|
||||
document.addEventListener('mouseup', this.onMouseUp)
|
||||
},
|
||||
@@ -37,17 +44,17 @@
|
||||
document.removeEventListener('mouseup', this.onMouseUp)
|
||||
document.removeEventListener('mousemove', this.onMouseMove)
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('changeLineHead', seekPos)
|
||||
this.$emit('change-linehead', seekPos)
|
||||
},
|
||||
onMouseMove (ev) {
|
||||
let seekPos = calculateLineHeadPosition(ev, this.$refs[this.refId])
|
||||
this.$emit('changeLineHead', seekPos)
|
||||
this.$emit('change-linehead', seekPos)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
percentageWidth () {
|
||||
let width = this.percentage < 1 ? this.percentage * 100 : this.percentage
|
||||
return `${width}%`
|
||||
calculateSize () {
|
||||
let value = this.percentage < 1 ? this.percentage * 100 : this.percentage
|
||||
return `${this.rowDirection ? 'width' : 'height'}: ${value}%`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
<style lang="scss">
|
||||
.ar-player {
|
||||
width: 100%;
|
||||
width: 380px;
|
||||
height: 120px;
|
||||
border: 1px solid #E8E8E8;
|
||||
border-radius: 24px;
|
||||
background-color: #FAFAFA;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #FAFAFA;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
||||
&-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
&-actions {
|
||||
width: 55%;
|
||||
@@ -17,46 +26,35 @@
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&-volume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 10px;
|
||||
&--compact {
|
||||
height: unset;
|
||||
flex-direction: row;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background-color: unset;
|
||||
|
||||
&-bar {
|
||||
width: 50px;
|
||||
height: 8px;
|
||||
background: #E6E6E6;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
& > .ar-player-actions {
|
||||
width: unset;
|
||||
|
||||
& > #download,
|
||||
& > #upload {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
fill: #747474;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
background-color: unset;
|
||||
margin-right: 3px;
|
||||
& > .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;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
width: 160px;
|
||||
height: 8px;
|
||||
border-radius: 5px;
|
||||
background-color: #E6E6E6;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
@@ -65,75 +63,93 @@
|
||||
font-size: 16px;
|
||||
width: 41px;
|
||||
}
|
||||
|
||||
&__play {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 2px 11px 11px rgba(0,0,0,0.07);
|
||||
|
||||
&--active {
|
||||
fill: white !important;
|
||||
background-color: #05CBCD !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import '../scss/icons';
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="ar-player" :class="{'ar-player--active': record.url}">
|
||||
<div class="ar-player" :class="{'ar-player--compact': compact}">
|
||||
|
||||
<div class="ar-player-actions">
|
||||
<icon-button
|
||||
id="download"
|
||||
class="ar-icon ar-icon__sm"
|
||||
name="download"
|
||||
@click.native="download"/>
|
||||
|
||||
<icon-button
|
||||
id="play"
|
||||
class="ar-icon ar-icon__lg ar-player__play"
|
||||
:name="playBtnIcon"
|
||||
:class="{'ar-player__play--active': isPlaying}"
|
||||
@click.native="playback"/>
|
||||
|
||||
<icon-button
|
||||
id="upload"
|
||||
class="ar-icon ar-icon__sm"
|
||||
name="save"
|
||||
@click.native="upload"/>
|
||||
</div>
|
||||
|
||||
<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"/>
|
||||
|
||||
@change-linehead="_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>
|
||||
<volume-control @change-volume="_onChangeVolume"/>
|
||||
</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>
|
||||
<audio :id="playerUniqId" :src="audioSource"></audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './icon-button.vue'
|
||||
import LineControl from './line-control.vue'
|
||||
import { convertTimeMMSS } from '../lib/utils.js'
|
||||
import IconButton from './icon-button'
|
||||
import LineControl from './line-control'
|
||||
import VolumeControl from './volume-control'
|
||||
import { convertTimeMMSS } from '@/lib/utils.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
uploadUrl: { type: String },
|
||||
record: { type: Object },
|
||||
startUpload: { type: Function },
|
||||
successfulUpload: { type: Function },
|
||||
failedUpload: { type: Function }
|
||||
src : { type: String },
|
||||
uploadUrl : { type: String },
|
||||
record : { type: Object },
|
||||
compact : { type: Boolean, default: true },
|
||||
startUpload : { type: Function },
|
||||
successfulUpload : { type: Function },
|
||||
failedUpload : { type: Function }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isPlaying: false,
|
||||
duration: convertTimeMMSS(0),
|
||||
playedTime: convertTimeMMSS(0),
|
||||
progress: 0,
|
||||
volume: 0.8
|
||||
progress: 0
|
||||
}
|
||||
},
|
||||
components: {
|
||||
IconButton,
|
||||
LineControl
|
||||
LineControl,
|
||||
VolumeControl
|
||||
},
|
||||
mounted: function() {
|
||||
this.player = document.getElementById('audio-recorder-player')
|
||||
this.player = document.getElementById(this.playerUniqId)
|
||||
|
||||
this.player.addEventListener('ended', () => {
|
||||
this.isPlaying = false
|
||||
@@ -149,11 +165,17 @@
|
||||
computed: {
|
||||
playBtnIcon () {
|
||||
return this.isPlaying ? 'pause' : 'play'
|
||||
},
|
||||
audioSource () {
|
||||
return this.src || this.record.url
|
||||
},
|
||||
playerUniqId () {
|
||||
return `audio-player${this._uid}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
playback () {
|
||||
if (!this.record.url) {
|
||||
if (!this.audioSource) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -166,7 +188,7 @@
|
||||
this.isPlaying = !this.isPlaying
|
||||
},
|
||||
upload () {
|
||||
if (!this.record.url) {
|
||||
if (!this.audioSource) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -194,7 +216,7 @@
|
||||
})
|
||||
},
|
||||
download () {
|
||||
if (!this.record.url) {
|
||||
if (!this.audioSource) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -216,10 +238,9 @@
|
||||
this.player.currentTime = pos * this.player.duration
|
||||
}
|
||||
},
|
||||
_onUpdateVolume (val) {
|
||||
_onChangeVolume (val) {
|
||||
if (val) {
|
||||
this.player.volume = val
|
||||
this.volume = val
|
||||
}
|
||||
}
|
||||
}
|
||||
325
src/components/recorder.vue
Normal file
325
src/components/recorder.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<style lang="scss">
|
||||
.ar {
|
||||
width: 420px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
border-radius: 16px;
|
||||
background-color: #FAFAFA;
|
||||
box-shadow: 0 4px 18px 0 rgba(0,0,0,0.17);
|
||||
position: relative;
|
||||
box-sizing: content-box;
|
||||
|
||||
&-content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-records {
|
||||
height: 138px;
|
||||
padding-top: 1px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__record {
|
||||
width: 320px;
|
||||
padding: 0 10px;
|
||||
margin: 0 auto;
|
||||
line-height: 45px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #E8E8E8;
|
||||
|
||||
&--selected {
|
||||
border: 1px solid #E8E8E8;
|
||||
border-radius: 24px;
|
||||
background-color: #FFFFFF;
|
||||
margin-top: -1px;
|
||||
padding: 0 34px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-recorder {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__duration {
|
||||
color: #AEAEAE;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__stop {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -52px;
|
||||
}
|
||||
|
||||
&__time-limit {
|
||||
position: absolute;
|
||||
color: #AEAEAE;
|
||||
font-size: 12px;
|
||||
top: 128px;
|
||||
}
|
||||
|
||||
&__records-limit {
|
||||
position: absolute;
|
||||
color: #AEAEAE;
|
||||
font-size: 12px;
|
||||
top: 78px;
|
||||
}
|
||||
}
|
||||
|
||||
&-spinner {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
width: 144px;
|
||||
z-index: 10;
|
||||
|
||||
&__dot {
|
||||
display: block;
|
||||
margin: 0 8px;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #05CBCD;
|
||||
animation-name: blink;
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
|
||||
&:nth-child(2) { animation-delay: .2s; }
|
||||
|
||||
&:nth-child(3) { animation-delay: .4s; }
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: .2; }
|
||||
20% { opacity: 1; }
|
||||
100% { opacity: .2; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: rgba(84,84,84,0.5);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&__blur {
|
||||
filter: blur(2px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&__overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&__upload-status {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
padding: 2px;
|
||||
letter-spacing: 1px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
&--success {
|
||||
color: green;
|
||||
}
|
||||
|
||||
&--fail {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import '../scss/icons';
|
||||
</style>
|
||||
|
||||
<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>
|
||||
<div class="ar-spinner__dot"></div>
|
||||
</div>
|
||||
|
||||
<div class="ar-content" :class="{'ar__blur': isUploading}">
|
||||
<div class="ar-recorder">
|
||||
<icon-button
|
||||
class="ar-icon ar-icon__lg"
|
||||
:name="iconButtonType"
|
||||
:class="{
|
||||
'ar-icon--rec': isRecording,
|
||||
'ar-icon--pulse': isRecording && volume > 0.02
|
||||
}"
|
||||
@click.native="toggleRecorder"/>
|
||||
<icon-button
|
||||
class="ar-icon ar-icon__sm ar-recorder__stop"
|
||||
name="stop"
|
||||
@click.native="stopRecorder"/>
|
||||
</div>
|
||||
|
||||
<div class="ar-recorder__records-limit" v-if="attempts">Attempts: {{attemptsLeft}}/{{attempts}}</div>
|
||||
<div class="ar-recorder__duration">{{recordedTime}}</div>
|
||||
<div class="ar-recorder__time-limit" v-if="time">Record duration is limited: {{time}}m</div>
|
||||
|
||||
<div class="ar-records">
|
||||
<div
|
||||
class="ar-records__record"
|
||||
:class="{'ar-records__record--selected': idx === selectedRecord.idx}"
|
||||
v-for="(record, idx) in recordList"
|
||||
@click="selectRecord(idx, record)">
|
||||
<div class="ar__text">Record {{idx + 1}}</div>
|
||||
<div class="ar__text">{{record.duration}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<audio-player
|
||||
:compact="compact"
|
||||
:record="selectedRecord"
|
||||
:upload-url="uploadUrl"
|
||||
:start-upload="startUpload"
|
||||
:successful-upload="successfulUpload"
|
||||
:failed-upload="failedUpload"
|
||||
@start-upload="onStartUpload"
|
||||
@end-upload="onEndUpload"/>
|
||||
|
||||
<div :class="uploadStatusClasses" v-if="uploadStatus">{{message}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioPlayer from './player.vue'
|
||||
import IconButton from './icon-button.vue'
|
||||
import Recorder from '@/lib/recorder.js'
|
||||
import { convertTimeMMSS } from '@/lib/utils.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
attempts : { type: Number },
|
||||
compact : { type: Boolean, default: false },
|
||||
time : { type: Number },
|
||||
uploadUrl : { type: String },
|
||||
|
||||
attemptsLimit : { type: Function },
|
||||
failedUpload : { type: Function },
|
||||
micFailed : { type: Function },
|
||||
startRecord : { type: Function },
|
||||
startUpload : { type: Function },
|
||||
stopRecord : { type: Function },
|
||||
successfulUpload : { type: Function },
|
||||
|
||||
successfulUploadMsg : { type: String, default: 'Upload successful' },
|
||||
failedUploadMsg : { type: String, default: 'Upload fail' }
|
||||
},
|
||||
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: [],
|
||||
selectedRecord: {},
|
||||
uploadStatus: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AudioPlayer,
|
||||
IconButton
|
||||
},
|
||||
methods: {
|
||||
toggleRecorder () {
|
||||
if (this.attempts && this.recorder.records.length >= this.attempts) {
|
||||
return
|
||||
}
|
||||
|
||||
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 () {
|
||||
if (!this.isRecording) {
|
||||
return
|
||||
}
|
||||
|
||||
this.recorder.stop()
|
||||
},
|
||||
selectRecord (idx, record) {
|
||||
this.selectedRecord = { idx: idx, url: record.url, blob: record.blob }
|
||||
},
|
||||
onStartUpload () {
|
||||
this.isUploading = true
|
||||
},
|
||||
onEndUpload (status) {
|
||||
this.isUploading = false
|
||||
this.uploadStatus = status
|
||||
setTimeout(() => {this.uploadStatus = null}, 1500)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
attemptsLeft () {
|
||||
return this.attempts - this.recorder.records.length
|
||||
},
|
||||
iconButtonType () {
|
||||
return this.isRecording && this.isPause ? 'mic' : this.isRecording ? 'pause' : 'mic'
|
||||
},
|
||||
isPause () {
|
||||
return this.recorder.isPause
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
60
src/components/volume-control.vue
Normal file
60
src/components/volume-control.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<style lang="scss">
|
||||
.ar-volume {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 10px;
|
||||
|
||||
&-bar {
|
||||
width: 50px;
|
||||
height: 6px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="ar-volume">
|
||||
<icon-button class="ar-volume__icon" name="volume"/>
|
||||
<line-control
|
||||
class="ar-volume-bar"
|
||||
ref-id="volume"
|
||||
:percentage="volume"
|
||||
@change-linehead="onChangeLinehead"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './icon-button.vue'
|
||||
import LineControl from './line-control.vue'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
volume: 0.8
|
||||
}
|
||||
},
|
||||
components: {
|
||||
IconButton,
|
||||
LineControl
|
||||
},
|
||||
methods: {
|
||||
onChangeLinehead (val) {
|
||||
this.$emit('change-volume', val)
|
||||
this.volume = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user