Commit 9208f384 by haojie

直播页面修改为两个窗口

parent 43755b23
......@@ -23,6 +23,8 @@
"import/extensions": [".js", ".jsx", ".ts", ".tsx"]
},
"rules": {
// 禁止 || true ,关闭
"no-constant-condition": 0,
"vue/no-setup-props-destructure": 0,
"vue/no-v-model-argument": 0,
"vue/no-v-for-template-key": 0,
......
<template>
<div>
<div class="add-video-play">
<!-- <button @click="onPlay">开始播放</button> -->
<div v-show="showFirstVideo">
<video ref="videoFirst" class="video-default" loop :src="video1" @canplay="canplay"></video>
</div>
<div v-show="!showFirstVideo">
<video ref="videoSecond" class="video-default2" :src="video2" @ended="onVideoEnded" @canplay="canplay2"></video>
</div>
<video
v-show="showFirstVideo"
:volume="firstVideoVolume"
ref="videoFirst"
class="video-default"
loop
:src="video1"
@canplay="canplay"
></video>
<video
v-show="!showFirstVideo"
ref="videoSecond"
:volume="secondVideoVolume"
class="video-default"
:src="video2"
@ended="onVideoEnded"
@canplay="canplay2"
></video>
<ConfirmDialog v-model="confirmVisible" :closeOnOverlayClick="false" :footer="footerStatus" @confirm="confirm">
<template #body>
<template v-if="loading">
......@@ -23,10 +35,12 @@
<script setup lang="ts">
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useStore } from 'vuex';
import Loading from '@/components/Loading/FirstCircle.vue';
import { onBeforeRouteLeave } from 'vue-router';
import { injectWindow } from '@/utils/pyqt';
import { show_message } from '@/utils/tool';
const props = defineProps<{
video1: string;
......@@ -55,6 +69,13 @@ const videoSecondFirstPlay = ref(true);
const videoFirst = ref<HTMLVideoElement>();
const videoSecond = ref<HTMLVideoElement>();
// 初始音量
const initVolume = 1;
// 第一个视频的音量
const firstVideoVolume = ref(initVolume);
// 第二个视频的音量
const secondVideoVolume = ref(initVolume);
let interval = null;
const total = ref(0);
......@@ -164,6 +185,31 @@ const updateTime = () => {
emit('update:progress', Math.floor((videoFirst.value.currentTime / total.value) * 100));
}
};
// 减小正在播放的视频音量
const lowerVideoVolume = () => {
console.error('减小音量');
let num = 0.5;
if (showFirstVideo.value) {
// 第一个正在播放
firstVideoVolume.value = num;
} else {
secondVideoVolume.value = num;
}
};
// 恢复音量,两个视频标签都恢复
const videoVolumeRestoration = () => {
console.error('恢复音量');
firstVideoVolume.value = initVolume;
secondVideoVolume.value = initVolume;
};
onMounted(() => {
// 全局function
injectWindow('lowerVideoVolume', lowerVideoVolume);
injectWindow('videoVolumeRestoration', videoVolumeRestoration);
console.log(window.pyEvent);
});
onBeforeUnmount(() => {
closeInterval();
......@@ -171,10 +217,12 @@ onBeforeUnmount(() => {
</script>
<style lang="less">
.video-default {
width: 100%;
}
.video-default2 {
.add-video-play {
width: 100%;
height: 100%;
.video-default {
width: 100%;
height: 100%;
}
}
</style>
......@@ -67,6 +67,20 @@ export const getRoutes = () => {
component: () => import('@/pages/startLive/index.vue'),
meta: { title: 'snowhome' },
},
// 只有视频的页面
{
path: routerConfig.onlyVideoLive.path,
name: routerConfig.onlyVideoLive.name,
component: () => import('@/pages/OnlyVideoLive/index.vue'),
meta: { title: 'snowhome', header: false, navbar: false },
},
// 只有人工回复的页面
{
path: routerConfig.interactiveResponse.path,
name: routerConfig.interactiveResponse.name,
component: () => import('@/pages/InteractiveResponse/index.vue'),
meta: { title: 'snowhome' },
},
{
path: routerConfig.login.path,
name: routerConfig.login.name,
......
......@@ -10,7 +10,7 @@
<keep-alive>
<component :is="Component" :key="getKey()" v-if="route.meta.keepAlive" />
</keep-alive>
<component :is="Component" :key="getKey()" v-if="!route.meta.keepAlive" />
<component :is="Component" v-if="!route.meta.keepAlive" />
</router-view>
</template>
......
......@@ -18,7 +18,25 @@ export default defineComponent({
onBeforeRouteUpdate((to) => {
footer.value = false;
});
// 是否显示navbar
const showNavbar = () => {
if (route.meta.header === false) {
return false;
}
return getUserCookie() ? <ToolBar /> : '';
};
// 是否显示header
const showHeader = () => {
if (route.meta.header === false) {
return false;
}
return true;
};
return {
showNavbar,
showHeader,
footer,
};
},
......@@ -37,8 +55,8 @@ export default defineComponent({
return (
<div>
<TLayout key="no-side">
<Header />
{getUserCookie() ? <ToolBar /> : ''}
{this.showHeader() ? <Header /> : ''}
{this.showNavbar()}
<TContent class="s-layout-content narrow-scrollbar" id="layout-scroll">
<Content />
{/* {this.footer ? (
......
<template>
<div class="start-live-audio-box">
<div class="start-live-audio-content">
<div class="label">音频脚本</div>
<div class="play-audio-box">
<div class="line">
<PlayAudioSvg></PlayAudioSvg>
<Button theme="opacity" style="color: #fff" v-if="true" @click="showTextarea">查看文字脚本</Button>
</div>
<div class="custom-video-progress">
<CustomProgress :value="modelValue"></CustomProgress>
</div>
</div>
<div v-if="textareaStatus">
<CustomTextarea :disabled="true" class="reset-live-audio-textarea" v-model="textareaValue"></CustomTextarea>
</div>
</div>
<div class="start-live-audio-footer">
<div class="live-status">
<div class="live-icon">
<LiveSvg></LiveSvg>
</div>
<span>
<template v-if="route.query.is_live == '1'"> 直播中 </template>
<template v-else> 未开播 </template>
</span>
</div>
<div class="stop" v-if="route.query.is_live == '1'">
<!-- <span class="start-time">00:52:20</span> -->
<Button theme="opacity" @click="closeLive">关闭直播</Button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import CustomProgress from '@/components/Progress.vue';
import CustomTextarea from '@/components/textarea.vue';
import Button from '@/components/Button.vue';
import LiveSvg from '@/assets/svg/home/live.svg';
import PlayAudioSvg from '@/assets/svg/home/playAudio.svg';
import { closeLiveTask } from '@/utils/api/userApi';
import { ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import routerConfig from '@/router/tool';
import { show_message } from '@/utils/tool';
import { useStore } from 'vuex';
import { callPyjsInWindow } from '@/utils/pyqt';
const props = withDefaults(
defineProps<{
url: string;
value?: string;
modelValue: number;
}>(),
{
value: '',
},
);
const store = useStore();
const route = useRoute();
const router = useRouter();
const canPlay = ref(false);
watch(
() => props.url,
(v) => {
if (v) {
canPlay.value = true;
}
},
);
watch(
() => props.value,
(v) => {
if (v) {
textareaValue.value = v;
} else {
textareaValue.value = '';
}
},
);
const textareaStatus = ref(false);
const textareaValue = ref(props.value);
const changeRouteQuery = () => {
router.replace({
path: routerConfig.startLive.path,
name: routerConfig.startLive.name,
query: {
...route.query,
is_live: '0',
},
});
};
const showTextarea = () => {
textareaStatus.value = true;
};
const closeLive = async () => {
try {
let res: any = await closeLiveTask(route.query.id);
if (res.code == 0) {
show_message('关播成功', 'success');
// 通知视频暂停播放
store.commit('live/setLiveVideoStatus', {
play: false,
});
changeRouteQuery();
// 通知python刷新所有首页的直播列表
callPyjsInWindow('reloadLiveTaskList');
}
} catch (e) {
console.log(e);
}
};
</script>
<style lang="less">
@import '@/style/variables.less';
.start-live-audio-box {
display: flex;
flex-direction: column;
padding: 0 4px;
.start-live-audio-content {
flex: 1;
margin-top: 40px;
border-radius: 0px 3px 3px 3px;
border: 1px solid #464646;
background: #1e1e1e;
padding: 12px;
.label {
font-size: @size-15;
color: #fff;
margin-bottom: 12px;
}
.play-audio-box {
border-radius: 8px;
background: linear-gradient(355deg, #00b58f 0%, rgba(255, 255, 255, 0.8) 100%, rgba(255, 255, 255, 0.9) 100%);
height: 120px;
padding: 12px;
.dj(space-around);
flex-direction: column;
.line {
.dja(space-between);
}
}
.reset-live-audio-textarea {
margin-top: 12px;
.t-textarea__inner {
border-radius: 8px;
background: #454545;
font-size: @size-14;
}
}
}
.start-live-audio-footer {
font-size: @size-15;
color: #00cca2;
padding: 20px 0;
.dja(space-between);
.stop {
.dja();
.start-time {
font-size: @size-14;
color: #bababa;
font-weight: 600;
margin-right: 12px;
}
}
.live-status {
.da();
.live-icon {
background: #04ae8a;
border-radius: 50%;
width: 40px;
height: 40px;
.dja();
}
& > :last-child {
margin-left: 8px;
}
}
}
}
</style>
<template>
<div class="start-live-human-box">
<Loading v-show="loading"></Loading>
<div class="content">
<Button theme="green" class="jump-btn" @click="onJump">连接直播平台</Button>
<div class="header">人工回复</div>
<div class="chose-sound-color">
<div class="label">选择音色</div>
<div class="value">
<div
class="right-chose-tones"
:class="{
'not-soundColor-class': !lists.soundColor.length,
}"
>
<div class="default-label">
<SelectionPopup
v-model="textTonesVisible"
title="选择一种音调"
:list="lists.tones"
v-model:value="textTonesValue"
@itemChange="textTonesChange"
>
<div>
<template v-if="!textTonesValue"> 音调</template>
<template v-else>
<div class="chose-tones-item">
<img :src="textTonesInfo.img" alt="" />
<div>
<div class="name">{{ textTonesInfo.c_name }}</div>
<div class="categorie">{{ textTonesInfo.c_categorie }}</div>
</div>
</div>
</template>
</div>
</SelectionPopup>
</div>
<template v-if="lists.soundColor.length">
<div class="default-add">+</div>
<div @click="openSoundColor" class="default-label">
<SelectionPopup
title="选择一种音色"
v-model="soundColorVisible"
:disabled="disabled"
v-model:value="soundColorValue"
:list="lists.soundColor"
@itemChange="soundColorItemChange"
>
<div>
<template v-if="!soundColorValue"> 音色</template>
<template v-else>
<div class="chose-tones-item">
<img :src="soundColorInfo.img" alt="" />
<div>
<div class="name">{{ soundColorInfo.c_name }}</div>
</div>
</div>
</template>
</div></SelectionPopup
>
</div>
</template>
</div>
</div>
</div>
<div class="input-box">
<Textarea v-model="textareaValue" placeholder="输入内容点击下方发送,数字人将口播内容"></Textarea>
</div>
</div>
<div class="footer">
<div class="live-status-box">
<div class="live-status">
<div class="live-icon">
<LiveSvg></LiveSvg>
</div>
<span>
<template v-if="route.query.is_live == '1'"> 直播中 </template>
<template v-else> 未开播 </template>
</span>
</div>
<div class="stop" v-if="route.query.is_live == '1'">
<!-- <span class="start-time">00:52:20</span> -->
<Button theme="opacity" @click="closeLive">关闭直播</Button>
</div>
</div>
<Button theme="green" class="reset-send-btn" @click="submit">发送</Button>
</div>
</div>
</template>
<script lang="ts" setup>
import LiveSvg from '@/assets/svg/home/live.svg';
import Loading from '@/components/loading.vue';
import Button from '@/components/Button.vue';
import Textarea from '@/components/textarea.vue';
import SelectionPopup from '@/components/SelectionPopup.vue';
import { show_message } from '@/utils/tool';
import { onMounted, reactive, ref, watch } from 'vue';
import { getTonesList } from '@/service/Common';
import { liveInteractionReply, closeLiveTask } from '@/utils/api/userApi';
import { useRoute, useRouter } from 'vue-router';
import routerConfig from '@/router/tool';
import { useStore } from 'vuex';
import { callPyjsInWindow } from '@/utils/pyqt';
const emit = defineEmits(['createAudio']);
const store = useStore();
const router = useRouter();
const route = useRoute();
const lists = reactive({
tones: [],
soundColor: [],
});
const loading = ref(false);
const textTonesVisible = ref(false);
const textTonesValue = ref('');
const textTonesInfo = ref({});
const soundColorVisible = ref(false);
const soundColorValue = ref('');
const soundColorInfo = ref({});
const disabled = ref(true);
const textareaValue = ref('');
const textTonesChange = (item: any) => {
textTonesInfo.value = item;
};
const soundColorItemChange = (item: any) => {
soundColorInfo.value = item;
};
const changeRouteQuery = () => {
router.replace({
path: routerConfig.interactiveResponse.path,
name: routerConfig.interactiveResponse.name,
query: {
...route.query,
is_live: '0',
},
});
};
// 关闭直播
const closeLive = async () => {
try {
let res: any = await closeLiveTask(route.query.id);
if (res.code == 0) {
show_message('关播成功', 'success');
// 通知视频暂停播放
store.commit('live/setLiveVideoStatus', {
play: false,
});
changeRouteQuery();
// 通知python刷新所有首页的直播列表
callPyjsInWindow('reloadLiveTaskList');
}
} catch (e) {
console.log(e);
}
};
const submit = async () => {
if (!textTonesValue.value) {
show_message('音调必选');
return;
}
try {
loading.value = true;
let res: any = await liveInteractionReply(route.query.id, {
phonetic_timbres_id: soundColorValue.value,
reply_content: textareaValue.value,
tone_id: textTonesValue.value,
});
if (res.code == 0) {
// 播放音频
emit('createAudio', res.data.status);
}
loading.value = false;
} catch (e) {
loading.value = false;
console.log(e);
}
};
watch(
() => textTonesValue.value,
(v) => {
if (v) {
disabled.value = false;
} else {
disabled.value = true;
}
},
);
const onJump = () => {
let params = {
id: route.query.id,
digital_image: route.query.digital_image,
digital_name: route.query.digital_name,
};
// 跳转
if (window.pyjs) {
window.pyjs.openCtrlPage(JSON.stringify(params));
} else {
show_message('empty py');
}
};
onMounted(async () => {
let res = await getTonesList();
lists.tones = res.tones;
lists.soundColor = res.soundColor;
});
</script>
<style lang="less">
@import '@/style/variables.less';
.start-live-human-box {
display: flex;
flex-direction: column;
padding: 0 4px;
position: relative;
.content {
flex: 1;
border-radius: 0px 3px 3px 3px;
border: 1px solid #464646;
background: #1e1e1e;
margin-top: 40px;
padding: 12px;
display: flex;
position: relative;
flex-direction: column;
.jump-btn {
position: absolute;
right: 6px;
top: 6px;
}
.header {
font-size: @size-15;
color: #fff;
margin-bottom: 12px;
}
.chose-sound-color {
.da();
.label {
font-size: @size-15;
color: #b4b4b4;
}
.value {
.right-chose-tones {
margin-left: 12px;
width: 245px;
height: 38px;
border-radius: 6px;
border: 1px solid #b5b5b5;
background: #1a1b1b;
display: flex;
justify-content: space-around;
align-items: center;
.default-label {
color: #b5b5b5;
font-weight: 600;
font-size: @size-14;
cursor: pointer;
}
.default-add {
color: rgb(180, 180, 180);
font-size: @size-20;
font-weight: bold;
}
.chose-tones-item {
width: 110px;
height: 34px;
border-radius: 6px;
border: 1px solid #363636;
background: #1a1b1b;
transition: all 0.2s;
display: flex;
justify-content: space-around;
align-items: center;
cursor: pointer;
padding: 0 6px;
& > :last-child {
margin-left: 4px;
}
img {
width: 25px;
height: 25px;
border-radius: 50%;
}
.name {
font-size: @size-12;
color: #e2e2e2;
line-height: 14px;
}
.categorie {
font-size: @size-10;
color: #d0d0d0;
}
}
}
.not-soundColor-class {
width: 120px;
}
}
}
.input-box {
flex: 1;
margin-top: 12px;
.custom-textarea-box {
height: 100%;
.t-textarea {
height: 100%;
.t-textarea__inner {
height: 100% !important;
border-radius: 6px;
border: 1px solid #d0d0d0;
&::placeholder {
font-size: @size-14;
}
}
}
}
}
}
.footer {
padding: 20px 12px;
display: flex;
align-items: center;
.reset-send-btn {
width: 125px;
border-radius: 4px;
margin-left: auto;
}
.live-status-box {
.da();
.live-status {
.da();
color: white;
.live-icon {
background: #04ae8a;
border-radius: 50%;
width: 40px;
height: 40px;
.dja();
}
& > :last-child {
margin-left: 8px;
}
}
.stop {
margin-left: 30px;
}
}
}
}
</style>
<template>
<div class="custom-interactive-response-page">
<Human @createAudio="createAudio"></Human>
<div v-show="false">
<audio :src="audioFile" ref="audioRef" @canplay="audioCanplay" @ended="audioEnded"></audio>
</div>
</div>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue';
import Human from './components/human.vue';
import { useRoute } from 'vue-router';
import { callPyjsInWindow, initPyqtToWindow, injectWindow } from '@/utils/pyqt';
import { getliveTaskReply, getUserCookie } from '@/utils/api/userApi';
import { useStore } from 'vuex';
// 互动回复
const store = useStore();
const route = useRoute();
// 音频文件
const audioRef = ref<HTMLAudioElement>();
const audioFile = ref('');
const imgs = {
mp4: new URL('../../assets/img/1.mp4', import.meta.url).href,
mp3: new URL('../../assets/img/2.wav', import.meta.url).href,
};
const createAudio = (url: string) => {
audioFile.value = url;
};
const audioCanplay = () => {
audioRef.value.play();
// 通知python将直播页面的音量减小
callPyjsInWindow('lowerVideoVolume');
};
// 音频播放结束
const audioEnded = () => {
// 通知python将直播页面的音量恢复
callPyjsInWindow('videoVolumeRestoration');
};
// 定时检测python的方法是否注入成功
let interval = null;
// 定时获取直播互动内容
let intervalLive = null;
// 处理后的视频
const realVideo = ref('');
// 新增视频(当前)
const addVideo = ref(imgs.mp4);
// 视频列表
const addVideoList = ref([]);
// 当前播放id
const addVideoId = ref('');
const stopInterval = () => {
window.clearInterval(interval);
clearInterval(interval);
interval = null;
};
// 获取最新的内容
const openInterval = () => {
interval = window.setInterval(() => {
// 找到第一个没有播放的
for (let i = 0; i < addVideoList.value.length; i++) {
let item = addVideoList.value[i];
if (item.play_status === false && item.remove) {
// 已有取走的任务正在执行
// console.log('已有取走的任务正在执行');
break;
}
if (item.play_status === false && item.remove === false) {
if (addVideo.value == item.reply_content) {
// 本次播放的视频与上次一致,通知视频模块重新播放
store.commit('live/videoReload');
// console.log('本次视频与上次一致');
}
addVideoList.value[i].remove = true;
addVideo.value = item.reply_content;
addVideoId.value = item.id;
break;
}
}
}, 100);
};
// 获取直播互动内容
const getLive = async () => {
try {
let res: any = await getliveTaskReply(route.query.id);
if (res.code == 0 && res.data && res.data.length) {
// if (isDev()) {
// num += 1;
// res.data = [
// {
// id: num,
// reply_content:
// num % 2 == 1
// ? 'https://yunyi-live.oss-cn-hangzhou.aliyuncs.com/live/output/1.mp4'
// : 'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/5c8eeb9e-6a7b-45e6-85f9-1c75c945b8b1.mp4',
// },
// {
// id: num++,
// reply_content:
// num % 2 == 1
// ? 'https://yunyi-live.oss-cn-hangzhou.aliyuncs.com/live/output/1.mp4'
// : 'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/5c8eeb9e-6a7b-45e6-85f9-1c75c945b8b1.mp4',
// },
// ];
// }
res.data.forEach((item: any) => {
item.play_status = false;
item.remove = false;
});
//id
// problem
// reply_content
addVideoList.value = addVideoList.value.concat(res.data);
}
} catch (e) {
console.log(e);
}
};
// 开启
const startLiveInterval = () => {
closeLiveInterval();
intervalLive = window.setInterval(() => {
getLive();
}, 3000);
};
// 关闭
const closeLiveInterval = () => {
window.clearInterval(intervalLive);
clearInterval(intervalLive);
intervalLive = null;
};
// python 回调
const mergeCallback = (params: any) => {
try {
if (params.video) {
realVideo.value = params.video;
}
if (params.add_video) {
addVideo.value = params.add_video;
}
} catch (e) {
console.log(e);
}
};
onMounted(async () => {
initPyqtToWindow();
// 将通知方法注入window
injectWindow('mergeCallback', mergeCallback);
// 传递用户token
try {
window.pyjs.setToken(getUserCookie());
} catch (e) {
console.error('没有pyjs');
}
// if (isDev()) {
// mergeCallback({
// video: imgs.mp4,
// });
// }
// 打开定时任务
startLiveInterval();
openInterval();
});
onBeforeUnmount(() => {
closeLiveInterval();
stopInterval();
});
</script>
<style lang="less">
@import '@/style/variables.less';
.custom-interactive-response-page {
display: flex;
width: @pageWidth2 !important;
& > * {
width: 100%;
background: #303030;
height: 100%;
}
& > :not(:first-child) {
margin-left: 4px;
}
}
</style>
<template>
<div class="start-only-video-live">
<!-- <Button theme="green" @click="playVideo">开始播放</Button> -->
<AddVideoPlay
v-model:progress="progress"
:playId="playId"
@playEnd="playEnd"
:video1="video"
:video2="video2"
></AddVideoPlay>
</div>
</template>
<script lang="ts" setup>
import Button from '@/components/Button.vue';
import { onBeforeUnmount, ref, watch } from 'vue';
import AddVideoPlay from '@/components/AddVideoPlay.vue';
const props = withDefaults(
defineProps<{
video: any;
video2: any;
playId?: any;
modelValue: any;
}>(),
{
playId: 0,
},
);
const videoRef1 = ref<HTMLVideoElement>();
let interval1 = null;
const progress = ref(0);
const emit = defineEmits(['update:modelValue', 'playEnd']);
watch(
() => progress.value,
(v) => {
emit('update:modelValue', v);
},
);
const playEnd = (id: any) => {
emit('playEnd', id);
};
const closeInterval = () => {
window.clearInterval(interval1);
clearInterval(interval1);
interval1 = null;
};
const playVideo = () => {
console.log(videoRef1.value);
videoRef1.value.play();
closeInterval();
interval1 = window.setInterval(() => {
// 播放结束
if (videoRef1.value.ended) {
closeInterval();
}
}, 100);
};
onBeforeUnmount(() => {
closeInterval();
});
</script>
<style lang="less">
.start-only-video-live {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
</style>
<template>
<div class="custom-start-only-video-page">
<Video :video="realVideo" v-model="progress" :video2="addVideo" :playId="addVideoId" @playEnd="playEnd"></Video>
</div>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import Video from './components/video.vue';
import { getLiveDetail } from '@/utils/api/userApi';
import { useRoute, useRouter } from 'vue-router';
import { show_message, isDev } from '@/utils/tool';
import { callPyjsInWindow, initPyqtToWindow, injectWindow } from '@/utils/pyqt';
import { getliveTaskReply, getUserCookie } from '@/utils/api/userApi';
import routerConfig from '@/router/tool';
import { useStore } from 'vuex';
const store = useStore();
const route = useRoute();
const router = useRouter();
const routeQuery = route.query;
const progress = ref(0);
const imgs = {
mp4: new URL('../../assets/img/1.mp4', import.meta.url).href,
mp3: new URL('../../assets/img/2.wav', import.meta.url).href,
};
const liveInfo = reactive({
// 原始链接
video: [],
content: '',
type_content: '',
});
// 定时检测python的方法是否注入成功
let interval = null;
// 定时获取直播互动内容
let intervalLive = null;
// 处理后的视频
const realVideo = ref('');
// 新增视频(当前)
const addVideo = ref(imgs.mp4);
// 视频列表
const addVideoList = ref([]);
// 当前播放id
const addVideoId = ref('');
// 添加的视频播放结束
const playEnd = (id: any) => {
if (id) {
//
let index = addVideoList.value.findIndex((item: any) => item.id == id);
if (index !== -1) {
console.log('播放结束并更新状态', id);
addVideoList.value[index].play_status = true;
// 移出
addVideoList.value.splice(index, 1);
}
}
};
const stopInterval = () => {
window.clearInterval(interval);
clearInterval(interval);
interval = null;
};
// 获取最新的内容
const openInterval = () => {
interval = window.setInterval(() => {
// 找到第一个没有播放的
for (let i = 0; i < addVideoList.value.length; i++) {
let item = addVideoList.value[i];
if (item.play_status === false && item.remove) {
// 已有取走的任务正在执行
// console.log('已有取走的任务正在执行');
break;
}
if (item.play_status === false && item.remove === false) {
if (addVideo.value == item.reply_content) {
// 本次播放的视频与上次一致,通知视频模块重新播放
store.commit('live/videoReload');
// console.log('本次视频与上次一致');
}
addVideoList.value[i].remove = true;
addVideo.value = item.reply_content;
addVideoId.value = item.id;
break;
}
}
}, 100);
};
// 获取直播互动内容
const getLive = async () => {
try {
let res: any = await getliveTaskReply(route.query.id);
if (res.code == 0 && res.data && res.data.length) {
// if (isDev()) {
// num += 1;
// res.data = [
// {
// id: num,
// reply_content:
// num % 2 == 1
// ? 'https://yunyi-live.oss-cn-hangzhou.aliyuncs.com/live/output/1.mp4'
// : 'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/5c8eeb9e-6a7b-45e6-85f9-1c75c945b8b1.mp4',
// },
// {
// id: num++,
// reply_content:
// num % 2 == 1
// ? 'https://yunyi-live.oss-cn-hangzhou.aliyuncs.com/live/output/1.mp4'
// : 'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/5c8eeb9e-6a7b-45e6-85f9-1c75c945b8b1.mp4',
// },
// ];
// }
res.data.forEach((item: any) => {
item.play_status = false;
item.remove = false;
});
//id
// problem
// reply_content
addVideoList.value = addVideoList.value.concat(res.data);
}
} catch (e) {
console.log(e);
}
};
// 开启
const startLiveInterval = () => {
closeLiveInterval();
intervalLive = window.setInterval(() => {
getLive();
}, 3000);
};
// 关闭
const closeLiveInterval = () => {
window.clearInterval(intervalLive);
clearInterval(intervalLive);
intervalLive = null;
};
const getDetail = async () => {
if (!routeQuery.id) {
show_message('禁止访问');
return;
}
try {
let res: any = await getLiveDetail(routeQuery.id);
if (res.code == 0) {
liveInfo.video = res.data.url;
liveInfo.content = res.data.content;
if (res.data.type_content) {
liveInfo.content = res.data.type_content;
}
// 通知python合并
submitVideo();
// 通知python刷新所有首页的直播列表
callPyjsInWindow('reloadLiveTaskList');
// 开播成功
router.replace({
path: routerConfig.onlyVideoLive.path,
name: routerConfig.onlyVideoLive.name,
query: {
...route.query,
is_live: '1',
},
});
}
} catch (e) {
console.log(e);
}
};
// 视频列表提交到py
const submitVideo = () => {
try {
if (window.pyjs) {
console.log(route.query.window_index, '窗口下标');
if (window.pyjs.run) {
window.pyjs.run(liveInfo.video, routeQuery.id, route.query.window_index);
console.log('执行了run方法');
} else {
console.log('没有run方法');
}
} else {
show_message('empty-1 py');
}
} catch (e) {
show_message(e.message);
}
};
// python 回调
const mergeCallback = (params: any) => {
try {
if (params.video) {
realVideo.value = params.video;
}
if (params.add_video) {
addVideo.value = params.add_video;
}
} catch (e) {
console.log(e);
}
};
onMounted(async () => {
initPyqtToWindow();
// 将通知方法注入window
injectWindow('mergeCallback', mergeCallback);
// 传递用户token
try {
window.pyjs.setToken(getUserCookie());
} catch (e) {
console.error('没有pyjs');
}
if (isDev()) {
mergeCallback({
// video: imgs.mp4,
video: 'http://yunyi-live.oss-cn-hangzhou.aliyuncs.com/live/output/87.mp4',
});
}
// 打开定时任务
startLiveInterval();
openInterval();
// 可以开播
await getDetail();
});
onBeforeUnmount(() => {
closeLiveInterval();
stopInterval();
});
</script>
<style lang="less">
@import '@/style/variables.less';
.custom-start-only-video-page {
display: flex;
width: 100% !important;
padding: 0 !important;
overflow: hidden;
& > * {
width: 100%;
background: #303030;
height: 100%;
}
}
</style>
......@@ -21,6 +21,9 @@
<Button class="digtal-people-start-end" theme="danger" height="40px" @click="startLive(item)"
>开播中</Button
>
<Button class="digtal-people-ctrl" theme="danger" height="40px" @click="openInteractiveResponse(item)"
>控制台</Button
>
</template>
<div class="digtal-people-hover-tool">
<Button size="13" theme="dark" @click="onEdit(item)">编辑</Button>
......@@ -65,7 +68,7 @@ import routerConfig from '@/router/tool';
import { onUpdateLiveTask } from '@/service/Common';
import { getLiveTask, deleteLiveTask } from '@/utils/api/userApi';
import { isDev, show_message } from '@/utils/tool';
import { initPyqtToWindow, injectWindow } from '@/utils/pyqt';
import { callPyjsInWindow, initPyqtToWindow, injectWindow } from '@/utils/pyqt';
const props = withDefaults(
defineProps<{
......@@ -84,7 +87,8 @@ const myDigtalList = reactive({
const confirmVisible = ref(false);
const confirmId = ref('');
const startLive = (item: any) => {
// 页面跳转前的校验
const beforePageJump = (item: any) => {
if (item.status == 2) {
show_message('直播未准备完毕');
return;
......@@ -102,18 +106,53 @@ const startLive = (item: any) => {
digital_name: item.name,
is_live: item.is_live,
};
if (isDev()) {
// 传一个id跳转到页面
router.push({
path: routerConfig.startLive.path,
name: routerConfig.startLive.name,
query: params,
});
} else {
// 通知python
window.pyjs.openLivePage(JSON.stringify(params));
return params;
};
const startLive = (item: any) => {
let params = beforePageJump(item);
if (params) {
if (isDev()) {
// 传一个id跳转到页面(旧的播放页面)
// router.push({
// path: routerConfig.startLive.path,
// name: routerConfig.startLive.name,
// query: params,
// });
// 新的播放页面
router.push({
path: routerConfig.onlyVideoLive.path,
name: routerConfig.onlyVideoLive.name,
query: params,
});
} else {
params.page_path = routerConfig.onlyVideoLive.path;
// window.pyjs.openLivePage(JSON.stringify(params));
// 打开只有视频播放的页面
callPyjsInWindow('openLivePage', params);
}
}
};
// 打开人工回复页面
const openInteractiveResponse = (item: any) => {
let params = beforePageJump(item);
if (params) {
if (isDev()) {
router.push({
path: routerConfig.interactiveResponse.path,
name: routerConfig.interactiveResponse.name,
query: params,
});
} else {
params.page_path = routerConfig.interactiveResponse.path;
// 通知python
callPyjsInWindow('openLivePage', params);
}
}
};
const getList = async () => {
try {
myDigtalList.loading = true;
......@@ -211,6 +250,12 @@ watch(
.digtal-people-start-end {
margin-top: 60px;
}
.digtal-people-start-end + .t-button {
margin-left: 0;
}
.digtal-people-ctrl {
margin-top: 20px;
}
.digtal-people-hover-tool {
flex: 1;
display: flex;
......
......@@ -22,6 +22,16 @@ export default {
path: '/startLive',
name: 'startLive',
},
// 只有视频播放的页面
onlyVideoLive: {
path: '/startLiveOnlyVideo',
name: 'startLiveOnlyVideo',
},
// 只有人工回复的页面
interactiveResponse: {
path: '/interactiveResponse',
name: 'interactiveResponse',
},
login: {
path: '/login',
name: 'login',
......
......@@ -5,10 +5,6 @@
width: fit-content;
}
.t-button + .t-button {
margin-left: @spacer;
}
.t-layout.t-layout--with-sider {
> .t-layout {
flex: 1;
......
......@@ -11,8 +11,14 @@ export const initPyqtToWindow = (key: string = '') => {
// 将pyqt要执行的事件挂载到window
export const injectWindow = (key: string, value: any, parent: string = '') => {
if (parent) {
if (!window[parent]) {
window[parent] = {};
}
window[parent][key] = value;
} else {
if (!window.pyEvent) {
window.pyEvent = {};
}
window.pyEvent[key] = value;
}
};
......@@ -24,6 +30,6 @@ export const callPyjsInWindow = (name: string, value: any = '') => {
try {
window.pyjs[name](JSON.stringify(value));
} catch (e) {
show_message('没有pyjs');
console.error(e);
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment