feat(liveView): 添加视频播放器控件并优化媒体流处理

- 新增video元素用于统一播放音视频流
- 合并音频和视频轨道到单一媒体流
- 优化播放器控件设置和样式
- 移除冗余代码并简化DOM操作
This commit is contained in:
yindongqi 2025-08-13 17:11:14 +08:00
parent 2103d3fcc1
commit 51da660509

View File

@ -4,64 +4,34 @@
<div id="liveContent" class="liveContent" ref="liveContent"> <div id="liveContent" class="liveContent" ref="liveContent">
<div id="screenAudio" style="display: none" ref="screenAudio"></div> <div id="screenAudio" style="display: none" ref="screenAudio"></div>
<div class="live-status" v-show="liveStatus == 'scheduled'"> <div class="live-status" v-show="liveStatus == 'scheduled'">
<svg <svg t="1733991622296" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
t="1733991622296" p-id="7587" width="38" height="38" data-spm-anchor-id="a313x.search_index.0.i3.59a33a81UKNXHQ">
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7587"
width="38"
height="38"
data-spm-anchor-id="a313x.search_index.0.i3.59a33a81UKNXHQ"
>
<path <path
d="M526.933333 288c-23.466667 0-42.666667 19.2-42.666666 42.666667v213.333333c0 23.466667 19.2 42.666667 42.666666 42.666667s42.666667-19.2 42.666667-42.666667v-213.333333c0-23.466667-17.066667-42.666667-42.666667-42.666667zM526.933333 629.333333c-12.8 0-21.333333 4.266667-29.866666 12.8-8.533333 8.533333-12.8 17.066667-12.8 29.866667 0 12.8 4.266667 23.466667 12.8 29.866667 8.533333 8.533333 19.2 12.8 29.866666 12.8 12.8 0 21.333333-4.266667 29.866667-12.8s12.8-19.2 12.8-32-4.266667-23.466667-12.8-29.866667c-6.4-6.4-17.066667-10.666667-29.866667-10.666667z" d="M526.933333 288c-23.466667 0-42.666667 19.2-42.666666 42.666667v213.333333c0 23.466667 19.2 42.666667 42.666666 42.666667s42.666667-19.2 42.666667-42.666667v-213.333333c0-23.466667-17.066667-42.666667-42.666667-42.666667zM526.933333 629.333333c-12.8 0-21.333333 4.266667-29.866666 12.8-8.533333 8.533333-12.8 17.066667-12.8 29.866667 0 12.8 4.266667 23.466667 12.8 29.866667 8.533333 8.533333 19.2 12.8 29.866666 12.8 12.8 0 21.333333-4.266667 29.866667-12.8s12.8-19.2 12.8-32-4.266667-23.466667-12.8-29.866667c-6.4-6.4-17.066667-10.666667-29.866667-10.666667z"
p-id="7588" p-id="7588" fill="#ffffff"></path>
fill="#ffffff"
></path>
<path <path
d="M526.933333 74.666667c-234.666667 0-426.666667 192-426.666666 426.666666s192 426.666667 426.666666 426.666667 426.666667-192 426.666667-426.666667-189.866667-426.666667-426.666667-426.666666z m0 768c-187.733333 0-341.333333-153.6-341.333333-341.333334s153.6-341.333333 341.333333-341.333333 341.333333 153.6 341.333334 341.333333-151.466667 341.333333-341.333334 341.333334z" d="M526.933333 74.666667c-234.666667 0-426.666667 192-426.666666 426.666666s192 426.666667 426.666666 426.666667 426.666667-192 426.666667-426.666667-189.866667-426.666667-426.666667-426.666666z m0 768c-187.733333 0-341.333333-153.6-341.333333-341.333334s153.6-341.333333 341.333333-341.333333 341.333333 153.6 341.333334 341.333333-151.466667 341.333333-341.333334 341.333334z"
p-id="7589" p-id="7589" fill="#ffffff"></path>
fill="#ffffff"
></path>
</svg> </svg>
オンライン授業未开始 オンライン授業未开始
</div> </div>
<div class="live-status" v-show="liveStatus == 'finished'"> <div class="live-status" v-show="liveStatus == 'finished'">
<svg <svg t="1733991622296" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
t="1733991622296" p-id="7587" width="38" height="38" data-spm-anchor-id="a313x.search_index.0.i3.59a33a81UKNXHQ">
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7587"
width="38"
height="38"
data-spm-anchor-id="a313x.search_index.0.i3.59a33a81UKNXHQ"
>
<path <path
d="M526.933333 288c-23.466667 0-42.666667 19.2-42.666666 42.666667v213.333333c0 23.466667 19.2 42.666667 42.666666 42.666667s42.666667-19.2 42.666667-42.666667v-213.333333c0-23.466667-17.066667-42.666667-42.666667-42.666667zM526.933333 629.333333c-12.8 0-21.333333 4.266667-29.866666 12.8-8.533333 8.533333-12.8 17.066667-12.8 29.866667 0 12.8 4.266667 23.466667 12.8 29.866667 8.533333 8.533333 19.2 12.8 29.866666 12.8 12.8 0 21.333333-4.266667 29.866667-12.8s12.8-19.2 12.8-32-4.266667-23.466667-12.8-29.866667c-6.4-6.4-17.066667-10.666667-29.866667-10.666667z" d="M526.933333 288c-23.466667 0-42.666667 19.2-42.666666 42.666667v213.333333c0 23.466667 19.2 42.666667 42.666666 42.666667s42.666667-19.2 42.666667-42.666667v-213.333333c0-23.466667-17.066667-42.666667-42.666667-42.666667zM526.933333 629.333333c-12.8 0-21.333333 4.266667-29.866666 12.8-8.533333 8.533333-12.8 17.066667-12.8 29.866667 0 12.8 4.266667 23.466667 12.8 29.866667 8.533333 8.533333 19.2 12.8 29.866666 12.8 12.8 0 21.333333-4.266667 29.866667-12.8s12.8-19.2 12.8-32-4.266667-23.466667-12.8-29.866667c-6.4-6.4-17.066667-10.666667-29.866667-10.666667z"
p-id="7588" p-id="7588" fill="#ffffff"></path>
fill="#ffffff"
></path>
<path <path
d="M526.933333 74.666667c-234.666667 0-426.666667 192-426.666666 426.666666s192 426.666667 426.666666 426.666667 426.666667-192 426.666667-426.666667-189.866667-426.666667-426.666667-426.666666z m0 768c-187.733333 0-341.333333-153.6-341.333333-341.333334s153.6-341.333333 341.333333-341.333333 341.333333 153.6 341.333334 341.333333-151.466667 341.333333-341.333334 341.333334z" d="M526.933333 74.666667c-234.666667 0-426.666667 192-426.666666 426.666666s192 426.666667 426.666666 426.666667 426.666667-192 426.666667-426.666667-189.866667-426.666667-426.666667-426.666666z m0 768c-187.733333 0-341.333333-153.6-341.333333-341.333334s153.6-341.333333 341.333333-341.333333 341.333333 153.6 341.333334 341.333333-151.466667 341.333333-341.333334 341.333334z"
p-id="7589" p-id="7589" fill="#ffffff"></path>
fill="#ffffff"
></path>
</svg> </svg>
オンライン授業が終わる オンライン授業が終わる
</div> </div>
<div <div class="watermark-container" v-if="isVisible" @animationend="onAnimationEnd" :key="animationKey">
class="watermark-container"
v-if="isVisible"
@animationend="onAnimationEnd"
:key="animationKey"
>
{{ loginInfo.member_realname }}_{{ loginInfo.member_passport }} {{ loginInfo.member_realname }}_{{ loginInfo.member_passport }}
</div> </div>
<div id="teacherCamera" class="teacherCamera" ref="teacherCamera"></div> <div id="teacherCamera" class="teacherCamera" ref="teacherCamera"></div>
<video ref="player" controls style="width: 100%; height: 100%;"></video>
<!-- <div class="custom-controls" v-show="showControlBar"> <!-- <div class="custom-controls" v-show="showControlBar">
<div class="teacherAudioList" v-show="teacherAudioList.length > 0"> <div class="teacherAudioList" v-show="teacherAudioList.length > 0">
<div v-for="(teacherAudio, index) in teacherAudioList" class="teacherAudio"> <div v-for="(teacherAudio, index) in teacherAudioList" class="teacherAudio">
@ -212,13 +182,8 @@
<img :src="message.avatar" alt="avatar" class="avatar" /> <img :src="message.avatar" alt="avatar" class="avatar" />
<div class="message-body"> <div class="message-body">
<div class="message-header"> <div class="message-header">
<span class="message-sender" <span class="message-sender"><span class="teacher-tag"
><span v-if="message.sender.slice(0, 7) === 'teacher'">講師</span>{{ message.sender }}</span>
class="teacher-tag"
v-if="message.sender.slice(0, 7) === 'teacher'"
>講師</span
>{{ message.sender }}</span
>
<span class="message-time">{{ <span class="message-time">{{
"\u00A0\u00A0\u00A0" + formatTimestamp(message.time) "\u00A0\u00A0\u00A0" + formatTimestamp(message.time)
}}</span> }}</span>
@ -231,11 +196,7 @@
<div ref="scrollAnchor"></div> <div ref="scrollAnchor"></div>
</div> </div>
<div class="message-input"> <div class="message-input">
<input <input v-model="newMessage" @keyup.enter="sendMessage" placeholder="メッセージを入力..." />
v-model="newMessage"
@keyup.enter="sendMessage"
placeholder="メッセージを入力..."
/>
<button @click="sendMessage">送信</button> <button @click="sendMessage">送信</button>
</div> </div>
</div> </div>
@ -252,46 +213,27 @@
<div class="info-content-item-title">開始時間</div> <div class="info-content-item-title">開始時間</div>
<div class="info-content-item-value">{{ liveInfo.start_time }}</div> <div class="info-content-item-value">{{ liveInfo.start_time }}</div>
</div> </div>
<div <div class="info-content-item" v-show="liveInfo.end_time != '' && liveInfo.end_time != null">
class="info-content-item"
v-show="liveInfo.end_time != '' && liveInfo.end_time != null"
>
<div class="info-content-item-title">終了時間</div> <div class="info-content-item-title">終了時間</div>
<div class="info-content-item-value">{{ liveInfo.end_time }}</div> <div class="info-content-item-value">{{ liveInfo.end_time }}</div>
</div> </div>
<div class="info-content-item" style=""> <div class="info-content-item" style="">
<div class="info-content-item-title">詳細</div> <div class="info-content-item-title">詳細</div>
<div <div class="info-content-item-value" style="
class="info-content-item-value"
style="
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding-top: 10px; padding-top: 10px;
" ">
> <svg t="1733988199430" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
<svg p-id="6925" data-spm-anchor-id="a313x.search_index.0.i0.14ef3a81TzRpck" width="64" height="64"
t="1733988199430" xmlns:xlink="http://www.w3.org/1999/xlink">
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6925"
data-spm-anchor-id="a313x.search_index.0.i0.14ef3a81TzRpck"
width="64"
height="64"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<path <path
d="M892.5 304.6L689.4 101.4c-22.7-22.7-52.8-35.1-84.9-35.1H218.7c-66.2 0-120 53.8-120 120v654.1c0 66.2 53.8 120 120 120h589c66.2 0 120-53.8 120-120v-451c0-32-12.5-62.2-35.2-84.8zM667.7 192.9l123.4 123.4h-83.4c-22.1 0-40-17.9-40-40v-83.4z m140 687.5h-589c-22.1 0-40-17.9-40-40V186.3c0-22.1 17.9-40 40-40h369v130c0 66.2 53.8 120 120 120h140v444.1c0 22-17.9 40-40 40z" d="M892.5 304.6L689.4 101.4c-22.7-22.7-52.8-35.1-84.9-35.1H218.7c-66.2 0-120 53.8-120 120v654.1c0 66.2 53.8 120 120 120h589c66.2 0 120-53.8 120-120v-451c0-32-12.5-62.2-35.2-84.8zM667.7 192.9l123.4 123.4h-83.4c-22.1 0-40-17.9-40-40v-83.4z m140 687.5h-589c-22.1 0-40-17.9-40-40V186.3c0-22.1 17.9-40 40-40h369v130c0 66.2 53.8 120 120 120h140v444.1c0 22-17.9 40-40 40z"
p-id="6926" p-id="6926" fill="#8a8a8a"></path>
fill="#8a8a8a"
></path>
<path <path
d="M310 378.1h177.1c22.1 0 40-17.9 40-40s-17.9-40-40-40H310c-22.1 0-40 17.9-40 40s17.9 40 40 40zM716.4 473.3H310c-22.1 0-40 17.9-40 40s17.9 40 40 40h406.4c22.1 0 40-17.9 40-40s-17.9-40-40-40zM716.4 648.6H310c-22.1 0-40 17.9-40 40s17.9 40 40 40h406.4c22.1 0 40-17.9 40-40s-17.9-40-40-40z" d="M310 378.1h177.1c22.1 0 40-17.9 40-40s-17.9-40-40-40H310c-22.1 0-40 17.9-40 40s17.9 40 40 40zM716.4 473.3H310c-22.1 0-40 17.9-40 40s17.9 40 40 40h406.4c22.1 0 40-17.9 40-40s-17.9-40-40-40zM716.4 648.6H310c-22.1 0-40 17.9-40 40s17.9 40 40 40h406.4c22.1 0 40-17.9 40-40s-17.9-40-40-40z"
p-id="6927" p-id="6927" fill="#8a8a8a"></path>
fill="#8a8a8a"
></path>
</svg> </svg>
詳細なし 詳細なし
</div> </div>
@ -440,16 +382,25 @@ export default {
audio.remove(); // <video> audio.remove(); // <video>
}); });
} }
audioTracks[index].play(this.$refs.screenAudio); // audioTracks[index].play(this.$refs.screenAudio);
} else { } else {
this.teacherAudioList.push({ this.teacherAudioList.push({
name: audioTracks[index].userID, name: audioTracks[index].userID,
isMute: false, isMute: false,
showVolumeControl: false, showVolumeControl: false,
}); });
// console.log("teacherAudioList:", this.teacherAudioList); // audioTracks[index].play(this.liveContent);
}
audioTracks[index].play(this.liveContent); // tracktrack
if (this.$refs.player.srcObject) {
//
const existingStream = this.$refs.player.srcObject;
existingStream.addTrack(audioTracks[index]._track.mediaTrack);
} else {
//
const newStream = new MediaStream();
newStream.addTrack(audioTracks[index]._track.mediaTrack);
this.$refs.player.srcObject = newStream;
} }
} }
@ -460,37 +411,61 @@ export default {
// <video> // <video>
// console.log("", videoElements); // console.log("", videoElements);
if (videoElements.length > 0) { // if (videoElements.length > 0) {
videoElements.forEach((video) => { // videoElements.forEach((video) => {
video.remove(); // <video> // video.remove(); // <video>
}); // });
// }
// videoTracks[index].play(this.liveContent);
//tracktrack
if (this.$refs.player.srcObject) {
//
const existingStream = this.$refs.player.srcObject;
existingStream.addTrack(videoTracks[index]._track.mediaTrack);
} else {
//
const newStream = new MediaStream();
newStream.addTrack(videoTracks[index]._track.mediaTrack);
this.$refs.player.srcObject = newStream;
} }
videoTracks[index].play(this.liveContent);
// 使 setTimeout // 使 setTimeout
setTimeout(() => { setTimeout(() => {
const videos = document.querySelectorAll( const videos = this.$refs.player;
"video.qnrtc-video-player.qnrtc-stream-player" videos.controls = true;
);
// controls
for (let i = 0; i < videos.length; i++) {
videos[i].controls = true;
// //
videos[i].style.pointerEvents = "auto"; videos.style.pointerEvents = "auto";
// //
videos[i].disablePictureInPicture = true; videos.disablePictureInPicture = true;
// //
videos[i].style.objectFit = "contain"; videos.style.objectFit = "contain";
// //
videos[i].addEventListener("contextmenu", function (e) { videos.addEventListener("contextmenu", function (e) {
e.preventDefault(); e.preventDefault();
}); });
// const videos = document.querySelectorAll(
// "video.qnrtc-video-player.qnrtc-stream-player"
// );
// controls
// for (let i = 0; i < videos.length; i++) {
// videos[i].controls = true;
// //
// videos[i].style.pointerEvents = "auto";
// //
// videos[i].disablePictureInPicture = true;
// //
// videos[i].style.objectFit = "contain";
// //
// videos[i].addEventListener("contextmenu", function (e) {
// e.preventDefault();
// });
// //
setTimeout(() => { setTimeout(() => {
this.isStop = false; this.isStop = false;
this.isVisible = true; this.isVisible = true;
}, 10000); }, 10000);
} // }
}, 100); }, 100);
} else if (videoTracks[index].tag === "video") { } else if (videoTracks[index].tag === "video") {
const teacherCameraElement = this.$refs.teacherCamera; const teacherCameraElement = this.$refs.teacherCamera;
@ -859,7 +834,7 @@ export default {
width: 100%; width: 100%;
position: relative; position: relative;
height: 35vh; height: 35vh;
background-color: gray; background-color: black;
overflow: hidden; overflow: hidden;
margin: auto; margin: auto;
margin-top: 5rem; margin-top: 5rem;