图片拍照:
<template>
<div>
<v-easy-camera
:fullscreen="true"
ref="easyCamera"
v-model="pictureData.picture"
class="main-camera"
>
<template #header>
<div class="top">
<van-image
class="mask_control_close"
width="20px"
height="20px"
fit="cover"
@click="cameraStop"
:src="require('@/assets/img/camera/叉叉.png')"
/>
</div>
</template>
</v-easy-camera>
<van-image
class="mask"
:src="require('@/assets/img/camera/333.png')"
/>
<van-image
class="mask_control"
width="80px"
height="80px"
fit="cover"
@click="cameraSnap"
:src="require('@/assets/img/camera/拍照.png')"
/>
<van-image
v-show="confirmIsShow"
class="mask_control_confirm"
width="40px"
height="40px"
fit="cover"
@click="confirm"
:src="require('@/assets/img/camera/确定.png')"
/>
<van-image
v-show="confirmIsShow"
class="mask_control_return"
width="40px"
height="40px"
fit="cover"
@click="cancel"
:src="require('@/assets/img/camera/撤销.png')"
/>
<van-image
v-show="!confirmIsShow && isAndroid"
class="mask_control_confirm"
width="40px"
height="40px"
fit="cover"
@click="flip"
:src="require('@/assets/img/camera/翻转镜头.png')"
/>
</div>
</template>
<script>
import EasyCamera from "easy-vue-camera";
export default {
name: "cameraCustom",
components: {
"v-easy-camera": EasyCamera,
},
data() {
return {
pictureData: {
picture: "",
pictureSize: "",
},
isEnable: true,
myEasyCamera: null,
confirmIsShow: false,
isFlip: false,
isAndroid: false,
currentFacingMode: "",
};
},
mounted() {
// 判断用户的操作系统
const isAndroid = /android/i.test(navigator.userAgent);
const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
//获取原始控件区并隐藏(处理为移除子元素)
let aaa = document.getElementsByClassName("camera-stack")[0];
let div = document.createElement("div");
div.className = "bott";
div.style.height = "180px";
div.style.widht = "100%";
aaa.appendChild(div);
let controlModel = document.getElementsByClassName(
"camera-stack-controls"
)[0];
controlModel.removeChild(controlModel.firstChild);
//获取拍照组件实例
this.myEasyCamera = this.$refs.easyCamera;
if (isAndroid) {
this.isAndroid = false;
console.log("This device is using Android.");
// 在 Android 上执行的逻辑
} else if (isIOS) {
this.isAndroid = true;
setInterval(() => {
if (this.isFlip == false) {
this.flip();
}
}, 500);
// 在 iOS 上执行的逻辑
} else {
console.log("This device is using a different operating system.");
// 在其他操作系统上执行的逻辑
}
},
methods: {
// 点击关闭
cameraStop() {
// console.log("关闭");
this.myEasyCamera.close();
this.$emit("close");
},
// 点击拍照
cameraSnap() {
if (!this.isEnable) {
return;
}
// console.log("拍照");
this.myEasyCamera.snap();
document.getElementsByClassName(
"camera-stack-controls"
)[0].style.display = "none";
this.confirmIsShow = true;
// this.pictureData.pictureSize =
// this.getImageSize(this.pictureData.picture) / 1024 / 1024;
this.isEnable = false;
},
// 点击对号完成
confirm() {
// console.log("完成");
this.isEnable = true;
this.confirmIsShow = false;
this.$emit("setPictureUrl", this.pictureData);
this.$emit("close");
},
// 点击返回重拍
cancel() {
// console.log("重拍");
this.isEnable = true;
this.confirmIsShow = false;
this.myEasyCamera.start();
},
// 获取照片大小
getImageSize(base64Str) {
const indexBase64 = base64Str.indexOf("base64,");
if (indexBase64 < 0) return false;
const str = base64Str.substr(indexBase64 + 6);
return (str.length * 0.75).toFixed(2);
},
flip() {
this.myEasyCamera.switchCamera();
this.isFlip = true;
},
},
};
</script>
<style scoped>
.top {
background-color: black;
height: 50px;
width: 100%;
}
.bottom {
background-color: black;
height: 60px;
width: 100%;
}
.bott {
position: absolute;
background-color: black;
width: 100%;
z-index: 18;
}
.mask {
position: absolute;
top: 50px;
left: 0;
z-index: 18;
height: calc(100% - 200px);
width: 100%;
}
.mask /deep/ img {
height: auto;
}
.mask_control {
position: absolute;
z-index: 18;
left: calc(50% - 40px);
bottom: 40px;
}
.mask_control_close {
position: absolute;
z-index: 18;
right: 20px;
top: 20px;
}
.mask_control_confirm {
position: absolute;
z-index: 18;
right: 40px;
bottom: 60px;
}
.mask_control_return {
position: absolute;
z-index: 18;
left: 40px;
bottom: 60px;
}
.main-camera {
background-color: #000;
}
.fullscreen-camera /deep/ .camera-stack{
transform: scaleX(-1);
}
</style>
视频录制:
<template>
<div class="publish">
<div class="top">
<van-image
class="mask_control_close"
width="20px"
height="20px"
fit="cover"
@click="CloseCamera"
:src="require('@/assets/img/camera/叉叉.png')"
/>
</div>
<div id="myElement" class="videomain">
<video
ref="video"
class="video_class"
autoplay
playsinline
webkit-playsinline="true"
></video>
<!-- <img class="mask" :src="require('@/assets/img/camera/333.png')" alt="" />-->
<!-- <div class="mask_div">
<div class="mask_mb"></div>
<img
class="mask"
:src="require('@/assets/img/camera/333.png')"
alt=""
/>
</div> -->
<van-image
class="mask"
:src="require('@/assets/img/camera/333.png')"
/>
</div>
<div class="bottom">
<div class="time" id="timer">
{{ padNumber(hours) }} : {{ padNumber(minutes) }} :
{{ padNumber(seconds) }}
</div>
<div class="bottom02">
<div
style="width: 40px; height: 40px"
v-show="isRecording && isSure"
></div>
<img
v-show="confirmIsShow"
class="mask_control_return"
width="40px"
height="40px"
fit="cover"
@click="stopCancel"
:src="require('@/assets/img/camera/撤销.png')"
/>
<img
v-if="isRecording && isSure"
class="mask_control"
width="80px"
height="80px"
fit="cover"
@click="startRecording"
:src="require('@/assets/img/camera/拍照.png')"
/>
<img
v-if="!isRecording && isSure"
class="mask_control"
width="80px"
height="80px"
fit="cover"
@click="stopRecording"
:src="require('@/assets/img/camera/拍照 (1).png')"
/>
<van-image
v-show="confirmIsShow"
class="mask_control_confirm"
width="40px"
height="40px"
fit="cover"
@click="downloadVideo"
:src="require('@/assets/img/camera/确定.png')"
/>
<van-image
v-show="isRecording && isAndroid"
class="mask_control_confirm"
width="40px"
height="40px"
fit="cover"
@click="toggleCamera"
:src="require('@/assets/img/camera/翻转镜头.png')"
/>
<div
style="width: 40px; height: 40px"
v-show="!isAndroid && isRecording"
></div>
</div>
</div>
</div>
</template>
<script>
import { Toast } from 'vant';
export default {
data() {
return {
mediaRecorder: null,
recordedChunks: [],
recording: false,
isSure: true,
isRecording: true,
isAndroid: false,
confirmIsShow: false,
hours: 0,
minutes: 0,
seconds: 0,
intervalId: null,
currentFacingMode: "environment", // 'user' for front camera, 'environment' for back camera
};
},
computed: {
elapsedTime() {
return this.startTime
? Math.floor((Date.now() - this.startTime) / 1000)
: 0;
},
hours() {
return Math.floor(this.elapsedTime / 3600);
},
minutes() {
return Math.floor((this.elapsedTime % 3600) / 60);
},
seconds() {
return this.elapsedTime % 60;
},
formattedTime() {
return `${this.pad(this.hours)}:${this.pad(this.minutes)}:${this.pad(
this.seconds
)}`;
},
},
mounted() {
// 判断用户的操作系统
const isAndroid = /android/i.test(navigator.userAgent);
const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
if (isAndroid) {
this.isAndroid = true;
this.currentFacingMode = "user";
console.log("This device is using Android.");
// 在 Android 上执行的逻辑
} else if (isIOS) {
this.isAndroid = true;
console.log("This device is using iOS.");
// 在 iOS 上执行的逻辑
} else {
console.log("This device is using a different operating system.");
// 在其他操作系统上执行的逻辑
}
const screenHeight =
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight;
this.screenHeight = screenHeight - 50 - 180;
console.log("🚀 ~ mounted ~ this.screenHeight:", this.screenHeight);
const myElement = document.getElementById("myElement"); // 替换 'myElement' 为你实际元素的ID
// 设置元素高度
if (myElement) {
myElement.style.height = this.screenHeight + "px"; // 替换 '100px' 为你想要设置的高度
}
this.canvas = document.createElement('canvas')
this.canvas.width = this.$refs.video.videoWidth
this.canvas.height = this.$refs.video.videoHeight
const ctx = this.canvas.getContext('2d')
const self = this
function drawCanvas() {
ctx.clearRect(0, 0, self.canvas.width, self.canvas.height)
ctx.drawImage(self.$refs.video, 0, 0, self.canvas.width, self.canvas.height)
window.requestAnimationFrame(drawCanvas)
}
// 获取视频流并将其绑定到视频元素
navigator.mediaDevices
.getUserMedia({
video: {
facingMode: this.currentFacingMode,
},
})
.then((stream) => {
this.$refs.video.srcObject = stream;
this.$refs.video.play();
drawCanvas()
})
.catch((error) => {
console.error("获取摄像头失败:", error);
});
},
methods: {
startRecording() {
this.recordedChunks = [];
this.isRecording = false;
this.recording = true;
this.startTimer(); //开始计时
// 创建 MediaRecorder 对象,将视频流传入
const isIOS = /iphone|ipad|ipod/.test(navigator.userAgent)
if (isIOS) {
try {
this.mediaRecorder = new MediaRecorder(this.$refs.video.srcObject, { mimeType: 'video/mp4;codecs=avc1,mp4a' });
} catch (e) {
try {
this.mediaRecorder = new MediaRecorder(this.canvas.captureStream(15), { mimeType: 'video/mp4;codecs=avc1,mp4a' });
} catch (e) {
console.error(e)
this.$toast.fail("当前浏览器不支持录屏,请直接上传文件");
}
}
} else {
try {
this.mediaRecorder = new MediaRecorder(this.$refs.video.srcObject, { mimeType: 'video/webm;codecs=h264' });
} catch(e) {
console.error(e)
this.$toast.fail("当前浏览器不支持录屏,请直接上传文件");
}
}
// 监听数据可用事件,将数据块存储到数组
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.recordedChunks.push(event.data);
}
};
this.mediaRecorder.onerror = (e) => {
console.error('error:', e)
}
// 监听录制结束事件
this.mediaRecorder.onstop = () => {
this.recording = false;
};
// 开始录制
this.mediaRecorder.start();
},
stopRecording() {
this.stopTimer();
this.isRecording = true;
this.isSure = false;
if (this.mediaRecorder && this.recording) {
// 停止录制
this.mediaRecorder.stop();
this.confirmIsShow = true;
}
},
stopCancel() {
this.resetTimer();
this.isSure = true;
this.confirmIsShow = false;
},
downloadVideo() {
console.log("seconds", this.seconds);
if (this.minutes == 0 && this.seconds < 5) {
Toast.fail('录制时长最少5秒钟');
return
}
// 将录制的数据块合并为 Blob
const blob = new Blob(this.recordedChunks, { type: "video/webm" });
// 假设 videoBlob 是一个录制的视频的 Blob 对象
const videoFile = new File(this.recordedChunks, "recorded-video.webm", {
type: "video/webm",
});
console.log(
"🚀 ~ file: videoCamera.vue:148 ~ downloadVideo ~ videoFile:",
videoFile
);
// 现在,videoFile 就是一个 File 对象,可以像处理文件一样使用它
console.log("🚀 ~ file: chatbb.vue:66 ~ downloadVideo ~ blob:", blob);
this.$emit("setPictureUrl2", videoFile);
this.$emit("close");
this.$refs.video.srcObject.getTracks().forEach((track) => {
track.stop()
})
// 创建下载链接并触发下载
// const url = URL.createObjectURL(blob);
// const a = document.createElement("a");
// a.href = url;
// a.download = "recorded-video.webm";
// a.click();
// 释放资源
// URL.revokeObjectURL(url);
},
CloseCamera() {
this.stopRecording();
this.$emit("close");
this.$refs.video.srcObject.getTracks().forEach((track) => {
track.stop()
})
// 停止录制
},
// 切换摄像头
toggleCamera() {
console.log(1);
this.currentFacingMode =
this.currentFacingMode === "user" ? "environment" : "user";
this.restartVideo();
},
async restartVideo() {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: this.currentFacingMode,
},
});
this.$refs.video.srcObject = stream;
},
padNumber(num) {
return String(num).padStart(2, "0");
},
startTimer() {
this.intervalId = setInterval(() => {
this.seconds++;
if (this.seconds === 60) {
this.seconds = 0;
this.minutes++;
if (this.minutes === 60) {
this.minutes = 0;
this.hours++;
}
}
}, 1000);
},
stopTimer() {
clearInterval(this.intervalId);
this.intervalId = null;
},
resetTimer() {
this.hours = 0;
this.minutes = 0;
this.seconds = 0;
this.stopTimer();
},
},
beforeDestroy() {
// 清理定时器
this.stopTimer();
},
};
</script>
<style scoped>
.publish {
display: flex;
flex-direction: column;
justify-content: space-around;
/* align-items: center; */
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 100;
background: #000;
}
.top {
background-color: black;
min-height: 50px;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
}
.video_class {
width: 100%;
height: 100%;
object-fit: cover;
z-index: 100;
}
.bottom {
position: relative;
min-height: 180px;
width: 100%;
background-color: #000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 999;
}
.bottom02 {
width: 100%;
background-color: #000;
display: flex;
justify-content: space-around;
align-items: center;
z-index: 999;
}
.mask_div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.mask_mb {
position: absolute;
/* background-color: rgba(0.129, 0.129, 0.125, 0.5); */
width: 100%;
height: 100%;
z-index: 333;
}
.mask {
/* background-color: #fff; */
position: absolute;
top: 0;
left: 0%;
display: flex;
justify-content: center;
max-width: 100%;
width: 100%;
height: 100%;
background-color: transparent; /* 中间区域透明,保持正常 */
}
.mask /deep/ img {
height: 140vw;
width: 100vw;
}
.mask_control_return {
/* position: absolute; */
z-index: 18;
left: 40px;
bottom: 60px;
}
.mask_control_close {
margin-right: 10px;
}
.flip {
background-color: #000;
}
.videomain {
position: relative;
background-color: #000;
flex-shrink: 0;
/* flex: 1; */
/* max-width: 500px; */
}
.time {
color: #fff;
position: absolute;
top: 5px;
left: 50%;
transform: translate(-50%);
z-index: 9999;
}
</style>