|
@@ -1,26 +1,43 @@
|
|
|
<script setup lang="ts">
|
|
|
-import { withResolvers } from '@/tools';
|
|
|
-import { getMediaStream } from '@/tools/camera.tool';
|
|
|
-import { tryOnMounted, tryOnUnmounted, useEventListener } from '@vueuse/core';
|
|
|
+import { withResolvers } from '@/tools';
|
|
|
+import { getMediaStream } from '@/tools/camera.tool';
|
|
|
+import { tryOnMounted, tryOnUnmounted, useElementSize, useEventListener } from '@vueuse/core';
|
|
|
|
|
|
|
|
|
-const { constraints = {} } = defineProps<{ constraints?: MediaTrackConstraints }>();
|
|
|
+let _stream: MediaStream;
|
|
|
+
|
|
|
+
|
|
|
+const { constraints = {} } = defineProps<{ constraints?: MediaTrackConstraints, multiple?: number; }>();
|
|
|
const width = defineModel('width', { default: 0 });
|
|
|
const height = defineModel('height', { default: 0 });
|
|
|
+const multiple = defineModel('multiple', { default: 1 });
|
|
|
+
|
|
|
+const offsetX = ref(0);
|
|
|
+const offsetY = ref(0);
|
|
|
+
|
|
|
+watch([ width, height, multiple ], ([ w, h, m ]) => {
|
|
|
+ applyConstraints({ width: w * m, height: h * m });
|
|
|
+});
|
|
|
+
|
|
|
|
|
|
const styleValue = computed(() => {
|
|
|
return width.value && height.value
|
|
|
- ? {
|
|
|
- width: `${ width.value }px`,
|
|
|
- height: `${ height.value }px`,
|
|
|
- }
|
|
|
+ ? { width: `${ width.value }px`, height: `${ height.value }px` }
|
|
|
: void 0;
|
|
|
});
|
|
|
|
|
|
+const containerStyleValue = reactive({
|
|
|
+ width: `${ width.value }px`,
|
|
|
+ height: `${ height.value }px`,
|
|
|
+});
|
|
|
+
|
|
|
const container = useTemplateRef<HTMLElement>('camera-container');
|
|
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
|
|
|
|
|
+/* 根容器 宽高 */
|
|
|
+const root = useElementSize(() => container.value?.parentElement);
|
|
|
+
|
|
|
const getVideoTrack = () => {
|
|
|
return videoRef.value?.srcObject instanceof MediaStream
|
|
|
? videoRef.value.srcObject.getVideoTracks()[ 0 ]
|
|
@@ -36,52 +53,81 @@ useEventListener(videoRef, 'canplay', (event) => {
|
|
|
const settings = track.getSettings();
|
|
|
const { width: w = 1, height: h = 1, aspectRatio = w / h } = settings;
|
|
|
const reverse = aspectRatio > 1;
|
|
|
- width.value = reverse ? h : w;
|
|
|
- height.value = reverse ? w : h;
|
|
|
- video.setAttribute('width', `${ width.value }`);
|
|
|
- video.setAttribute('height', `${ height.value }`);
|
|
|
- canvas!.width = width.value;
|
|
|
- canvas!.height = height.value;
|
|
|
+ const _width = reverse ? h : w;
|
|
|
+ const _height = reverse ? w : h;
|
|
|
+ video.setAttribute('width', `${ _width }`);
|
|
|
+ video.setAttribute('height', `${ _height }`);
|
|
|
+
|
|
|
+ containerStyleValue.width = `${ _width }px`;
|
|
|
+ containerStyleValue.height = `${ _height }px`;
|
|
|
+
|
|
|
+ offsetX.value = (
|
|
|
+ _width - width.value
|
|
|
+ ) / 2;
|
|
|
+ offsetY.value = (
|
|
|
+ _height - height.value
|
|
|
+ ) / 2;
|
|
|
|
|
|
video.play().then(
|
|
|
() => {},
|
|
|
// (_) => (error.value = _)
|
|
|
);
|
|
|
console.group('[log] camera:', '流媒体可播放');
|
|
|
- console.log(`width=${ width.value }, height=${ height.value }`);
|
|
|
+ console.log(`width=${ _width }, height=${ _height }`);
|
|
|
console.log(`浏览器支持: 可缩放 ${ !!(
|
|
|
<any> navigator.mediaDevices.getSupportedConstraints()
|
|
|
).zoom }`);
|
|
|
console.log(`摄像头支持: 可缩放 ${ !!(
|
|
|
<any> track.getCapabilities()
|
|
|
)[ 'zoom' ] }`);
|
|
|
+ console.log(`当前设置缩放: ${ multiple.value }`);
|
|
|
+ console.log(`当前实际缩放: (${ _width / width.value }, ${ _height / height.value })`);
|
|
|
+ console.log(`图象偏移距离: (${ offsetX.value }, ${ offsetY.value })`);
|
|
|
console.groupEnd();
|
|
|
});
|
|
|
|
|
|
tryOnMounted(() => {
|
|
|
- const style = getComputedStyle(container.value!);
|
|
|
- width.value ??= Number.parseInt(style.width);
|
|
|
- height.value ??= Number.parseInt(style.height);
|
|
|
- init(container.value!, { width: width.value, height: height.value, ...constraints });
|
|
|
-});
|
|
|
+ const _container = container.value!;
|
|
|
+ canvasRef.value = container.value!.querySelector('canvas');
|
|
|
+ videoRef.value = container.value!.querySelector('video');
|
|
|
|
|
|
-tryOnUnmounted(() => {
|
|
|
- getVideoTrack().stop();
|
|
|
+ const style = getComputedStyle(_container);
|
|
|
+ const _width = width.value || Number.parseInt(style.width);
|
|
|
+ const _height = height.value || Number.parseInt(style.height);
|
|
|
+ const _multiple = multiple.value ?? 1;
|
|
|
|
|
|
- const video = videoRef.value;
|
|
|
- if ( video ) {
|
|
|
- video.pause();
|
|
|
- video.srcObject = null;
|
|
|
- }
|
|
|
+ canvasRef.value!.width = _width;
|
|
|
+ canvasRef.value!.height = _height;
|
|
|
+
|
|
|
+ applyConstraints({ ...constraints, width: _width * _multiple, height: _height * _multiple });
|
|
|
+});
|
|
|
|
|
|
+tryOnUnmounted(() => {
|
|
|
+ destroy();
|
|
|
videoRef.value = null;
|
|
|
canvasRef.value = null;
|
|
|
});
|
|
|
|
|
|
-async function init(container: HTMLElement, constraints: MediaTrackConstraints) {
|
|
|
- canvasRef.value = container.querySelector('canvas');
|
|
|
- videoRef.value = container.querySelector('video');
|
|
|
- videoRef.value!.srcObject = await getMediaStream(<any> constraints);
|
|
|
+async function applyConstraints(constraints: Partial<MediaTrackConstraints>) {
|
|
|
+ const { width, height, ..._constraints } = constraints;
|
|
|
+ // @ts-ignore
|
|
|
+ if ( !!width && !!height ) {
|
|
|
+ destroy();
|
|
|
+ _stream = await getMediaStream(<any> { ..._constraints, width, height, facingMode: 'user' });
|
|
|
+ videoRef.value!.srcObject = _stream;
|
|
|
+ } else {
|
|
|
+ await _stream.getVideoTracks()[ 0 ]?.applyConstraints(constraints);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function destroy() {
|
|
|
+ const tracks = _stream?.getTracks() ?? [];
|
|
|
+ for ( const track of tracks ) track.stop();
|
|
|
+ _stream = void 0 as any;
|
|
|
+ if ( videoRef.value ) {
|
|
|
+ videoRef.value.pause();
|
|
|
+ videoRef.value.srcObject = null as any;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
|
|
@@ -92,7 +138,8 @@ const handle = async (promise: Promise<boolean>) => {
|
|
|
if ( video.paused ) await video.play();
|
|
|
|
|
|
const context = canvas.getContext('2d')!;
|
|
|
- context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
+ context.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
+ context.drawImage(video, offsetX.value, offsetY.value, video.width, video.height, 0, 0, canvas.width, canvas.height);
|
|
|
video.pause();
|
|
|
|
|
|
promise.then(value => video.paused && value ? video.play() : void 0);
|
|
@@ -119,20 +166,22 @@ defineExpose({
|
|
|
});
|
|
|
</script>
|
|
|
<template>
|
|
|
- <div class="camera-container size-full" ref="camera-container" :style="styleValue">
|
|
|
+ <div class="camera-container size-full" ref="camera-container" :style="containerStyleValue">
|
|
|
<canvas style="display: none;"></canvas>
|
|
|
<video></video>
|
|
|
- <slot></slot>
|
|
|
+ <slot :style="styleValue" :offsetX="offsetX" :offsetY="offsetY"></slot>
|
|
|
</div>
|
|
|
</template>
|
|
|
<style scoped lang="scss">
|
|
|
.camera-container {
|
|
|
- position: relative;
|
|
|
+ position: absolute;
|
|
|
+ z-index: 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
|
|
|
> * {
|
|
|
position: absolute;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
}
|
|
|
|
|
|
video {
|