123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- <template>
- <teleport :to="appEl" v-if="appEl">
- <!-- <div class="countdown strengthen" v-if="!custom.showBottomBar && countdown">
- <p class="title"><span>{{countdown}}</span>秒后开始录制</p>
- <p>按ESC可暂停录制</p>
- </div> -->
- <ui-editor-toolbar :toolbar="custom.showBottomBar" class="shot-ctrl">
- <ui-button type="submit" class="btn" @click="close">取消</ui-button>
- <ui-button
- type="primary"
- class="btn"
- @click="complete"
- :class="{ disabled: blobs.length === 0 }"
- >合并视频</ui-button
- >
- <div class="other" :style="{ bottom: barHeight }">
- <ui-icon
- class="icon"
- type="video1"
- ctrl
- @click="start"
- tip="继续录制"
- tipV="top"
- />
- </div>
- <div class="video-list" v-if="videoList.length">
- <div class="layout" :style="{ width: `${videoList.length * 130}px` }">
- <div v-for="video in videoList" :key="video.cover" class="cover">
- <img :src="video.cover" />
- <ui-icon
- type="preview"
- ctrl
- class="preview"
- @click="palyUrl = video.origin"
- />
- </div>
- </div>
- </div>
- </ui-editor-toolbar>
- <Preview
- v-if="palyUrl"
- :items="[{ type: MediaType.video, url: palyUrl }]"
- @close="palyUrl = null"
- />
- <ShotImiate v-if="!custom.showBottomBar && !countdown" />
- </teleport>
- </template>
- <script lang="ts">
- import {
- ref,
- defineComponent,
- onUnmounted,
- watch,
- shallowReactive,
- PropType,
- computed,
- watchEffect,
- } from "vue";
- import { VideoRecorder } from "simaqcore";
- import { sdk } from "@/sdk";
- import { getVideoCover, togetherCallback } from "@/utils";
- import { MediaType, Preview } from "@/components/static-preview/index.vue";
- import { Record, getRecordFragmentBlobs } from "@/store";
- import ShotImiate from "./shot-imitate.vue";
- import {
- getResource,
- showRightCtrlPanoStack,
- showRightPanoStack,
- showBottomBarStack,
- custom,
- bottomBarHeightStack,
- showHeadBarStack,
- showLeftPanoStack,
- } from "@/env";
- import { appEl } from "@/store";
- import { useViewStack } from "@/hook";
- import { currentModel } from "@/model";
- import { Message } from "bill/expose-common";
- export default defineComponent({
- props: {
- record: {
- type: Object as PropType<Record>,
- required: true,
- },
- },
- emits: {
- append: (blobs: Blob[]) => true,
- updateCover: (cover: string) => true,
- close: () => true,
- preview: () => true,
- deleteRecord: () => true,
- },
- setup(props, { emit }) {
- const config: any = {
- uploadUrl: "",
- resolution: "4k",
- debug: false,
- };
- const videoRecorder = new VideoRecorder(config);
- const showLeftPano = ref(false);
- const showBottomBar = ref(false);
- const MAX_SIZE = 2 * 1024 * 1024 * 1024;
- const MAX_TIME = 30 * 60 * 1000;
- type VideoItem = { origin: Blob | string; cover: string };
- const countdown = ref(0);
- let interval: NodeJS.Timer;
- let recordIng = ref(false);
- const start = () => {
- if (size.value > MAX_SIZE || pauseTime.value < 2000) {
- return Message.warning("已超出限制大小无法继续录制,可保存后继续录制!");
- }
- showBottomBar.value = false;
- countdown.value = 2;
- const timeiffe = () => {
- if (--countdown.value <= 0) {
- clearInterval(interval);
- videoRecorder.startRecord();
- recordIng.value = true;
- } else {
- interval = setTimeout(timeiffe, 300);
- }
- };
- timeiffe();
- // interval = setInterval(() => {
- // if (--countdown.value === 0) {
- // clearInterval(interval)
- // videoRecorder.startRecord()
- // recordIng = true
- // }
- // }, 1000)
- };
- const pause = () => {
- if (countdown.value === 0 && recordIng.value) {
- videoRecorder.endRecord();
- recordIng.value = false;
- }
- countdown.value = 0;
- showBottomBar.value = true;
- clearInterval(interval);
- };
- watch(recordIng, (_n, _o, onCleanup) => {
- if (recordIng.value) {
- const timeout = setTimeout(() => videoRecorder.endRecord(), pauseTime.value);
- onCleanup(() => clearTimeout(timeout));
- }
- });
- const blobs: File[] = shallowReactive([]);
- const size = computed(() => {
- console.log(videoList);
- return videoList.reduce(
- (t, f) => (typeof f.origin === "string" ? t : t + f.origin.size),
- 0
- );
- });
- const pauseTime = computed(() => (MAX_TIME / MAX_SIZE) * (MAX_SIZE - size.value));
- videoRecorder.off("*");
- videoRecorder.on("record", (blob) => {
- if (recordIng.value) {
- blobs.push(new File([blob], "录屏.mp4", { type: "video/mp4; codecs=h264" }));
- }
- });
- videoRecorder.on("cancelRecord", pause);
- videoRecorder.on("endRecord", pause);
- const palyUrl = ref<string | Blob | null>(null);
- const videoList: VideoItem[] = shallowReactive([]);
- let initial = false;
- watch(
- [blobs, props],
- async () => {
- const existsVideos = [];
- if (props.record.url) {
- existsVideos.push(getResource(props.record.url));
- }
- const fragmentBlobs = getRecordFragmentBlobs(props.record);
- existsVideos.push(...fragmentBlobs, ...blobs);
- for (const blob of existsVideos) {
- if (videoList.some((item) => item.origin === blob)) {
- continue;
- }
- const cover = await getVideoCover(blob, 3, 120, 80);
- videoList.push({ origin: blob, cover });
- }
- for (let i = 0; i < videoList.length; i++) {
- if (!existsVideos.some((blob) => videoList[i].origin === blob)) {
- videoList.splice(i--, 1);
- }
- }
- if (!props.record.cover && videoList.length) {
- emit("updateCover", videoList[0].cover);
- }
- if (!initial) {
- initial = true;
- start();
- }
- },
- { immediate: true }
- );
- const upHandler = (ev: KeyboardEvent) =>
- ev.code === `Escape` && videoRecorder.endRecord();
- document.body.addEventListener("keyup", upHandler, { capture: true });
- const complete = () => {
- emit("append", blobs);
- close();
- };
- const close = () => {
- pause();
- emit("close");
- };
- const barHeight = computed(() => (videoList.length ? "180px" : "60px"));
- onUnmounted(() => {
- document.body.removeEventListener("keyup", upHandler, { capture: true });
- });
- watch(currentModel, () => {
- if (currentModel.value) {
- showLeftPano.value = false;
- }
- });
- useViewStack(() => {
- return togetherCallback([
- showHeadBarStack.push(ref(false)),
- showRightCtrlPanoStack.push(ref(false)),
- showRightPanoStack.push(ref(false)),
- showBottomBarStack.push(showBottomBar),
- bottomBarHeightStack.push(barHeight),
- showLeftPanoStack.push(showLeftPano),
- close,
- () => {
- console.log("pop", showBottomBarStack.current);
- },
- ]);
- });
- return {
- MediaType,
- complete,
- pause,
- close,
- start,
- barHeight,
- el: sdk.layout,
- blobs,
- countdown,
- custom,
- videoList,
- palyUrl,
- appEl,
- };
- },
- components: {
- Preview,
- ShotImiate,
- },
- });
- </script>
- <style lang="scss" src="./style.scss" scoped></style>
|