| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- <template>
- <div
- ref="containerRef"
- class="video-container"
- >
- <div class="video-wrapper">
- <WebRTCVideo
- ref="webrtcRef"
- :serno="webrtcSerno"
- :drag-flag="true"
- @connected="onWebRTCConnected"
- @disconnected="onWebRTCDisconnected"
- @error="handleVideoError"
- />
- <!-- 全屏按钮 -->
- <button
- class="fullscreen-btn"
- :title="isFullscreen ? '退出全屏 (F)' : '全屏 (F)'"
- @click="toggleFullscreen"
- >
- <svg
- v-if="!isFullscreen"
- viewBox="0 0 24 24"
- width="16"
- height="16"
- >
- <path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
- </svg>
- <svg
- v-else
- viewBox="0 0 24 24"
- width="16"
- height="16"
- >
- <path fill="currentColor" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
- </svg>
- </button>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, onUnmounted } from 'vue'
- import WebRTCVideo from '@/components/WebRTCVideo.vue'
- const webrtcRef = ref()
- const isFullscreen = ref(false)
- const containerRef = ref<HTMLElement>()
- // 设备序列号,可通过 URL 参数 ?serno=xxx 传入
- const webrtcSerno = ref('ANSJ-00-I7N3-QINU-00000027')
- function initFromUrl() {
- const params = new URLSearchParams(window.location.search)
- const serno = params.get('serno')
- if (serno) webrtcSerno.value = serno
- }
- function onWebRTCConnected() {
- console.log('WebRTC 已连接')
- }
- function onWebRTCDisconnected() {
- console.log('WebRTC 已断开')
- }
- const toggleFullscreen = () => {
- if (!containerRef.value) return
- if (!isFullscreen.value) {
- containerRef.value.requestFullscreen?.()
- } else {
- document.exitFullscreen?.()
- }
- }
- const handleFullscreenChange = () => {
- isFullscreen.value = !!document.fullscreenElement
- }
- const handleKeydown = (event: KeyboardEvent) => {
- switch (event.code) {
- case 'KeyF':
- case 'F11':
- event.preventDefault()
- toggleFullscreen()
- break
- case 'Escape':
- if (isFullscreen.value) toggleFullscreen()
- break
- }
- }
- const handleVideoError = (error: any) => {
- console.error('视频播放错误:', error)
- }
- onMounted(() => {
- initFromUrl()
- document.addEventListener('fullscreenchange', handleFullscreenChange)
- document.addEventListener('keydown', handleKeydown)
- })
- onUnmounted(() => {
- document.removeEventListener('fullscreenchange', handleFullscreenChange)
- document.removeEventListener('keydown', handleKeydown)
- })
- </script>
- <style lang="scss" scoped>
- .video-container {
- width: calc(100% - 40px);
- margin: 20px;
- position: relative;
- overflow: hidden;
- background: #000;
- border-radius: 8px;
- aspect-ratio: 16 / 9;
- max-height: calc(100vh - var(--v3-header-height, 50px) - 40px);
- }
- .video-container:fullscreen {
- max-height: none !important;
- aspect-ratio: auto !important;
- width: 100vw !important;
- height: 100vh !important;
- }
- .video-wrapper {
- position: relative;
- width: 100%;
- height: 100%;
- :deep(.webrtc-container) {
- width: 100%;
- height: 100%;
- }
- :deep(.webrtc-video-wrapper) {
- width: 100%;
- height: 100%;
- background: #000;
- overflow: hidden;
- }
- :deep(video) {
- width: 100%;
- height: 100%;
- object-fit: fill;
- }
- :deep(.webrtc-controls) {
- opacity: 0;
- transition: opacity 0.3s ease;
- }
- &:hover :deep(.webrtc-controls) {
- opacity: 1;
- }
- }
- .fullscreen-btn {
- position: absolute;
- top: 16px;
- right: 16px;
- width: 40px;
- height: 40px;
- background: rgba(0, 0, 0, 0.6);
- border: none;
- border-radius: 8px;
- color: white;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: 0;
- transition: all 0.3s ease;
- backdrop-filter: blur(8px);
- z-index: 10;
- &:hover {
- background: rgba(0, 0, 0, 0.8);
- transform: scale(1.05);
- }
- &:active {
- transform: scale(0.95);
- }
- .video-wrapper:hover & {
- opacity: 1;
- }
- .video-container:fullscreen & {
- top: 20px;
- right: 20px;
- width: 48px;
- height: 48px;
- }
- }
- @media (max-width: 768px) {
- .video-container {
- height: 100vh;
- }
- .fullscreen-btn {
- width: 36px;
- height: 36px;
- top: 12px;
- right: 12px;
- svg { width: 14px; height: 14px; }
- }
- }
- @media (prefers-reduced-motion: reduce) {
- * {
- transition: none !important;
- animation: none !important;
- }
- }
- </style>
|