123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- <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 '@simaq/core';
- 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>
|