vue 调用pc端本地摄像头、麦克风实现拍照、录视频、录音 并上传
自己写blog只是为了下次方便使用 过程确实很烦 ,自己摸索加各大网站cv查看
可以直接使用
首先是npm i vue-video-player -D 安装依赖
在main.js里面引入
import VideoPlayer from 'vue-video-player'
require('video.js/dist/video-js.css')
require('vue-video-player/src/custom-theme.css')
Vue.use(VideoPlayer)
此处代码主要是用来覆盖video的播放按钮样式
require(’./styles/video.css’);具体内容如下
.video-js .vjs-big-play-button {
width: 72px;
height: 72px;
border-radius: 100%;
z-index: 100;
background-color: #ffffff;
border: solid 1px #979797;
}
<template>
<div class="main ">
<div class="left">
<div class="vedio">
<div class="shexiang">
<!--图片展示-->
<video
ref="video"
width="350px"
height="265px"
autoplay
></video>
<div class="btnsBackground">
<el-popover
placement="top-start"
width="160"
trigger="click"
>
<div style="text-align: left; margin: 0">
<div style="letter-spacing:1px">
间隔 <input
v-model="everyTime"
type="text"
id="everyTime"
style="width:30px;color:#41CFB1"
>
秒拍一次;</div>
<div style="letter-spacing:1px">
最长拍照时间<input
v-model="allTime"
type="text"
id="allTime"
style="width:30px;color:#41CFB1;margin-top:10px"
> 秒</div>
<el-checkbox
v-model="checked"
style="margin-top:10px"
>连拍模式</el-checkbox>
</div>
<el-image
slot="reference"
class="photo1"
:src="require('@/assets/images/luxiang/setting.png')"
fit="cover"
></el-image>
</el-popover>
<el-image
class="photo2"
@click="recordMedia"
:src="require('@/assets/images/luxiang/video.png')"
fit="cover"
></el-image>
<el-image
@click="takephoto"
class="photo3"
:src="require('@/assets/images/luxiang/photo.png')"
fit="fill"
></el-image>
</div>
</div>
<span class="greySpan"> 注:1、录制视频文件时,一个视频时长不能超过一分钟;</span>
<span
class="greySpan"
style="margin-left:24px"
> 2、点击设置按钮,可选择连拍模式</span>
</div>
</div>
<div class="right">
//通过canvas绘画
<canvas
width="350px"
height="265px"
ref="canvas"
v-show="false"
></canvas>
<div class="right1">
<div
class="box"
v-for="(item, index) in dataList"
:key="'photo-' + index"
>
<div
v-if="item.type=='vedio'"
style="width: 100%; height:100%;border-radius: 5px;cursor: pointer;"
@click="preVideo(item.src)"
>
<video
:src="item.src"
style="width: 100%; height:100%;object-fit:cover"
></video>
<div class="player">
<img
:src="require('@/assets/images/luxiang/player.png')"
alt=""
>
</div>
</div>
<el-image
v-if="item.type=='photo'"
:preview-src-list="prephotoList"
:src="item.src"
fit="cover"
style="width: 100%;height: 100%;border-radius: 5px;"
></el-image>
<div class="del-icon"> <i
class="el-icon-delete "
@click="deletePhoto(index)"
></i></div>
</div>
</div>
//手动分页
<el-pagination
small
v-if="pagation.total>18"
:pager-count="5"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
:current-page.sync="pagation.currentPage"
:page-size="pagation.pagesize"
layout="prev, pager, next,total"
:total="pagation.total"
>
</el-pagination>
</div>
<div class="text-center margin-b10 subBtn">
<el-button
@click="closeContent"
class="width-80 margin-r20"
>取 消</el-button>
<el-button
type="primary"
@click="submitContent"
class="width-80"
>确 定</el-button>
</div>
//点击视频预览
<el-dialog
modal
class="videoShowDialog"
destroy-on-close
close-on-press-escape
close-on-click-modal
:visible.sync="preVideoShow"
>
<video-player
onPlayerPlay
class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
style="width:100%;height:100%"
:options="playerOptions"
:x5-video-player-fullscreen="true"
></video-player>
</el-dialog>
</div>
</template>
<script>
import "video.js/dist/video-js.css";
import "vue-video-player/src/custom-theme.css";
import { videoPlayer } from "vue-video-player";
import {
dataDetailApi,
showFloderApi,
addFolderApi,
} from "@/services/api/apply.js";
import Bus from "@/assets/js/bus";
import UploadBigFile from "./vedioUpload.vue";
import { getUserInfo, UUID } from "@/utils/helpers.js";
import { addDataFileApi } from "@/services/api/apply.js";
import { uploadFileApi, uploadMoreFileApi } from "@/services/api/common.js";
export default {
components: { UploadBigFile, videoPlayer },
props: ["dragShow", "name", "datasetId"],
data() {
return {
time: null,
time1: null,
time2: null,
preVideoShow: false,
pagation: {
pagesize: 18,
total: 0,
currentPage: 1,
},
checked: false,
recordType: 0, //0 未录制 1 录制中
mediaRecorder: null,
photoAndVedioList: [],
photoList: [],
prephotoList: [],
everyTime: "",
allTime: "",
list: [],
remotePath: "",
inputName: "",
editorNameShow: false,
addPathData: {},
id: "",
addPath: "",
data: [],
defaultProps: {
children: "children",
label: "name",
},
backFloader: [{}],
firstPath: "",
nowPath: "",
lastPath: "",
fileData: { listDirectory: [], listFile: [], fileServer: "" },
playerOptions: {
playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度
autoplay: false, //如果true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: "auto", // 建议浏览器在
language: "zh-CN",
aspectRatio: "16:9", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [
{
type: "video/mp4", //这里的种类支持很多种:基本视频格式、直播、流媒体等,具体可以参看git网址项目
src: "", //url地址
},
],
poster: "", //你的封面地址
// width: document.documentElement.clientWidth, //播放器宽度
notSupportedMessage: "此视频暂无法播放,请稍后再试", //允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true, //全屏按钮
},
},
};
},
methods: {
//弹窗预览视频
preVideo(url) {
this.preVideoShow = true;
this.playerOptions.sources[0].src = url;
},
handleSizeChange(size) {
this.pagation.pagesize = size;
},
//切换页码
handleCurrentChange(currentPage) {
this.pagation.currentPage = currentPage;
},
//视频录制按钮控制
recordMedia() {
if (this.recordType == 0) {
this.startMakeVideo();
} else {
this.stopMakeVideo();
}
},
// 调用摄像头
callCamera() {
// H5调用电脑摄像头API
navigator.mediaDevices
.getUserMedia({
video: true,
// audio: true,
})
.then((success) => {
// 摄像头开启成功
this.$refs["video"].srcObject = success;
this.mediaRecorder = new MediaRecorder(success, {
//因为视频和音频分开录制这里直接注释了
// audioBitsPerSecond: 128000, // 音频码率
videoBitsPerSecond: 1000000, // 视频码率
mimeType: "video/webm;codecs=h264", // 编码格式
});
// 实时拍照效果
this.$refs["video"].play();
})
.catch((error) => {
console.error("摄像头开启失败,请检查摄像头是否可用!");
});
},
//录制视频
startMakeVideo() {
this.mediaRecorder.start();
console.log("开始采集");
this.recordType = 1;
this.time = window.setTimeout(() => {
this.stopMakeVideo();
}, 60 * 1000);
},
//停止录制并上传
stopMakeVideo() {
this.mediaRecorder.stop();
// 事件
let _this = this;
this.mediaRecorder.ondataavailable = function (e) {
console.log(e);
//构建文件blob流
let blob = new Blob([e.data], { type: "video/mp4" });
//调用转base64函数
_this.blobToBase64(blob).then((res) => {
_this.photoList.push({ src: res, type: "vedio" });
_this.pagation.total = _this.photoList.length;
// 转化后的base64
console.log("base64", res);
});
console.log("停止采集视频");
};
this.recordType = 0;
window.clearTimeout(this.time);
},
//拍照
takephoto() {
if (this.checked) {
if (this.everyTime && this.allTime) {
//设置参数后定时拍照
let time1 = window.setInterval(() => {
this.savePhoto();
}, this.everyTime * 1000);
let time2 = setTimeout(() => {
window.clearInterval(time1);
}, this.allTime * 1000);
} else {
this.$message.error("请设置连拍参数");
}
} else {
this.savePhoto();
}
},
//图片展示在右侧 并保存在数组里
savePhoto() {
let ctx = this.$refs["canvas"].getContext("2d");
// 把当前视频帧内容渲染到canvas上
ctx.drawImage(this.$refs["video"], 0, 0, 350, 265);
// 转base64格式、图片格式转换、图片质量压缩
let imgBase64 = this.$refs["canvas"].toDataURL("image/png", 0.7); // 由字节转换为KB 判断大小
console.log(this.dataURLtoFile(imgBase64));
this.photoList.push({ src: imgBase64, type: "photo" });
this.prephotoList.push(imgBase64);
this.pagation.total = this.photoList.length;
},
//blob转base64
blobToBase64(blob) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e) => {
resolve(e.target.result);
};
// readAsDataURL
fileReader.readAsDataURL(blob);
fileReader.onerror = () => {
reject(new Error("blobToBase64 error"));
};
});
},
//base64转文件
dataURLtoFile(dataurl) {
let arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], UUID(), { type: mime });
},
//多文件列表上传
async uploadMoreFile() {
let formData = new FormData();
this.photoList.forEach((it) => {
formData.append("multipartFiles", this.dataURLtoFile(it.src));
});
const userInfo = getUserInfo();
formData.append("userId", userInfo.userId);
//这是项目的上传接口
const res = await this.$request(uploadMoreFileApi, formData);
if (res && res.status == 200 && res.data) {
res.data.forEach(({ fileFixedPath, fileType, fileSize, fileName }) => {
this.photoAndVedioList.push({
datasetcontentPath: fileFixedPath,
datasetcontentSuffix: fileType,
datasetcontentSize: fileSize,
datasetcontentName: fileName + "." + fileType.split("/").pop(),
});
});
} else {
this.$message.error("文件上传失败");
}
},
closeContent() {
this.$emit("close");
},
async getDetail() {
this.treeLoading = true;
const res = await this.$request(dataDetailApi, { id: this.datasetId });
if (res && res.status == 200) {
this.firstPath = this.nowPath = res.data.filesPath || "";
this.data = [];
}
},
deletePhoto(index) {
this.photoList.splice(index + (this.pagation.currentPage - 1) * 18, 1);
this.pagation.total = this.photoList.length;
if (this.dataList.length == 0) {
if (this.pagation.currentPage > 1)
this.handleCurrentChange(this.pagation.currentPage - 1);
}
},
async submitContent() {
if (this.photoList.length == 0) {
this.$message.error("请拍摄照片或数据集");
return;
}
//调用上传先传至服务器
this.uploadMoreFile().then(() => {
const datasetcontentsList = this.photoAndVedioList.map((it) => {
return { ...it, datasetId: this.datasetId };
});
//提交保存在这个项目中
this.$emit("submit", datasetcontentsList);
this.$emit("close");
});
},
},
created() {
this.callCamera();
this.getDetail();
},
computed: {
dataList() {
const dataCodeList = this.photoList;
const pagesize = this.pagation.pagesize;
const currentPage = this.pagation.currentPage;
return dataCodeList.slice(
(currentPage - 1) * pagesize,
pagesize * currentPage
);
},
},
};
</script>
1、创建recorder.js
// 兼容
window.URL = window.URL || window.webkitURL
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
let HZRecorder = function (stream, config) {
config = config || {}
config.sampleBits = config.sampleBits || 8 // 采样数位 8, 16
config.sampleRate = config.sampleRate || (44100 / 6) // 采样率(1/6 44100)
let context = new (window.webkitAudioContext || window.AudioContext)()
let audioInput = context.createMediaStreamSource(stream)
let createScript = context.createScriptProcessor || context.createJavaScriptNode
let recorder = createScript.apply(context, [4096, 1, 1])
let audioData = {
size: 0, // 录音文件长度
buffer: [], // 录音缓存
inputSampleRate: context.sampleRate, // 输入采样率
inputSampleBits: 16, // 输入采样数位 8, 16
outputSampleRate: config.sampleRate, // 输出采样率
oututSampleBits: config.sampleBits, // 输出采样数位 8, 16
input: function (data) {
this.buffer.push(new Float32Array(data))
this.size += data.length
},
compress: function () { // 合并压缩
// 合并
let data = new Float32Array(this.size)
let offset = 0
for (let i = 0; i < this.buffer.length; i++) {
data.set(this.buffer[i], offset)
offset += this.buffer[i].length
}
// 压缩
let compression = parseInt(this.inputSampleRate / this.outputSampleRate)
let length = data.length / compression
let result = new Float32Array(length)
let index = 0; let j = 0
while (index < length) {
result[index] = data[j]
j += compression
index++
}
return result
},
encodeWAV: function () {
let sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate)
let sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits)
let bytes = this.compress()
let dataLength = bytes.length * (sampleBits / 8)
let buffer = new ArrayBuffer(44 + dataLength)
let data = new DataView(buffer)
let channelCount = 1// 单声道
let offset = 0
let writeString = function (str) {
for (let i = 0; i < str.length; i++) {
data.setUint8(offset + i, str.charCodeAt(i))
}
}
// 资源交换文件标识符
writeString('RIFF'); offset += 4
// 下个地址开始到文件尾总字节数,即文件大小-8
data.setUint32(offset, 36 + dataLength, true); offset += 4
// WAV文件标志
writeString('WAVE'); offset += 4
// 波形格式标志
writeString('fmt '); offset += 4
// 过滤字节,一般为 0x10 = 16
data.setUint32(offset, 16, true); offset += 4
// 格式类别 (PCM形式采样数据)
data.setUint16(offset, 1, true); offset += 2
// 通道数
data.setUint16(offset, channelCount, true); offset += 2
// 采样率,每秒样本数,表示每个通道的播放速度
data.setUint32(offset, sampleRate, true); offset += 4
// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4
// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2
// 每样本数据位数
data.setUint16(offset, sampleBits, true); offset += 2
// 数据标识符
writeString('data'); offset += 4
// 采样数据总数,即数据总大小-44
data.setUint32(offset, dataLength, true); offset += 4
// 写入采样数据
if (sampleBits === 8) {
for (let i = 0; i < bytes.length; i++ , offset++) {
let s = Math.max(-1, Math.min(1, bytes[i]))
let val = s < 0 ? s * 0x8000 : s * 0x7FFF
val = parseInt(255 / (65535 / (val + 32768)))
data.setInt8(offset, val, true)
}
} else {
for (let i = 0; i < bytes.length; i++ , offset += 2) {
let s = Math.max(-1, Math.min(1, bytes[i]))
data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
}
}
return new Blob([data], { type: 'audio/mp3' })
}
}
// 开始录音
this.start = function () {
audioInput.connect(recorder)
recorder.connect(context.destination)
}
// 停止
this.stop = function () {
recorder.disconnect()
}
// 获取音频文件
this.getBlob = function () {
this.stop()
return audioData.encodeWAV()
}
// 回放
this.play = function (audio) {
let downRec = document.getElementById('downloadRec')
downRec.href = window.URL.createObjectURL(this.getBlob())
downRec.download = new Date().toLocaleString() + '.mp3'
audio.src = window.URL.createObjectURL(this.getBlob())
}
// 上传
this.upload = function (url, callback) {
let fd = new FormData()
fd.append('audioData', this.getBlob())
let xhr = new XMLHttpRequest()
/* eslint-disable */
if (callback) {
xhr.upload.addEventListener('progress', function (e) {
callback('uploading', e)
}, false)
xhr.addEventListener('load', function (e) {
callback('ok', e)
}, false)
xhr.addEventListener('error', function (e) {
callback('error', e)
}, false)
xhr.addEventListener('abort', function (e) {
callback('cancel', e)
}, false)
}
/* eslint-disable */
xhr.open('POST', url)
xhr.send(fd)
}
// 音频采集
recorder.onaudioprocess = function (e) {
audioData.input(e.inputBuffer.getChannelData(0))
// record(e.inputBuffer.getChannelData(0));
}
}
// 抛出异常
HZRecorder.throwError = function (message) {
alert(message)
throw new function () { this.toString = function () { return message } }()
}
// 是否支持录音
HZRecorder.canRecording = (navigator.getUserMedia != null)
// 获取录音机
HZRecorder.get = function (callback, config) {
if (callback) {
if (navigator.getUserMedia) {
navigator.getUserMedia(
{ audio: true } // 只启用音频
, function (stream) {
let rec = new HZRecorder(stream, config)
callback(rec,stream)
}
, function (error) {
switch (error.code || error.name) {
case 'PERMISSION_DENIED':
case 'PermissionDeniedError':
HZRecorder.throwError('用户拒绝提供信息。')
break
case 'NOT_SUPPORTED_ERROR':
case 'NotSupportedError':
HZRecorder.throwError('浏览器不支持硬件设备。')
break
case 'MANDATORY_UNSATISFIED_ERROR':
case 'MandatoryUnsatisfiedError':
HZRecorder.throwError('无法发现指定的硬件设备。')
break
default:
HZRecorder.throwError('无法打开麦克风。异常信息:' + (error.code || error.name))
break
}
})
} else {
HZRecorder.throwErr('当前浏览器不支持录音功能。'); return
}
}
}
export default HZRecorder
上面callback的回调参数我加了stream 便于离开页面时自动关闭麦克
2、先写个播放预览动态组件
audioplay.vue
<template>
<div
class="audio-user"
@click.stop="playThisAudio($event, url)"
>
<div style="display:flex">
<img
style="width:20px;height:20px"
v-if="!isLive"
:src="require('@/assets/images/luxiang/audio.png')"
//自己静态展示图
alt=""
>
<div
id="run"
v-if="isLive"
>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div>00:{{durations}}</div>
</div>
</template>
<script>
export default {
name: "audioplay",
components: {},
props: ["url", "duration", "index"],
data() {
return {
isLive: false,
audioStatus: "播放",
durations: this.duration,
audio: "",
};
},
methods: {
// 播放语音
playThisAudio(e, url) {
this.$emit("play", this.index);
let that = this;
if (this.audioStatus === "播放中...") {
if (this.audio) {
this.audio.pause();
that.isLive = false;
this.audioStatus = "暂停";
return false;
}
}
if (this.audioStatus === "暂停") {
if (this.audio) {
this.audio.play();
that.isLive = true;
this.audioStatus = "播放中...";
return false;
}
}
this.audio = new Audio(url);
this.audio.autoplay = true;
this.audio.play();
that.isLive = true;
var sumDuration = this.duration;
var audioI = 0;
this.audio.addEventListener("timeupdate", function () {
audioI++;
if (audioI <= 1) {
that.isLive = true;
that.audioStatus = "播放中...";
}
let du = parseInt(sumDuration - that.audio.currentTime);
if (du < 1) {
that.durations = "00";
} else if (du < 10) {
that.durations = "0" + du;
} else {
that.durations = du;
}
});
this.audio.addEventListener("ended", function () {
that.durations = that.duration;
that.isLive = false;
that.audioStatus = "播放完成";
setTimeout(() => {
this.audio = "";
}, 150);
});
},
},
created() {},
watch: {
duration(newValue) {
if (this.duration) {
this.durations = newValue;
}
},
},
};
</script>
<style lang="less" scoped>
#run {
width: 20px;
height: 20px;
}
#run div {
display: inline-block;
position: absolute;
bottom: 10px;
padding: 0;
margin: 0;
width: 2px;
height: 20px;
background-color: #41cfb1;
transform-origin: bottom;
border-radius: 5px 5px 0 0;
}
#run div:nth-child(1) {
left: 5px;
animation: musicWave 0.5s infinite linear both alternate;
}
#run div:nth-child(2) {
left: 10px;
animation: musicWave 0.2s infinite linear both alternate;
}
#run div:nth-child(3) {
left: 15px;
animation: musicWave 0.6s infinite linear both alternate;
}
#run div:nth-child(4) {
left: 20px;
animation: musicWave 0.3s infinite linear both alternate;
}
#run div:nth-child(5) {
left: 25px;
animation: musicWave 0.3s infinite linear both alternate;
}
@keyframes musicWave {
0% {
height: 2px;
}
100% {
height: 15px;
}
}
.audio-user {
/* width: 3rem;
height: 0.5rem; */
width: 100px;
height: 40px;
line-height: 0.5rem;
cursor: pointer;
position: relative;
/* background-color: #eee; */
/* border-radius: 0.5rem; */
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
/* padding: 0 0.2rem; */
color: green;
}
</style>
3、Cvoice.vue
<template>
<div class="main ">
<div class="left">
<div class="vedio">
<div class="shexiang">
<!--canvas截取流-->
<!--图片展示-->
//这边因为要自己写动态展示波浪 能力有限就只能暴力写法 动画也是对应的
<div class="big">
<div :class="isLive?'bs':'nomove1'"></div>
<div :class="isLive?'bs':'nomove2'"></div>
<div :class="isLive?'bs':'nomove3'"></div>
<div :class="isLive?'bs':'nomove4'"></div>
<div :class="isLive?'bs':'nomove5'"></div>
<div :class="isLive?'bs':'nomove6'"></div>
<div :class="isLive?'bs':'nomove7'"></div>
<div :class="isLive?'bs':'nomove8'"></div>
<div :class="isLive?'bs':'nomove9'"></div>
<div :class="isLive?'bs':'nomove10'"></div>
<div :class="isLive?'bs':'nomove11'"></div>
<div :class="isLive?'bs':'nomove12'"></div>
<div :class="isLive?'bs':'nomove13'"></div>
<div :class="isLive?'bs':'nomove14'"></div>
<div :class="isLive?'bs':'nomove15'"></div>
<div :class="isLive?'bs':'nomove16'"></div>
<div :class="isLive?'bs':'nomove17'"></div>
<div :class="isLive?'bs':'nomove18'"></div>
<div :class="isLive?'bs':'nomove19'"></div>
<div :class="isLive?'bs':'nomove20'"></div>
<div :class="isLive?'bs':'nomove21'"></div>
<div :class="isLive?'bs':'nomove22'"></div>
<div :class="isLive?'bs':'nomove23'"></div>
<div :class="isLive?'bs':'nomove24'"></div>
<div :class="isLive?'bs':'nomove25'"></div>
<div :class="isLive?'bs':'nomove26'"></div>
<div :class="isLive?'bs':'nomove27'"></div>
<div :class="isLive?'bs':'nomove28'"></div>
<div :class="isLive?'bs':'nomove29'"></div>
<div :class="isLive?'bs':'nomove30'"></div>
</div>
<!-- <video
ref="video"
width="350px"
height="136px"
autoplay
></video> -->
<el-button
class="btn"
@mousedown.native="mouseStart"
@mouseleave.native="mouseEnd"
@mouseup.native="mouseEnd"
type="primary"
round
icon="el-icon-microphone"
>按住录制声音</el-button>
</div>
</div>
<div class="catagory">
<span style="padding-left:5px">选择数据存储路径</span>
<div class="flex-r-wrap margin-t5 padding-0-5">
<div class=" margin-t5 single-file">
<el-tree
:node-key="id"
:show-checkbox="false"
ref="tree"
:data="data"
:props="defaultProps"
@node-click="nodeClick"
@node-expand="handleNodeClick"
>
<div
class="custom-tree-node"
slot-scope="{ node ,data }"
>
<span v-if="data.type=='folder'"> <img :src='listImg("folder")' /></span>
<span v-else>
<img :src="getFileTpe(data.name)" />
</span>
<span style="margin-left:14px">{{
node.label
}}</span>
</div>
</el-tree>
<div
style="height:100%"
@click="resetPath"
></div>
</div>
<el-button
type="primary"
class="addBtn"
@click="addFolder"
>新建文件夹</el-button>
</div>
</div>
</div>
<div class="right">
<div class="right1">
<div
class="box"
v-for="(item, index) in dataList"
:key="'audio-' + index"
>
<audioplay
:url="item.src"
:duration="item.duration"
:ref='"audioPlay"+index'
:index="index"
@play="play"
></audioplay>
<i
class="el-icon-error text-ct3"
@click="deleteAudio(index)"
></i>
</div>
</div>
<el-pagination
small
v-if="pagation.total>30"
:pager-count="5"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
:current-page.sync="pagation.currentPage"
:page-size="pagation.pagesize"
layout="prev, pager, next,total"
:total="pagation.total"
>
</el-pagination>
</div>
<div class="text-center margin-b10 subBtn">
<el-button
@click="closeContent"
class="width-80 margin-r20"
>取 消</el-button>
<el-button
type="primary"
@click="submitContent"
class="width-80"
>确 定</el-button>
</div>
</div>
</template>
<script>
//有些接口是我自己用的可以根据自己需求改
import audioplay from "@/components/audioplay.vue";
import recording from "./recorder.js";
import {
dataDetailApi,
showFloderApi,
addFolderApi,
} from "@/services/api/apply.js";
import Bus from "@/assets/js/bus";
import UploadBigFile from "./vedioUpload.vue";
import { getUserInfo, UUID } from "@/utils/helpers.js";
import { addDataFileApi } from "@/services/api/apply.js";
import { uploadFileApi, uploadMoreFileApi } from "@/services/api/common.js";
export default {
components: { UploadBigFile, audioplay },
props: ["dragShow", "name", "datasetId"],
data() {
return {
MediaStreamTrack: null, //用来关闭媒体流的
number: "",
audio: "",
isLive: false,
pagation: {
pagesize: 30,
total: 0,
currentPage: 1,
},
audioList: [],
recorder: null,
interval: "",
timeOut: "",
audioFileList: [], // 上传语音列表
startTime: "", // 语音开始时间
endTime: "", // 语音结束
photoAndVedioList: [],
list: [],
remotePath: "",
inputName: "",
editorNameShow: false,
addPathData: {},
id: "",
addPath: "",
data: [],
defaultProps: {
children: "children",
label: "name",
},
backFloader: [{}],
firstPath: "",
nowPath: "",
lastPath: "",
fileData: { listDirectory: [], listFile: [], fileServer: "" },
};
},
methods: {
//点击空白处选择根目录 用于上传
resetPath() {
this.addPath = this.firstPath;
this.addPathData = {};
console.log(this.addPath);
},
//audioplay调用播放 下面对number监听 同时只能播放一个音频
play(index) {
this.number = index;
},
handleSizeChange(size) {
this.pagation.pagesize = size;
},
//手动分页切换页码
handleCurrentChange(currentPage) {
this.pagation.currentPage = currentPage;
},
// 清除定时器
clearTimer() {
if (this.interval) {
this.num = 60;
clearInterval(this.interval);
}
},
// 长按说话
mouseStart() {
this.isLive = true;
this.clearTimer();
this.startTime = new Date().getTime();
//调用recorder.js的get() 有回调
recording.get((rec, stream) => {
// 当首次按下时,要获取浏览器的麦克风权限,所以这时要做一个判断处理
if (rec) {
this.MediaStreamTrack = stream.getTracks();
// track set 流中所有 MediaStreamTrack 对象的序列 下面关闭麦克用的
this.recorder = rec;
this.recorder.start();
}
});
//最长录音时间60s
this.timeOut = setTimeout(() => {
this.mouseEnd();
}, 60 * 1000);
},
// 松开时上传语音
mouseEnd() {
if (this.timeOut) {
clearTimeout(this.timeOut);
}
this.clearTimer();//清定时器
this.endTime = new Date().getTime();
let duration = Math.ceil((this.endTime - this.startTime) / 1000 - 0.1);
if (duration < 1) {
this.$message.error("说话时间太短啦!");
return;
}
if (this.recorder) {
//停止录音
this.recorder.stop();
// 获取语音二进制文件
let bold = this.recorder.getBlob();
// 将获取的二进制对象转为二进制Base64文件流
this.blobToBase64(bold).then((res) => {
this.audioList.push({
src: res,
duration: duration < 10 ? "0" + duration : duration,
type: "voice",
});
this.pagation.total = this.audioList.length;
});
}
this.recorder = null;//录音器对象置空
this.isLive = false;
},
//关闭麦克设备
cancalCloseAudio() {
this.MediaStreamTrack[0].stop();
},
//blob转base64
blobToBase64(blob) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (e) => {
resolve(e.target.result);
};
// readAsDataURL
fileReader.readAsDataURL(blob);
fileReader.onerror = () => {
reject(new Error("blobToBase64 error"));
};
});
},
//base64转文件
dataURLtoFile(dataurl) {
let arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], UUID(), { type: mime });
},
//上传多个文件
async uploadMoreFile() {
let formData = new FormData();//以formData传文件
this.audioList.forEach((it) => {
formData.append("multipartFiles", this.dataURLtoFile(it.src));//base64转文件
});
const userInfo = getUserInfo();
formData.append("userId", userInfo.userId);//可根据自己后台接口需求增加参数
const res = await this.$request(uploadMoreFileApi, formData);
if (res && res.status == 200 && res.data) {
res.data.forEach(({ fileFixedPath, fileType, fileSize, fileName }) => {
this.photoAndVedioList.push({
datasetcontentPath: fileFixedPath,
datasetcontentSuffix: fileType,
datasetcontentSize: fileSize,
datasetcontentName: fileName + "." + fileType.split("/").pop(),
});
});
} else {
this.$message.error("文件上传失败");
}
},
//关闭窗口
closeContent() {
this.$emit("close");
},
//树形目录展示图标
getFileTpe(name) {
console.log(name);
let type = name && name.split(".").pop();
return this.listImg(type);
},
//新建文件夹
async submitName() {
console.log(JSON.stringify(this.addPathData) == "{}");
if (JSON.stringify(this.addPathData) == "{}") {
this.addPathData = {
id: "folder_1",
localPath: this.addPath,
name: this.inputName,
type: "folder",
children: [{}],
};
}
console.log(this.addPathData);
const newChild = {
id:
this.addPathData.id +
"_" +
(this.addPathData.children.filter((item) => {
return item.type == "folder";
}).length +
1),
name: this.inputName,
children: [],
type: "folder",
localPath: this.addPathData.localPath + "/" + this.inputName,
};
console.log(this.addPathData);
const res = await this.$request(addFolderApi, {
localPath: this.addPathData.localPath + "/" + this.inputName,
id: this.datasetId,
});
if (res && res.status == 200) {
if (!this.addPathData.children) {
this.$set(this.addPathData, "children", []);
}
this.addPathData.children.push(newChild);
this.editorNameShow = false;
if (this.addPath == this.remotePath) {
this.getDetail();
}
this.$emit("initData");
} else if (res.status == 611) {
this.$message.error("文件夹名称名称重复");
}
},
async addFolder() {
this.editorNameShow = true;
},
//
async getDetail() {
this.treeLoading = true;
const res = await this.$request(dataDetailApi, { id: this.datasetId });
if (res && res.status == 200) {
this.firstPath = this.nowPath = res.data.filesPath || "";
this.data = [];
this.showList([], res.data.filesPath || "");
}
},
//树节点点击
nodeClick(data) {
this.addPathData = data;
this.addPath = data.localPath;
console.log(this.addPathData);
},
//树节点展开
handleNodeClick(data) {
this.addPathData = data;
this.addPath = data.localPath;
// console.log(JSON.stringify(data.children[0]) == "{}");
if (data.children && JSON.stringify(data.children[0]) !== "{}") {
return;
} else if (data.children && JSON.stringify(data.children[0]) == "{}") {
this.showList(data, data.localPath);
}
},
//展示树
async showList(data, path) {
if (data.length == 0) {
this.nowPath = path;
let index = path.lastIndexOf("/");
if (index == 0) {
this.lastPath = path.substring(0, index + 1);
} else {
this.lastPath = path.substring(0, index);
}
this.remotePath = path;
this.addPath = path;
const res = await this.$request(showFloderApi, {
remotePath: path,
});
if (res && res.status == 200) {
this.fileData = res.data;
if (res.data.listDirectory.length != 0) {
for (let i = 0; i < res.data.listDirectory.length; i++) {
this.data.push({
id: "folder_" + (i + 1),
...res.data.listDirectory[i],
children: [{}],
type: "folder",
});
}
}
// for (let i = 0; i < res.data.listFile.length; i++) {
// this.data.push({
// id: "file_" + (i + 1),
// type: "file",
// ...res.data.listFile[i],
// });
// }
}
} else {
this.nowPath = path;
let index = path.lastIndexOf("/");
if (index == 0) {
this.lastPath = path.substring(0, index + 1);
} else {
this.lastPath = path.substring(0, index);
}
const res = await this.$request(showFloderApi, {
remotePath: path,
});
if (res && res.status == 200) {
this.fileData = res.data;
if (res.data.listDirectory.length == 0) {
data.children.pop();
} else {
for (let i = 0; i < res.data.listDirectory.length; i++) {
if (i == 0) {
data.children.pop();
}
data.children.push({
id: data.id + "_" + (i + 1),
...res.data.listDirectory[i],
children: [{}],
type: "folder",
});
}
}
}
}
},
listImg(file) {
if (file === "vedio" || file === "mp4") {
return require("@/assets/images/vedio.png");
} else if (file === "voice" || file === "mp3") {
return require("@/assets/images/voice.png");
} else if (file === "ZIP") {
return require("@/assets/images/zip.png");
} else if (file === "txt") {
return require("@/assets/images/txt.png");
} else if (file === "jpg" || file === "png") {
return require("@/assets/images/picture.png");
} else if (file === "doc") {
return require("@/assets/images/doc.png");
} else if (file === "folder") {
return require("@/assets/images/folder.png");
} else {
return require("@/assets/images/doc.png");
}
},
// queryFile(path, file) {
// console.log(file);
// this.list.push({
// datasetcontentPath: path,
// datasetcontentSuffix: file.fileType,
// datasetcontentSize: file.size,
// datasetcontentName: file.name,
// });
// console.log(this.list);
// },
//删除音频
deleteAudio(index) {
// console.log(index);
// console.log(
// index + (this.pagation.currentPage - 1) * this.pagation.pagesize
// );
//对应分页位置
this.audioList.splice(
index + (this.pagation.currentPage - 1) * this.pagation.pagesize,
1
);
console.log(this.audioList);
this.pagation.total = this.audioList.length;
if (this.dataList.length == 0) {
if (this.pagation.currentPage > 1)
this.handleCurrentChange(this.pagation.currentPage - 1);
}
},
//提交上传,更新内容
async submitContent() {
if (this.audioList.length == 0) {
this.$message.error("请录制音频");
return;
}
this.uploadMoreFile().then(() => {//上传完的回调
const datasetcontentsList = this.photoAndVedioList.map((it) => {
return { ...it, datasetId: this.datasetId };
});
this.$emit("submit", datasetcontentsList, this.addPath);//调用了更新接口
this.$emit("close");
});
},
},
created() {
this.getDetail();
},
beforeDestroy() {
this.cancalCloseAudio();
},
watch: {
//监听number 用于展示一个同时只能播放一个音频
number(newValue, oldValue) {
if (oldValue || oldValue === 0) {
if (newValue || newValue === 0) {
this.$refs["audioPlay" + oldValue][0].audio.currentTime = 0;
this.$refs["audioPlay" + oldValue][0].audio.pause();
this.$refs["audioPlay" + oldValue][0].audioStatus = "暂停";//控制播放时间的
this.$refs["audioPlay" + oldValue][0].isLive = false;//控制动态样式
}
}
},
},
computed: {
dataList() {
const dataCodeList = this.audioList;
const pagesize = this.pagation.pagesize;
const currentPage = this.pagation.currentPage;
console.log(
dataCodeList.slice((currentPage - 1) * pagesize, pagesize * currentPage)
);
return dataCodeList.slice(
(currentPage - 1) * pagesize,
pagesize * currentPage
);
},
},
};
</script>
<style lang="less" scoped>
.main {
display: flex;
flex-wrap: nowrap;
position: relative;
}
.subBtn {
position: absolute;
top: 650px;
left: 350px;
z-index: 2;
}
.addBtn {
padding: 3px;
top: 350px;
left: 15px;
position: absolute;
z-index: 2;
}
.single-file /deep/ .el-tree-node.is-expanded > .el-tree-node__children {
display: inline;
}
.single-file {
display: flex;
flex-direction: column;
padding-top: 10px;
width: 350px;
height: 300px;
overflow: auto;
border: 1px solid #e6e6e6;
}
#everyTime {
width: 20px;
border: 0px;
outline: none;
border-bottom: 1px solid black;
}
#allTime {
outline: none;
width: 30px;
border: 0px;
border-bottom: 1px solid black;
}
.right {
margin-top: 10px;
margin-left: 10px;
width: 500px;
height: 600px;
// background: red;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
overflow: auto;
.right1 {
align-self: flex-start;
}
.box {
margin-top: 10px;
margin-right: 10px;
// padding: 10px;
width: 114px;
height: 46px;
background: #ffffff;
box-shadow: 0px 0px 10px 0px rgba(51, 51, 51, 0.06);
display: inline-block;
position: relative;
}
.text-ct3 {
position: absolute;
top: 0px;
right: 0px;
color: #ccc;
font-size: 18px;
cursor: pointer;
}
}
.left {
display: inline-block;
width: 350px;
height: 600px;
margin-left: 20px;
margin-top: 20px;
// background: greenyellow;
.vedio {
// background-color: yellowgreen;
box-shadow: 0px 0px 10px 0px rgba(51, 51, 51, 0.06);
.greySpan {
color: grey;
display: inline-block;
padding-left: 10px;
}
.shexiang {
width: 350px;
height: 136px;
background: #ffffff;
height: 136px;
position: relative;
.big {
width: 310px;
height: 100px;
margin: 0px 18px;
display: flex;
align-items: center;
justify-content: space-around;
}
.bs {
// display: inline-block;
// position: absolute;
// bottom: 76px;
padding: 0;
margin: 0;
width: 4px;
height: 28px;
background: #464a49;
border-radius: 2px;
// transform-origin: bottom;
}
.bs:nth-child(1) {
left: 18px;
animation: musicWave 0.5s infinite linear both alternate;
}
.nomove1 {
width: 4px;
height: 10px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(2) {
left: 28px;
animation: musicWave 0.2s infinite linear both alternate;
}
.nomove2 {
width: 4px;
height: 20px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(3) {
left: 38px;
animation: musicWave 0.6s infinite linear both alternate;
}
.nomove3 {
width: 4px;
height: 30px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(4) {
left: 48px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove4 {
width: 4px;
height: 24px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(5) {
left: 58px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove5 {
width: 4px;
height: 26px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(6) {
left: 68px;
animation: musicWave 0.5s infinite linear both alternate;
}
.nomove6 {
width: 4px;
height: 20px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(7) {
left: 78px;
animation: musicWave 0.2s infinite linear both alternate;
}
.nomove7 {
width: 4px;
height: 22px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(8) {
left: 88px;
animation: musicWave 0.6s infinite linear both alternate;
}
.nomove8 {
width: 4px;
height: 27px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(9) {
left: 98px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove9 {
width: 4px;
height: 30px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(10) {
left: 108px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove10 {
width: 4px;
height: 36px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(11) {
left: 118px;
animation: musicWave 0.5s infinite linear both alternate;
}
.nomove11 {
width: 4px;
height: 16px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(12) {
left: 128px;
animation: musicWave 0.2s infinite linear both alternate;
}
.nomove12 {
width: 4px;
height: 25px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(13) {
left: 138px;
animation: musicWave 0.6s infinite linear both alternate;
}
.nomove13 {
width: 4px;
height: 19px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(14) {
left: 148px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove14 {
width: 4px;
height: 27px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(15) {
left: 158px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove15 {
width: 4px;
height: 27px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(16) {
left: 168px;
animation: musicWave 0.5s infinite linear both alternate;
}
.nomove16 {
width: 4px;
height: 36px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(17) {
left: 178px;
animation: musicWave 0.2s infinite linear both alternate;
}
.nomove17 {
width: 4px;
height: 28px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(18) {
left: 188px;
animation: musicWave 0.6s infinite linear both alternate;
}
.nomove18 {
width: 4px;
height: 23px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(19) {
left: 198px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove19 {
width: 4px;
height: 25px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(20) {
left: 208px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove20 {
width: 4px;
height: 22px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(21) {
left: 218px;
animation: musicWave 0.5s infinite linear both alternate;
}
.nomove21 {
width: 4px;
height: 19px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(22) {
left: 228px;
animation: musicWave 0.2s infinite linear both alternate;
}
.nomove22 {
width: 4px;
height: 26px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(23) {
left: 238px;
animation: musicWave 0.6s infinite linear both alternate;
}
.nomove23 {
width: 4px;
height: 18px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(24) {
left: 248px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove24 {
width: 4px;
height: 20px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(25) {
left: 258px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove25 {
width: 4px;
height: 25px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(26) {
left: 268px;
animation: musicWave 0.5s infinite linear both alternate;
}
.nomove26 {
width: 4px;
height: 36px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(27) {
left: 278px;
animation: musicWave 0.2s infinite linear both alternate;
}
.nomove27 {
width: 4px;
height: 21px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(28) {
left: 288px;
animation: musicWave 0.6s infinite linear both alternate;
}
.nomove28 {
width: 4px;
height: 19px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(29) {
left: 298px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove29 {
width: 4px;
height: 25px;
background: #464a49;
border-radius: 2px;
}
.bs:nth-child(30) {
left: 308px;
animation: musicWave 0.3s infinite linear both alternate;
}
.nomove30 {
width: 4px;
height: 22px;
background: #464a49;
border-radius: 2px;
}
@keyframes musicWave {
0% {
height: 2px;
}
100% {
height: 28px;
}
}
.btn {
cursor: pointer;
position: absolute;
bottom: 12px;
right: 105px;
z-index: 2;
}
}
}
.catagory {
margin-top: 78px;
position: relative;
}
}
</style>