javascript
复制代码
<template>
<div style="height: 100vh;background: #000;">
<div style="position: fixed;top: 15px;left: 15px;color: #fff;font-size: 18px;z-index: 100;" @click="$router.go(-1)">
<img src="./img/left.svg" alt="" style="width: 25px;height: 25px;">
</div>
<div v-if="recordedVideoUrl" style="position: fixed;top: 15px;right: 15px;color: #fff;font-size: 18px;z-index: 100;" @click="getto">
<img src="./img/dh.svg" alt="" style="width: 30px;height: 30px;">
</div>
<video ref="video" autoplay muted playsinline></video>
<div class="video_btn" style="bottom: 130px;" v-if="!isRecording && deviceslistnum > 0 && !currentText">
<div class="video_btn_item">
<div :class="tabindex == id ? 'video_btn_item_topa' : 'video_btn_item_top'" v-for="(it,id) in deviceslistnum" :key="id" @click="setdevices(id)">
{{ '后置' + (id + 1) }}
</div>
</div>
</div>
<div class="video_btn" v-if="!recordedVideoUrl">
<div style="width: 30px;height: 30px;">
<!-- 手电筒控制按钮 -->
<!-- <img v-if="isFlashSupported && !currentText && !isRecording" :src="isTorchOn ? './img/sdtk.svg' : './img/sdtg.svg'" alt="手电筒" @click="toggleTorch" style="width: 30px;height: 30px;"/> -->
</div>
<div class="btn_round">
<div class="progress-button" @touchstart.prevent="gtouchstart" @touchend.prevent="gtouchend">
<svg class="progress-circle" width="90" height="90">
<circle class="progress-circle-background" cx="45" cy="45" r="35" />
<circle class="progress-circle-fill" cx="45" cy="45" r="35" :style="circleStyle" />
</svg>
<div class="progress-text"></div>
</div>
</div>
<div style="width: 30px;height: 30px;">
<img v-if="!isRecording" src="./img/tanform.svg" alt="" @click="setdevicestwo" style="width: 30px;height: 30px;">
</div>
</div>
<div class="video_btn" v-else>
<div class="video_btn_bottom" style="color: #fff;" @click="clearvideo">
重新录制
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex"
export default {
data() {
return {
mediaStream: null, // 摄像头视频流
mediaRecorder: null, // 媒体录制器
recordedChunks: [], // 录制的数据块
recordedVideoUrl: null, // 录制后的视频 URL
isRecording: false, // 是否正在录制
currentDeviceId: null, // 当前摄像头设备 ID
currentText: true, // true前 false后
devices: [], // 设备列表
deviceslistnum: 0,
timer: null, // 计时器,用于限制录制时间
timernum: 30, // 计时器,用于限制录制时间
maxFileSize: 100 * 1024 * 1024, // 100MB
progress: 0,
interval: null,
isTorchOn: false, // 手电筒状态
isFlashSupported: false, // 是否支持闪光灯
tabindex: 0,
}
},
computed: {
...mapState("login", ["token", "giant_opo_userdata"]),
circleStyle() {
const circumference = 2 * Math.PI * 40
const offset = circumference - (this.progress / 100) * circumference
return {
strokeDasharray: `${circumference} ${circumference}`,
strokeDashoffset: offset,
}
},
},
mounted() {
this.startCamera()
},
methods: {
// 切换摄像头
setdevices(val) {
this.tabindex = val
this.switchCamera(val + 1)
},
//
setdevicestwo() {
if (this.currentText) {
this.switchCamera(1)
this.currentText = false
} else {
this.switchCamera(0)
this.currentText = true
}
},
gtouchstart() {
// //真正长按后应该执行的内容
this.isRecording = true
this.startProgress()
this.startRecording()
console.log('按下')
return false
},
//手释放,如果在500毫秒内就释放,则取消长按事件,此时可以执行onclick应该执行的事件
gtouchend() {
this.isRecording = false
this.progress = 0
clearInterval(this.interval)
this.stopRecording()
console.log('松开')
return false
},
startProgress() {
this.interval = setInterval(() => {
if (this.progress >= 100) {
clearInterval(this.interval)
this.interval = null
return
}
this.progress += 1
}, 300)
},
async startCamera() {
try {
// 获取设备列表
const devices = await navigator.mediaDevices.enumerateDevices()
this.devices = devices.filter(device => device.kind === "videoinput")
if (this.devices.length === 0) {
throw new Error("没有找到摄像头设备")
}
// 默认选择第一个设备 首次前置
this.currentDeviceId = this.devices[0].deviceId
// 获取视频流并绑定
this.mediaStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: this.currentDeviceId }
})
const videoElement = this.$refs.video
videoElement.srcObject = this.mediaStream
// 检查设备是否支持闪光灯
const videoTrack = this.mediaStream.getVideoTracks()[0]
if (videoTrack && "torch" in videoTrack.getCapabilities()) {
this.isFlashSupported = true
}
} catch (error) {
console.error("无法访问摄像头:", error)
}
},
// 控制闪光灯(手电筒)
async toggleTorch() {
if (this.isFlashSupported) {
const videoTrack = this.mediaStream.getVideoTracks()[0]
if (videoTrack) {
try {
// 切换 torch 状态
const capabilities = videoTrack.getCapabilities()
if (capabilities.torch) {
this.isTorchOn = !this.isTorchOn
await videoTrack.applyConstraints({
advanced: [{ torch: this.isTorchOn }]
})
}
} catch (error) {
console.error("无法切换手电筒:", error)
}
}
}
},
// 开始录制或停止录制
startRecording() {
if (this.mediaStream) {
this.mediaRecorder = new MediaRecorder(this.mediaStream)
this.recordedChunks = []
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.recordedChunks.push(event.data)
// 检查文件大小是否超过 95MB
const totalSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0)
if (totalSize > this.maxFileSize) {
this.stopRecording()
}
}
}
this.mediaRecorder.onstop = () => {
const blob = new Blob(this.recordedChunks, { type: "video/webm" })
// this.recordedVideoUrl = URL.createObjectURL(blob)
this.recordedVideoUrl = blob
}
this.timer = setInterval(() => {
this.settime()
}, 1000) // 60秒后停止录制
this.mediaRecorder.start()
this.isRecording = true
}
},
settime() {
if (this.timernum < 1) {
this.stopRecording()
clearInterval(this.timer)
return
} else {
this.timernum--
}
},
stopRecording() {
if (this.mediaRecorder) {
this.mediaRecorder.stop()
clearInterval(this.timer) // 停止定时器
this.isRecording = false
}
this.progress = 0
this.timernum = 30
},
async switchCamera(val) {
try {
// 停止当前的媒体流
this.stopStream()
// 切换到下一个摄像头设备(循环切换)
const devices = await navigator.mediaDevices.enumerateDevices()
this.devices = devices.filter(device => device.kind === "videoinput")
this.deviceslistnum = this.devices.length - 1
// let currentIndex = 0
// if (this.devices.length == 2) {
// currentIndex = this.devices.findIndex(device => device.deviceId === this.currentDeviceId)
// const nextIndex = (currentIndex + 1) % this.devices.length
// this.currentDeviceId = this.devices[nextIndex].deviceId
this.currentDeviceId = this.devices[val].deviceId
// } else if (this.devices.length > 2) {
// if (this.currentText) {
// this.currentDeviceId = this.devices[1].deviceId
// this.currentText = false
// } else {
// this.currentDeviceId = this.devices[0].deviceId
// this.currentText = true
// }
// }
// 获取新的摄像头流
this.mediaStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: this.currentDeviceId }
})
const videoElement = this.$refs.video
videoElement.srcObject = this.mediaStream
} catch (error) {
this.$toast('无法切换摄像头' + error)
}
},
stopStream() {
if (this.mediaStream) {
const tracks = this.mediaStream.getTracks()
tracks.forEach(track => track.stop())
}
},
// 重新录制
clearvideo() {
this.isRecording = false
this.progress = 0
this.recordedVideoUrl = null
clearInterval(this.interval)
},
// 发布
getto() {
let _this = this
// let formdata = new FormData()
// formdata.append("video", file)
_this.$loadingService.show({}, true)
let httpURL = _this.$store.state.api.qywx_admin_api + "/upload/video_blob"
// console.log(file,'123123123123')
_this.axios
.post(httpURL, this.recordedVideoUrl, {
headers: {
token: _this.token,
},
})
.then(function (res) {
_this.$loadingService.hide()
if (res.status == 200) {
if (typeof res.data != "object") {
try {
res.data = JSON.parse(res.data)
} catch (error) {
_this.$toast("拉取数据失败:接口异常")
console.error(error)
return false
}
}
if (res.data.status == 1) {
if (_this.$route.query.path == 'addFriend') {
_this.$router.push({
path: '/' + _this.$route.query.path,
query: {
fileUrl: res.data.data.path
}
})
}
} else {
_this.$toast(res.data.msg || "出现了一些问题")
}
} else {
_this.$toast(res.data.msg ? res.data.msg : "请求失败")
}
})
.catch(function (error) {
_this.$loadingService.hide()
console.log(error)
// file.status = "failed";
// file.message = "上传失败";
_this.$toast("接口异常,出现了一些问题")
})
}
}
}
</script>
<style scoped lang='less'>
.video_btn {
position: fixed;
bottom: 30px;
z-index: 99;
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
max-width: 640px;
margin: 0 auto;
.video_btn_bottom{
padding: 10px;
text-align: center;
background: #858585;
border-radius: 10px;
}
}
.video_btn_item{
border-radius: 20px;
display: flex;
}
.video_btn_item_topa{
color: #fff;
padding:0px 8px;
border-radius: 20px;
border: 1px solid #fff;
}
.video_btn_item_top{
color: #fff;
padding:0px 8px;
}
video {
width: 100%;
height: 80vh;
max-width: 640px;
margin: 0 auto;
}
/* 按钮 */
// .btn_round {
// width: 60px;
// height: 60px;
// border-radius: 50px;
// background: #fff;
// position: relative;
// .btn_round_item_one {
// position: absolute;
// width: 55px;
// height: 55px;
// border-radius: 50px;
// background: #ccc;
// z-index: 1;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%);
// }
// .btn_round_item_two {
// position: absolute;
// width: 40px;
// height: 40px;
// border-radius: 50px;
// background: #999898;
// z-index: 2;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%);
// }
// }
.progress-button {
position: relative;
cursor: pointer;
}
.progress-circle {
transform: rotate(-90deg);
}
.progress-circle-background {
fill: none;
stroke: #fff;
stroke-width: 5;
}
.progress-circle-fill {
fill: none;
stroke: #13e013;
stroke-width: 5;
transition: stroke-dashoffset 0.3s;
}
.progress-text {
position: absolute;
top: 48%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
width: 60px;
height: 60px;
border-radius: 60px;
}
</style>