mirror of
https://github.com/kevin-DL/vue-audio-recorder.git
synced 2026-01-13 19:45:25 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e89d8a33a | ||
|
|
420cf2e194 | ||
|
|
52b7dfe958 | ||
|
|
5d8139d674 | ||
|
|
206216643a | ||
|
|
fc0c9c824a | ||
|
|
261d7a80ec | ||
|
|
860f7e6158 | ||
|
|
83ccce2374 | ||
|
|
cb7a410ae5 |
15
README.md
15
README.md
@@ -15,7 +15,7 @@
|
|||||||
- A lot of callbacks
|
- A lot of callbacks
|
||||||
- Individual an audio player
|
- Individual an audio player
|
||||||
|
|
||||||
### Tested in
|
### Tested in (desktop)
|
||||||
|
|
||||||
- Chrome
|
- Chrome
|
||||||
- Firefox
|
- Firefox
|
||||||
@@ -33,6 +33,7 @@ npm i vue-audio-recorder --save
|
|||||||
| --------------------- | -------- | --------------------------------------------------------------- |
|
| --------------------- | -------- | --------------------------------------------------------------- |
|
||||||
| attempts | Number | Number of recording attempts |
|
| attempts | Number | Number of recording attempts |
|
||||||
| compact | Boolean | Hide the download and upload buttons |
|
| compact | Boolean | Hide the download and upload buttons |
|
||||||
|
| headers | Object | HTTP headers |
|
||||||
| time | Number | Time limit for the record (minutes) |
|
| time | Number | Time limit for the record (minutes) |
|
||||||
| upload-url | String | URL for uploading |
|
| upload-url | String | URL for uploading |
|
||||||
| start-record | Function | Fires after click the record button |
|
| start-record | Function | Fires after click the record button |
|
||||||
@@ -52,17 +53,6 @@ npm i vue-audio-recorder --save
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The most common use case is to register the component globally
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {AudioRecorder, AudioPlayer} from 'vue-audio-recorder'
|
|
||||||
|
|
||||||
Vue.component(AudioPlayer)
|
|
||||||
Vue.component(AudioRecorder)
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively you can do this to register the components
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import AudioRecorder from 'vue-audio-recorder'
|
import AudioRecorder from 'vue-audio-recorder'
|
||||||
|
|
||||||
@@ -73,6 +63,7 @@ Alternatively you can do this to register the components
|
|||||||
<audio-recorder
|
<audio-recorder
|
||||||
upload-url="YOUR_API_URL"
|
upload-url="YOUR_API_URL"
|
||||||
:attempts="3"
|
:attempts="3"
|
||||||
|
:headers="headers"
|
||||||
:time="2"
|
:time="2"
|
||||||
:start-record="callback"
|
:start-record="callback"
|
||||||
:stop-record="callback"
|
:stop-record="callback"
|
||||||
|
|||||||
13
demo/app.vue
13
demo/app.vue
@@ -13,6 +13,7 @@
|
|||||||
upload-url="some url"
|
upload-url="some url"
|
||||||
:attempts="3"
|
:attempts="3"
|
||||||
:time="2"
|
:time="2"
|
||||||
|
:headers="headers"
|
||||||
:start-record="callback"
|
:start-record="callback"
|
||||||
:stop-record="callback"
|
:stop-record="callback"
|
||||||
:start-upload="callback"
|
:start-upload="callback"
|
||||||
@@ -24,19 +25,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AudioPlayer from '../src/components/player'
|
|
||||||
import AudioRecorder from '../src/components/recorder'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
|
||||||
AudioPlayer,
|
|
||||||
AudioRecorder
|
|
||||||
},
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mp3: '/demo/example.mp3',
|
mp3: '/demo/example.mp3',
|
||||||
showRecorder: true
|
showRecorder: true,
|
||||||
|
headers: {
|
||||||
|
'X-Custom-Header': 'some data'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ import Vue from 'vue'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import app from './app'
|
import app from './app'
|
||||||
|
|
||||||
|
import AudioRecorder from '@/index'
|
||||||
|
|
||||||
Vue.prototype.$http = axios
|
Vue.prototype.$http = axios
|
||||||
|
|
||||||
|
Vue.use(AudioRecorder)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
render: h => h(app)
|
render: h => h(app)
|
||||||
|
|||||||
3
dist/vue-audio-recorder.min.js
vendored
3
dist/vue-audio-recorder.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/vue-audio-recorder.min.js.map
vendored
2
dist/vue-audio-recorder.min.js.map
vendored
File diff suppressed because one or more lines are too long
14
package.json
14
package.json
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-audio-recorder",
|
"name": "vue-audio-recorder",
|
||||||
"description": "Audio recorder for Vue.js. It allows to create, play, download and store records on a server",
|
"description": "Audio recorder for Vue.js. It allows to create, play, download and store records on a server",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"author": "Gennady Grishkovtsov <grishkovelli@gmail.com>",
|
"author": "Gennady Grishkovtsov <grishkovelli@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot --https",
|
"dev": "webpack-dev-server --env.NODE_ENV=development --mode development --open --hot --https",
|
||||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
|
"build": "webpack --env.NODE_ENV=production --mode production --progress --hide-modules"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
@@ -26,11 +26,13 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"node-sass": "^4.5.3",
|
"node-sass": "^4.5.3",
|
||||||
"sass-loader": "^6.0.6",
|
"sass-loader": "^6.0.6",
|
||||||
|
"uglifyjs-webpack-plugin": "^2.0.1",
|
||||||
"vue": "^2.5.16",
|
"vue": "^2.5.16",
|
||||||
"vue-loader": "^13.0.5",
|
"vue-loader": "^14.2.2",
|
||||||
"vue-template-compiler": "^2.4.4",
|
"vue-template-compiler": "^2.4.4",
|
||||||
"webpack": "^3.6.0",
|
"webpack": "^4.17.1",
|
||||||
"webpack-dev-server": "^2.9.1",
|
"webpack-cli": "^3.1.2",
|
||||||
|
"webpack-dev-server": "^3.1.9",
|
||||||
"webpack-merge": "^4.1.3"
|
"webpack-merge": "^4.1.3"
|
||||||
},
|
},
|
||||||
"main": "dist/vue-audio-recorder.min.js",
|
"main": "dist/vue-audio-recorder.min.js",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { calculateLineHeadPosition } from '@/lib/utils.js'
|
import { calculateLineHeadPosition } from '@/lib/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -88,20 +88,20 @@
|
|||||||
id="download"
|
id="download"
|
||||||
class="ar-icon ar-icon__sm"
|
class="ar-icon ar-icon__sm"
|
||||||
name="download"
|
name="download"
|
||||||
@click.native="download"/>
|
@click.native="decorator(download)"/>
|
||||||
|
|
||||||
<icon-button
|
<icon-button
|
||||||
id="play"
|
id="play"
|
||||||
class="ar-icon ar-icon__lg ar-player__play"
|
class="ar-icon ar-icon__lg ar-player__play"
|
||||||
:name="playBtnIcon"
|
:name="playBtnIcon"
|
||||||
:class="{'ar-player__play--active': isPlaying}"
|
:class="{'ar-player__play--active': isPlaying}"
|
||||||
@click.native="playback"/>
|
@click.native="decorator(playback)"/>
|
||||||
|
|
||||||
<icon-button
|
<uploader
|
||||||
id="upload"
|
id="upload"
|
||||||
class="ar-icon ar-icon__sm"
|
class="ar-icon ar-icon__sm"
|
||||||
name="save"
|
:record="record"
|
||||||
@click.native="upload"/>
|
:options="uploaderOptions"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ar-player-bar">
|
<div class="ar-player-bar">
|
||||||
@@ -120,32 +120,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import IconButton from './icon-button'
|
import IconButton from './icon-button'
|
||||||
import LineControl from './line-control'
|
import LineControl from './line-control'
|
||||||
|
import Uploader from './uploader'
|
||||||
import VolumeControl from './volume-control'
|
import VolumeControl from './volume-control'
|
||||||
import { convertTimeMMSS } from '@/lib/utils.js'
|
import { convertTimeMMSS } from '@/lib/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
src : { type: String },
|
src : { type: String },
|
||||||
uploadUrl : { type: String },
|
record : { type: Object },
|
||||||
record : { type: Object },
|
compact : { type: Boolean, default: true },
|
||||||
compact : { type: Boolean, default: true },
|
uploaderOptions : { type: Object, default: () => new Object }
|
||||||
startUpload : { type: Function },
|
|
||||||
successfulUpload : { type: Function },
|
|
||||||
failedUpload : { type: Function }
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isPlaying: false,
|
isPlaying : false,
|
||||||
duration: convertTimeMMSS(0),
|
duration : convertTimeMMSS(0),
|
||||||
playedTime: convertTimeMMSS(0),
|
playedTime : convertTimeMMSS(0),
|
||||||
progress: 0
|
progress : 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
IconButton,
|
IconButton,
|
||||||
LineControl,
|
LineControl,
|
||||||
|
Uploader,
|
||||||
VolumeControl
|
VolumeControl
|
||||||
},
|
},
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
@@ -161,24 +160,29 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.player.addEventListener('timeupdate', this._onTimeUpdate)
|
this.player.addEventListener('timeupdate', this._onTimeUpdate)
|
||||||
|
|
||||||
|
this.$eventBus.$on('remove-record', () => {
|
||||||
|
this._resetProgress()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
audioSource () {
|
||||||
|
let url = this.src || this.record.url
|
||||||
|
if (url) {
|
||||||
|
return url
|
||||||
|
} else {
|
||||||
|
this._resetProgress()
|
||||||
|
}
|
||||||
|
},
|
||||||
playBtnIcon () {
|
playBtnIcon () {
|
||||||
return this.isPlaying ? 'pause' : 'play'
|
return this.isPlaying ? 'pause' : 'play'
|
||||||
},
|
},
|
||||||
audioSource () {
|
|
||||||
return this.src || this.record.url
|
|
||||||
},
|
|
||||||
playerUniqId () {
|
playerUniqId () {
|
||||||
return `audio-player${this._uid}`
|
return `audio-player${this._uid}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
playback () {
|
playback () {
|
||||||
if (!this.audioSource) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isPlaying) {
|
if (this.isPlaying) {
|
||||||
this.player.pause()
|
this.player.pause()
|
||||||
} else {
|
} else {
|
||||||
@@ -187,47 +191,27 @@
|
|||||||
|
|
||||||
this.isPlaying = !this.isPlaying
|
this.isPlaying = !this.isPlaying
|
||||||
},
|
},
|
||||||
upload () {
|
|
||||||
if (!this.audioSource) {
|
|
||||||
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 () {
|
download () {
|
||||||
if (!this.audioSource) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let link = document.createElement('a')
|
let link = document.createElement('a')
|
||||||
link.href = this.record.url
|
link.href = this.record.url
|
||||||
link.download = 'record.wav'
|
link.download = 'record.wav'
|
||||||
link.click()
|
link.click()
|
||||||
},
|
},
|
||||||
|
decorator (func) {
|
||||||
|
if (!this.audioSource) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func()
|
||||||
|
},
|
||||||
_resetProgress () {
|
_resetProgress () {
|
||||||
this.isPlaying = false
|
if (this.isPlaying) {
|
||||||
this.progress = 0
|
this.player.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.duration = convertTimeMMSS(0)
|
||||||
|
this.playedTime = convertTimeMMSS(0)
|
||||||
|
this.progress = 0
|
||||||
|
this.isPlaying = false
|
||||||
},
|
},
|
||||||
_onTimeUpdate () {
|
_onTimeUpdate () {
|
||||||
this.playedTime = convertTimeMMSS(this.player.currentTime)
|
this.playedTime = convertTimeMMSS(this.player.currentTime)
|
||||||
|
|||||||
@@ -23,12 +23,14 @@
|
|||||||
|
|
||||||
&__record {
|
&__record {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
|
height: 45px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
line-height: 45px;
|
line-height: 45px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: 1px solid #E8E8E8;
|
border-bottom: 1px solid #E8E8E8;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
border: 1px solid #E8E8E8;
|
border: 1px solid #E8E8E8;
|
||||||
@@ -105,7 +107,7 @@
|
|||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
0% { opacity: .2; }
|
0% { opacity: .2; }
|
||||||
20% { opacity: 1; }
|
20% { opacity: 1; }
|
||||||
100% { opacity: .2; }
|
100% { opacity: .2; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,6 +146,20 @@
|
|||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__rm {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
line-height: 6px;
|
||||||
|
margin: auto;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
color: rgb(244, 120, 90);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@import '../scss/icons';
|
@import '../scss/icons';
|
||||||
@@ -182,23 +198,20 @@
|
|||||||
<div class="ar-records">
|
<div class="ar-records">
|
||||||
<div
|
<div
|
||||||
class="ar-records__record"
|
class="ar-records__record"
|
||||||
:class="{'ar-records__record--selected': idx === selectedRecord.idx}"
|
:class="{'ar-records__record--selected': record.id === selected.id}"
|
||||||
|
:key="record.id"
|
||||||
v-for="(record, idx) in recordList"
|
v-for="(record, idx) in recordList"
|
||||||
@click="selectRecord(idx, record)">
|
@click="selected = 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 {{idx + 1}}</div>
|
||||||
<div class="ar__text">{{record.duration}}</div>
|
<div class="ar__text">{{record.duration}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<audio-player
|
<audio-player :compact="compact" :record="selected" :uploader-options="uploaderOptions"/>
|
||||||
: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 :class="uploadStatusClasses" v-if="uploadStatus">{{message}}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,52 +219,76 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AudioPlayer from './player.vue'
|
import AudioPlayer from './player'
|
||||||
import IconButton from './icon-button.vue'
|
import IconButton from './icon-button'
|
||||||
import Recorder from '@/lib/recorder.js'
|
import Recorder from '@/lib/recorder'
|
||||||
import { convertTimeMMSS } from '@/lib/utils.js'
|
import { convertTimeMMSS } from '@/lib/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
attempts : { type: Number },
|
attempts : { type: Number },
|
||||||
compact : { type: Boolean, default: false },
|
compact : { type: Boolean, default: false },
|
||||||
time : { type: Number },
|
time : { type: Number },
|
||||||
uploadUrl : { type: String },
|
|
||||||
|
attemptsLimit : { type: Function },
|
||||||
|
micFailed : { type: Function },
|
||||||
|
startRecord : { type: Function },
|
||||||
|
stopRecord : { type: Function },
|
||||||
|
|
||||||
attemptsLimit : { type: Function },
|
|
||||||
failedUpload : { type: Function },
|
failedUpload : { type: Function },
|
||||||
micFailed : { type: Function },
|
headers : { type: Object },
|
||||||
startRecord : { type: Function },
|
|
||||||
startUpload : { type: Function },
|
startUpload : { type: Function },
|
||||||
stopRecord : { type: Function },
|
|
||||||
successfulUpload : { type: Function },
|
successfulUpload : { type: Function },
|
||||||
|
uploadUrl : { type: String },
|
||||||
|
|
||||||
successfulUploadMsg : { type: String, default: 'Upload successful' },
|
successfulUploadMsg : { type: String, default: 'Upload successful' },
|
||||||
failedUploadMsg : { type: String, default: 'Upload fail' }
|
failedUploadMsg : { type: String, default: 'Upload fail' }
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isUploading: false,
|
isUploading : false,
|
||||||
recorder: new Recorder({
|
recorder : new Recorder({
|
||||||
afterStop: () => {
|
afterStop: () => {
|
||||||
this.recordList = this.recorder.recordList()
|
this.recordList = this.recorder.recordList()
|
||||||
|
if (this.stopRecord) {
|
||||||
if (this.stopRecord) {
|
this.stopRecord('stop record')
|
||||||
this.stopRecord('stop record')
|
}
|
||||||
}
|
},
|
||||||
},
|
attempts: this.attempts,
|
||||||
attempts: this.attempts,
|
time: this.time
|
||||||
time: this.time
|
}),
|
||||||
}),
|
recordList : [],
|
||||||
recordList: [],
|
selected : {},
|
||||||
selectedRecord: {},
|
uploadStatus : null,
|
||||||
uploadStatus: null
|
uploaderOptions : {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AudioPlayer,
|
AudioPlayer,
|
||||||
IconButton
|
IconButton
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
this.uploaderOptions = {
|
||||||
|
failedUpload : this.failedUpload,
|
||||||
|
headers : this.headers,
|
||||||
|
startUpload : this.startUpload,
|
||||||
|
successfulUpload : this.successfulUpload,
|
||||||
|
uploadUrl : this.uploadUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$eventBus.$on('start-upload', () => {
|
||||||
|
this.isUploading = true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$eventBus.$on('end-upload', (resp) => {
|
||||||
|
this.isUploading = false
|
||||||
|
this.uploadStatus = status
|
||||||
|
setTimeout(() => {this.uploadStatus = null}, 1500)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.stopRecorder()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleRecorder () {
|
toggleRecorder () {
|
||||||
if (this.attempts && this.recorder.records.length >= this.attempts) {
|
if (this.attempts && this.recorder.records.length >= this.attempts) {
|
||||||
@@ -277,16 +314,10 @@
|
|||||||
|
|
||||||
this.recorder.stop()
|
this.recorder.stop()
|
||||||
},
|
},
|
||||||
selectRecord (idx, record) {
|
removeRecord (idx) {
|
||||||
this.selectedRecord = { idx: idx, url: record.url, blob: record.blob }
|
this.recordList.splice(idx, 1)
|
||||||
},
|
this.$set(this.selected, 'url', null)
|
||||||
onStartUpload () {
|
this.$eventBus.$emit('remove-record')
|
||||||
this.isUploading = true
|
|
||||||
},
|
|
||||||
onEndUpload (status) {
|
|
||||||
this.isUploading = false
|
|
||||||
this.uploadStatus = status
|
|
||||||
setTimeout(() => {this.uploadStatus = null}, 1500)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
54
src/components/uploader.vue
Normal file
54
src/components/uploader.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<style lang="scss">
|
||||||
|
@import '../scss/icons';
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<icon-button name="save" @click.native="upload"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import IconButton from './icon-button'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
options : { type: Object },
|
||||||
|
record : { type: Object }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
IconButton
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
upload () {
|
||||||
|
if (!this.record.url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$eventBus.$emit('start-upload')
|
||||||
|
|
||||||
|
if (this.options.startUpload) {
|
||||||
|
this.options.startUpload()
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = new FormData()
|
||||||
|
data.append('audio', this.record.blob, 'my-record')
|
||||||
|
|
||||||
|
let headers = Object.assign(this.options.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)
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
this.$eventBus.$emit('end-upload', 'fail')
|
||||||
|
|
||||||
|
if (this.options.failedUpload) {
|
||||||
|
this.options.failedUpload(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import IconButton from './icon-button.vue'
|
import IconButton from './icon-button'
|
||||||
import LineControl from './line-control.vue'
|
import LineControl from './line-control'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import AudioPlayer from './components/player.vue'
|
import AudioPlayer from '@/components/player.vue'
|
||||||
import AudioRecorder from './components/recorder.vue'
|
import AudioRecorder from '@/components/recorder.vue'
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
AudioPlayer,
|
AudioPlayer,
|
||||||
@@ -12,6 +12,8 @@ const components = {
|
|||||||
|
|
||||||
this.installed = true
|
this.installed = true
|
||||||
|
|
||||||
|
Vue.prototype.$eventBus = Vue.prototype.$eventBus || new Vue
|
||||||
|
|
||||||
Vue.component('audio-player', AudioPlayer)
|
Vue.component('audio-player', AudioPlayer)
|
||||||
Vue.component('audio-recorder', AudioRecorder)
|
Vue.component('audio-recorder', AudioRecorder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default class {
|
|||||||
navigator.mediaDevices.getUserMedia({audio: true})
|
navigator.mediaDevices.getUserMedia({audio: true})
|
||||||
.then(this._micCaptured.bind(this))
|
.then(this._micCaptured.bind(this))
|
||||||
.catch(this._micError.bind(this))
|
.catch(this._micError.bind(this))
|
||||||
this.isPause = false
|
this.isPause = false
|
||||||
this.isRecording = true
|
this.isRecording = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,26 +34,28 @@ export default class {
|
|||||||
this.context.close()
|
this.context.close()
|
||||||
|
|
||||||
let encoder = new WavEncoder({
|
let encoder = new WavEncoder({
|
||||||
bufferSize: this.bufferSize,
|
bufferSize : this.bufferSize,
|
||||||
sampleRate: this.context.sampleRate,
|
sampleRate : this.context.sampleRate,
|
||||||
samples: this.samples
|
samples : this.samples
|
||||||
})
|
})
|
||||||
|
|
||||||
let audioBlob = encoder.getData()
|
let audioBlob = encoder.getData()
|
||||||
let audioUrl = URL.createObjectURL(audioBlob)
|
let audioUrl = URL.createObjectURL(audioBlob)
|
||||||
|
|
||||||
this.samples = []
|
this.samples = []
|
||||||
|
|
||||||
this.records.push({
|
this.records.push({
|
||||||
blob: audioBlob,
|
id : Date.now(),
|
||||||
url: audioUrl,
|
blob : audioBlob,
|
||||||
duration: convertTimeMMSS(this.duration)
|
duration : convertTimeMMSS(this.duration),
|
||||||
|
url : audioUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isPause = false
|
|
||||||
this.isRecording = false
|
|
||||||
this._duration = 0
|
this._duration = 0
|
||||||
this.duration = 0
|
this.duration = 0
|
||||||
|
|
||||||
|
this.isPause = false
|
||||||
|
this.isRecording = false
|
||||||
|
|
||||||
if (this.afterStop) {
|
if (this.afterStop) {
|
||||||
this.afterStop()
|
this.afterStop()
|
||||||
@@ -79,11 +81,11 @@ export default class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_micCaptured (stream) {
|
_micCaptured (stream) {
|
||||||
this.context = new(window.AudioContext || window.webkitAudioContext)()
|
this.context = new(window.AudioContext || window.webkitAudioContext)()
|
||||||
this.input = this.context.createMediaStreamSource(stream)
|
this.duration = this._duration
|
||||||
this.processor = this.context.createScriptProcessor(this.bufferSize, 1, 1)
|
this.input = this.context.createMediaStreamSource(stream)
|
||||||
this.duration = this._duration
|
this.processor = this.context.createScriptProcessor(this.bufferSize, 1, 1)
|
||||||
this.stream = stream
|
this.stream = stream
|
||||||
|
|
||||||
this.processor.onaudioprocess = (ev) => {
|
this.processor.onaudioprocess = (ev) => {
|
||||||
let sample = ev.inputBuffer.getChannelData(0)
|
let sample = ev.inputBuffer.getChannelData(0)
|
||||||
|
|||||||
@@ -1,67 +1,70 @@
|
|||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const merge = require('webpack-merge')
|
const merge = require('webpack-merge')
|
||||||
const env = `./webpack.${process.env.NODE_ENV === 'production' ? 'prod' : 'dev'}.js`
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
module.exports = merge(require(env), {
|
module.exports = (env, args) => {
|
||||||
module: {
|
let conf = `./webpack.${env.NODE_ENV === 'production' ? 'prod' : 'dev'}.js`
|
||||||
rules: [
|
|
||||||
{
|
return merge(require(conf), {
|
||||||
test: /\.scss$/,
|
module: {
|
||||||
use: [
|
rules: [
|
||||||
'vue-style-loader',
|
{
|
||||||
'css-loader',
|
test: /\.scss$/,
|
||||||
'sass-loader'
|
use: [
|
||||||
],
|
'vue-style-loader',
|
||||||
},
|
'css-loader',
|
||||||
{
|
'sass-loader'
|
||||||
test: /\.vue$/,
|
],
|
||||||
loader: 'vue-loader',
|
},
|
||||||
options: {
|
{
|
||||||
loaders: {
|
test: /\.vue$/,
|
||||||
'scss': [
|
loader: 'vue-loader',
|
||||||
'vue-style-loader',
|
options: {
|
||||||
'css-loader',
|
loaders: {
|
||||||
'sass-loader'
|
'scss': [
|
||||||
]
|
'vue-style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'sass-loader'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpg|gif|svg)$/,
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]?[hash]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
exclude: /node_modules/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpg|gif|svg)$/,
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: '[name].[ext]?[hash]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'vue$': 'vue/dist/vue.esm.js',
|
|
||||||
'@': path.resolve(__dirname, 'src')
|
|
||||||
},
|
},
|
||||||
extensions: ['*', '.js', '.vue', '.json']
|
resolve: {
|
||||||
},
|
alias: {
|
||||||
plugins: [
|
'vue$': 'vue/dist/vue.esm.js',
|
||||||
new webpack.DefinePlugin({
|
'@': path.resolve(__dirname, 'src')
|
||||||
'process.env': {
|
|
||||||
NODE_ENV: `"${process.env.NODE_ENV}"`
|
|
||||||
},
|
},
|
||||||
VERSION: JSON.stringify(require("./package.json").version)
|
extensions: ['*', '.js', '.vue', '.json']
|
||||||
}),
|
},
|
||||||
],
|
plugins: [
|
||||||
devServer: {
|
new webpack.DefinePlugin({
|
||||||
historyApiFallback: true,
|
'process.env': {
|
||||||
noInfo: true,
|
NODE_ENV: `"${process.env.NODE_ENV}"`
|
||||||
overlay: true
|
},
|
||||||
},
|
VERSION: JSON.stringify(require("./package.json").version)
|
||||||
performance: {
|
}),
|
||||||
hints: false
|
],
|
||||||
}
|
devServer: {
|
||||||
})
|
historyApiFallback: true,
|
||||||
|
noInfo: true,
|
||||||
|
overlay: true
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
hints: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ const path = require('path')
|
|||||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devtool: '#eval-source-map',
|
|
||||||
entry: './demo/index.js',
|
entry: './demo/index.js',
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, './demo')
|
path: path.resolve(__dirname, './demo')
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devtool: '#source-map',
|
entry: {
|
||||||
entry: './src/index.js',
|
main: './src/index.js'
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
filename: 'vue-audio-recorder.min.js',
|
filename: 'vue-audio-recorder.min.js',
|
||||||
@@ -12,18 +14,21 @@ module.exports = {
|
|||||||
libraryExport: 'default',
|
libraryExport: 'default',
|
||||||
umdNamedDefine: true
|
umdNamedDefine: true
|
||||||
},
|
},
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new UglifyJsPlugin({
|
||||||
|
cache: true,
|
||||||
|
parallel: true,
|
||||||
|
sourceMap: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
NODE_ENV: '"production"'
|
NODE_ENV: '"production"'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
|
||||||
sourceMap: true,
|
|
||||||
compress: {
|
|
||||||
warnings: true
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
minimize: false
|
minimize: false
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user