feat: 视频播放组件封装

This commit is contained in:
JiangQiming 2023-03-02 20:23:21 +08:00
parent 86ff238df7
commit 8a3acd57ee
5 changed files with 153 additions and 29 deletions

View File

@ -1,6 +1,6 @@
import server from '@/utils/request'; import server from '@/utils/request';
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
export default { export default {
// 列表 // 列表
@ -19,7 +19,7 @@ export default {
// ========== 视频播放 ========== // ========== 视频播放 ==========
// 开始直播 // 开始直播
ptzStart: (deviceId: string, channelId: string, type: string) => ptzStart: (deviceId: string, channelId: string, type: string) =>
`/media/device/${deviceId}/${channelId}/live.${type}?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`, `${BASE_API_PATH}/media/device/${deviceId}/${channelId}/live.${type}?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`,
// 云台控制-停止 // 云台控制-停止
ptzStop: (deviceId: string, channelId: string) => server.post(`/media/device/${deviceId}/${channelId}/_ptz/STOP`), ptzStop: (deviceId: string, channelId: string) => server.post(`/media/device/${deviceId}/${channelId}/_ptz/STOP`),

View File

@ -1,25 +1,36 @@
<!-- 视频播放 --> <!-- 视频播放 -->
<template> <template>
<vue3videoPlay <vue3videoPlay v-bind="options" />
v-bind="options"
poster="https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/ironMan.jpg"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import 'vue3-video-play/dist/style.css'; import 'vue3-video-play/dist/style.css';
import vue3videoPlay from 'vue3-video-play'; import vue3videoPlay from 'vue3-video-play';
const props = defineProps({
src: { type: String, default: '' },
type: { type: String, default: 'mp4' },
});
watch(
() => props.src,
(val: string) => {
options.src = val;
},
);
const options = reactive({ const options = reactive({
...props,
width: '500px', // width: '500px', //
height: '280px', // height: '280px', //
color: '#409eff', // color: '#409eff', //
title: '', // title: '', //
src: 'https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/IronMan.mp4', // // src: props.src,
// type: props.type,
muted: false, // muted: false, //
webFullScreen: false, webFullScreen: false,
speedRate: ['0.75', '1.0', '1.25', '1.5', '2.0'], // speedRate: ['0.75', '1.0', '1.25', '1.5', '2.0'], //
autoPlay: false, // autoPlay: true, //
loop: false, // loop: false, //
mirror: false, // mirror: false, //
ligthOff: false, // ligthOff: false, //
@ -37,5 +48,3 @@ const options = reactive({
], //, ], //,
}); });
</script> </script>
<style lang="less" scoped></style>

View File

@ -2,16 +2,32 @@
<template> <template>
<div class="live-player-tools"> <div class="live-player-tools">
<div class="direction"> <div class="direction">
<div class="direction-item up"> <div
class="direction-item up"
@mousedown="emit('onMouseDown', 'UP')"
@mouseup="emit('onMouseUp', 'UP')"
>
<AIcon class="direction-icon" type="CaretUpOutlined" /> <AIcon class="direction-icon" type="CaretUpOutlined" />
</div> </div>
<div class="direction-item right"> <div
class="direction-item right"
@mousedown="emit('onMouseDown', 'RIGHT')"
@mouseup="emit('onMouseUp', 'RIGHT')"
>
<AIcon class="direction-icon" type="CaretRightOutlined" /> <AIcon class="direction-icon" type="CaretRightOutlined" />
</div> </div>
<div class="direction-item left"> <div
class="direction-item left"
@mousedown="emit('onMouseDown', 'LEFT')"
@mouseup="emit('onMouseUp', 'LEFT')"
>
<AIcon class="direction-icon" type="CaretLeftOutlined" /> <AIcon class="direction-icon" type="CaretLeftOutlined" />
</div> </div>
<div class="direction-item down"> <div
class="direction-item down"
@mousedown="emit('onMouseDown', 'DOWN')"
@mouseup="emit('onMouseUp', 'DOWN')"
>
<AIcon class="direction-icon" type="CaretDownOutlined" /> <AIcon class="direction-icon" type="CaretDownOutlined" />
</div> </div>
<div class="direction-audio"> <div class="direction-audio">
@ -19,17 +35,31 @@
</div> </div>
</div> </div>
<div class="zoom"> <div class="zoom">
<div class="zoom-item zoom-in"> <div
class="zoom-item zoom-in"
@mousedown="emit('onMouseDown', 'ZOOM_IN')"
@mouseup="emit('onMouseUp', 'ZOOM_IN')"
>
<AIcon type="PlusOutlined" /> <AIcon type="PlusOutlined" />
</div> </div>
<div class="zoom-item zoom-out"> <div
class="zoom-item zoom-out"
@mousedown="emit('onMouseDown', 'ZOOM_OUT')"
@mouseup="emit('onMouseUp', 'ZOOM_OUT')"
>
<AIcon type="MinusOutlined" /> <AIcon type="MinusOutlined" />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
type Emits = {
(e: 'onMouseDown', type: string): void;
(e: 'onMouseUp', type: string): void;
};
const emit = defineEmits<Emits>();
</script>
<style lang="less" scoped> <style lang="less" scoped>
@import './mediaTool.less'; @import './mediaTool.less';

View File

@ -6,13 +6,14 @@
cancelText="取消" cancelText="取消"
okText="确定" okText="确定"
width="800px" width="800px"
:maskClosable="false"
@ok="_vis = false" @ok="_vis = false"
@cancel="_vis = false" @cancel="_vis = false"
> >
<div class="media-live"> <div class="media-live">
<div class="media-live-video"> <div class="media-live-video">
<div class="media-tool"> <div class="media-tool">
<div class="tool-item"> <div class="tool-item" @click.stop="handleRecord">
{{ {{
isRecord === 0 isRecord === 0
? '开始录像' ? '开始录像'
@ -22,17 +23,24 @@
}} }}
</div> </div>
<div class="tool-item">刷新</div> <div class="tool-item">刷新</div>
<div class="tool-item">重置</div> <div class="tool-item" @click.stop="handleReset">重置</div>
</div> </div>
<LivePlayer :url="url" /> <LivePlayer :src="src" :type="mediaType" />
</div> </div>
<MediaTool /> <MediaTool
@onMouseDown="handleMouseDown"
@onMouseUp="handleMouseUp"
/>
</div> </div>
<div class="media-live-tool"> <div class="media-live-tool">
<a-radio-group v-model:value="mediaType" button-style="solid"> <a-radio-group
v-model:value="mediaType"
button-style="solid"
@change="mediaStart"
>
<a-radio-button value="mp4">MP4</a-radio-button> <a-radio-button value="mp4">MP4</a-radio-button>
<a-radio-button value="flv">FLV</a-radio-button> <a-radio-button value="flv">FLV</a-radio-button>
<a-radio-button value="hls">HLS</a-radio-button> <a-radio-button value="m3u8">HLS</a-radio-button>
</a-radio-group> </a-radio-group>
</div> </div>
</a-modal> </a-modal>
@ -42,6 +50,7 @@
import { PropType } from 'vue'; import { PropType } from 'vue';
import LivePlayer from '@/components/Player/index.vue'; import LivePlayer from '@/components/Player/index.vue';
import MediaTool from '@/components/Player/mediaTool.vue'; import MediaTool from '@/components/Player/mediaTool.vue';
import channelApi from '@/api/media/channel';
type Emits = { type Emits = {
(e: 'update:visible', data: boolean): void; (e: 'update:visible', data: boolean): void;
@ -61,14 +70,89 @@ const _vis = computed({
set: (val) => emit('update:visible', val), set: (val) => emit('update:visible', val),
}); });
//
const src = ref('');
//
const mediaType = ref('mp4');
/**
* 媒体开始播放
*/
const mediaStart = () => {
src.value = channelApi.ptzStart(
props.data.deviceId,
props.data.channelId,
mediaType.value,
);
};
//
const isRecord = ref(0); // 0 1 2
/**
* 查询录像状态
*/
const getIsRecord = async () => {
const { result } = await channelApi.ptzIsRecord(
props.data.deviceId,
props.data.channelId,
);
isRecord.value = result ? 2 : 0;
};
/**
* 点击录像/停止录像
*/
const handleRecord = async () => {
if (isRecord.value === 0) {
isRecord.value = 1;
const res = await channelApi.recordStart(
props.data.deviceId,
props.data.channelId,
{ local: false },
);
if (res.success) {
isRecord.value = 2;
} else {
isRecord.value = 0;
}
} else if (isRecord.value === 2) {
const res = await channelApi.recordStop(
props.data.deviceId,
props.data.channelId,
{ local: false },
);
if (res.success) {
isRecord.value = 0;
}
}
};
/**
* 重置
*/
const handleReset = async () => {
channelApi.mediaStop(props.data.deviceId, props.data.channelId);
};
/**
* 点击控制按钮
* @param type 控制类型
*/
const handleMouseDown = (type: string) => {
channelApi.ptzTool(props.data.deviceId, props.data.channelId, type);
};
const handleMouseUp = () => {
channelApi.ptzStop(props.data.deviceId, props.data.channelId);
};
watch( watch(
() => _vis.value, () => _vis.value,
(val: boolean) => {}, (val: boolean) => {
if (val) {
mediaStart();
getIsRecord();
}
},
); );
const isRecord = ref(0);
const url = ref('');
const mediaType = ref('mp4');
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import './index.less'; @import './index.less';

View File

@ -83,7 +83,7 @@
:channelData="channelData" :channelData="channelData"
@submit="listRef.reload()" @submit="listRef.reload()"
/> />
<Live v-model:visible="playerVis" /> <Live v-model:visible="playerVis" :data="channelData" />
</page-container> </page-container>
</template> </template>
@ -205,6 +205,7 @@ const getActions = (
}, },
icon: 'VideoCameraOutlined', icon: 'VideoCameraOutlined',
onClick: () => { onClick: () => {
channelData.value = cloneDeep(data);
playerVis.value = true; playerVis.value = true;
}, },
}, },