123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- <template>
- <ui-group-option class="sign-guide">
- <div class="info">
- <div class="guide-cover">
- <img :src="getResource(getFileUrl(guide.cover))" />
- <ui-icon
- type="preview"
- class="icon"
- ctrl
- @click="playSceneGuide(paths, undefined, true)"
- v-if="paths.length"
- />
- </div>
- <div>
- <p>{{ guide.title }}</p>
- </div>
- </div>
- <div class="actions" v-if="edit">
- <ui-more
- :options="menus"
- style="margin-left: 20px"
- @click="(action: keyof typeof actions) => actions[action]()"
- />
- </div>
- </ui-group-option>
- <Teleport to="body">
- <div class="edit-add-type" v-if="downloading">
- <div class="edit-hot-item">
- <h3 class="edit-title">设置视频参数</h3>
- <ui-input
- require
- class="input"
- :options="[
- { value: '1080p', label: '1080p' },
- { value: '2k', label: '2k' },
- { value: '4k', label: '4k' },
- ]"
- width="100%"
- placeholder="设置分辨率"
- type="select"
- v-model="videoConfig.resolution"
- maxlength="15"
- />
- <ui-input
- require
- class="input"
- :options="[
- { value: 30, label: '30' },
- { value: 60, label: '60' },
- { value: 90, label: '90' },
- ]"
- width="100%"
- placeholder="设置帧率"
- type="select"
- v-model="videoConfig.frameRate"
- maxlength="15"
- />
- <div>
- <span>预计渲染时间:</span>
- <span>{{ time }}s</span>
- </div>
- <div class="edit-hot">
- <a @click="() => (downloading = false)">
- <ui-icon type="nav-edit" />
- 确定
- </a>
- </div>
- </div>
- </div>
- </Teleport>
- </template>
- <script setup lang="ts">
- import { Guide, getGuidePaths } from "@/store";
- import { getFileUrl, saveAs } from "@/utils";
- import { getResource } from "@/env";
- import { computed, watchEffect, nextTick, ref } from "vue";
- import { playSceneGuide, isScenePlayIng, pauseSceneGuide } from "@/sdk";
- import { VideoRecorder } from "@simaq/core";
- const props = withDefaults(defineProps<{ guide: Guide; edit?: boolean }>(), {
- edit: true,
- });
- const emit = defineEmits<{
- (e: "delete"): void;
- (e: "play"): void;
- (e: "edit"): void;
- }>();
- const menus = [
- { label: "编辑", value: "edit" },
- { label: "下载", value: "download" },
- { label: "删除", value: "delete" },
- ];
- const downloading = ref(false);
- const videoConfig = ref({
- resolution: "1080p",
- frameRate: 60,
- });
- const actions = {
- edit: () => emit("edit"),
- delete: () => emit("delete"),
- download: async () => {
- downloading.value = true;
- await new Promise<void>((resolve) => {
- const stop = watchEffect(() => {
- if (downloading.value === false) {
- stop();
- resolve();
- }
- });
- });
- const config: any = {
- // uploadUrl: '',
- // resolution: '1080p' | '2k' | '4k';
- // frameRate: [30, 60, 90]
- // autoDownload: false,
- // systemAudio: true,
- // debug: true,
- resolution: videoConfig.value.resolution || "1080p",
- autoDownload: false,
- platform: "canvas",
- config: {
- frameRate: videoConfig.value.frameRate || 60,
- canvasId: ".scene-canvas > canvas",
- },
- disbaledAudio: false,
- systemAudio: false,
- debug: false,
- };
- const videoRecorder = new VideoRecorder(config);
- videoRecorder.startRecord();
- let stopWatch: () => void;
- const stopRecord = () => {
- stopWatch && stopWatch();
- pauseSceneGuide();
- };
- videoRecorder.on("record", (blob) => {
- saveAs(
- new File([blob], "录屏.mp4", { type: "video/mp4; codecs=h264" }),
- props.guide.title + ".mp4"
- );
- });
- videoRecorder.off("*");
- videoRecorder.on("startRecord", () => {
- playSceneGuide(paths.value, undefined, true);
- stopWatch = watchEffect(() => {
- if (!isScenePlayIng.value) {
- videoRecorder.endRecord();
- nextTick(stopWatch);
- }
- });
- });
- videoRecorder.on("cancelRecord", stopRecord);
- videoRecorder.on("endRecord", stopRecord);
- },
- };
- const paths = computed(() => getGuidePaths(props.guide));
- const time = computed(() => paths.value.reduceRight((t, c) => t + c.time, 0));
- </script>
- <style lang="scss" scoped>
- .sign-guide {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20px 0;
- border-bottom: 1px solid var(--colors-border-color);
- &:first-child {
- border-top: 1px solid var(--colors-border-color);
- }
- .info {
- flex: 1;
- display: flex;
- align-items: center;
- .guide-cover {
- position: relative;
- &::after {
- content: "";
- position: absolute;
- inset: 0;
- background: rgba(0, 0, 0, 0.2);
- }
- .icon {
- position: absolute;
- z-index: 1;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- font-size: 16px;
- }
- img {
- width: 48px;
- height: 48px;
- object-fit: cover;
- border-radius: 4px;
- overflow: hidden;
- background-color: rgba(255, 255, 255, 0.6);
- display: block;
- }
- }
- div {
- margin-left: 10px;
- p {
- color: #fff;
- font-size: 14px;
- margin-bottom: 6px;
- }
- }
- }
- .actions {
- flex: none;
- }
- }
- .edit-add-type {
- color: #fff;
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.3);
- backdrop-filter: blur(4px);
- z-index: 2000;
- padding: 20px;
- overflow-y: auto;
- .edit-hot-item {
- margin: 100px auto 20px;
- width: 400px;
- padding: 20px;
- background: rgba(27, 27, 28, 0.8);
- box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
- border-radius: 4px;
- .input {
- margin-bottom: 10px;
- }
- }
- }
- .edit-hot {
- margin-top: 20px;
- text-align: right;
- span {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.6);
- cursor: pointer;
- }
- }
- .edit-close {
- position: absolute;
- cursor: pointer;
- top: calc((100% - 18px) / 2);
- right: 0;
- transform: translateY(-50%);
- }
- .edit-title {
- padding-bottom: 18px;
- margin-bottom: 18px;
- position: relative;
- color: #fff;
- &::after {
- content: "";
- position: absolute;
- left: -20px;
- right: -20px;
- height: 1px;
- bottom: 0;
- background-color: rgba(255, 255, 255, 0.16);
- }
- }
- .edit-title {
- padding-bottom: 18px;
- margin-bottom: 18px;
- position: relative;
- &::after {
- content: "";
- position: absolute;
- left: -20px;
- right: -20px;
- height: 1px;
- bottom: 0;
- background-color: rgba(255, 255, 255, 0.16);
- }
- }
- </style>
|