index.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <div
  3. ref="containerRef"
  4. class="video-container"
  5. >
  6. <div class="video-wrapper">
  7. <WebRTCVideo
  8. ref="webrtcRef"
  9. :serno="webrtcSerno"
  10. :drag-flag="true"
  11. @connected="onWebRTCConnected"
  12. @disconnected="onWebRTCDisconnected"
  13. @error="handleVideoError"
  14. />
  15. <!-- 全屏按钮 -->
  16. <button
  17. class="fullscreen-btn"
  18. :title="isFullscreen ? '退出全屏 (F)' : '全屏 (F)'"
  19. @click="toggleFullscreen"
  20. >
  21. <svg
  22. v-if="!isFullscreen"
  23. viewBox="0 0 24 24"
  24. width="16"
  25. height="16"
  26. >
  27. <path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
  28. </svg>
  29. <svg
  30. v-else
  31. viewBox="0 0 24 24"
  32. width="16"
  33. height="16"
  34. >
  35. <path fill="currentColor" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
  36. </svg>
  37. </button>
  38. </div>
  39. </div>
  40. </template>
  41. <script setup lang="ts">
  42. import { ref, onMounted, onUnmounted } from 'vue'
  43. import WebRTCVideo from '@/components/WebRTCVideo.vue'
  44. const webrtcRef = ref()
  45. const isFullscreen = ref(false)
  46. const containerRef = ref<HTMLElement>()
  47. // 设备序列号,可通过 URL 参数 ?serno=xxx 传入
  48. const webrtcSerno = ref('ANSJ-00-I7N3-QINU-00000027')
  49. function initFromUrl() {
  50. const params = new URLSearchParams(window.location.search)
  51. const serno = params.get('serno')
  52. if (serno) webrtcSerno.value = serno
  53. }
  54. function onWebRTCConnected() {
  55. console.log('WebRTC 已连接')
  56. }
  57. function onWebRTCDisconnected() {
  58. console.log('WebRTC 已断开')
  59. }
  60. const toggleFullscreen = () => {
  61. if (!containerRef.value) return
  62. if (!isFullscreen.value) {
  63. containerRef.value.requestFullscreen?.()
  64. } else {
  65. document.exitFullscreen?.()
  66. }
  67. }
  68. const handleFullscreenChange = () => {
  69. isFullscreen.value = !!document.fullscreenElement
  70. }
  71. const handleKeydown = (event: KeyboardEvent) => {
  72. switch (event.code) {
  73. case 'KeyF':
  74. case 'F11':
  75. event.preventDefault()
  76. toggleFullscreen()
  77. break
  78. case 'Escape':
  79. if (isFullscreen.value) toggleFullscreen()
  80. break
  81. }
  82. }
  83. const handleVideoError = (error: any) => {
  84. console.error('视频播放错误:', error)
  85. }
  86. onMounted(() => {
  87. initFromUrl()
  88. document.addEventListener('fullscreenchange', handleFullscreenChange)
  89. document.addEventListener('keydown', handleKeydown)
  90. })
  91. onUnmounted(() => {
  92. document.removeEventListener('fullscreenchange', handleFullscreenChange)
  93. document.removeEventListener('keydown', handleKeydown)
  94. })
  95. </script>
  96. <style lang="scss" scoped>
  97. .video-container {
  98. width: calc(100% - 40px);
  99. margin: 20px;
  100. position: relative;
  101. overflow: hidden;
  102. background: #000;
  103. border-radius: 8px;
  104. aspect-ratio: 16 / 9;
  105. max-height: calc(100vh - var(--v3-header-height, 50px) - 40px);
  106. }
  107. .video-container:fullscreen {
  108. max-height: none !important;
  109. aspect-ratio: auto !important;
  110. width: 100vw !important;
  111. height: 100vh !important;
  112. }
  113. .video-wrapper {
  114. position: relative;
  115. width: 100%;
  116. height: 100%;
  117. :deep(.webrtc-container) {
  118. width: 100%;
  119. height: 100%;
  120. }
  121. :deep(.webrtc-video-wrapper) {
  122. width: 100%;
  123. height: 100%;
  124. background: #000;
  125. overflow: hidden;
  126. }
  127. :deep(video) {
  128. width: 100%;
  129. height: 100%;
  130. object-fit: fill;
  131. }
  132. :deep(.webrtc-controls) {
  133. opacity: 0;
  134. transition: opacity 0.3s ease;
  135. }
  136. &:hover :deep(.webrtc-controls) {
  137. opacity: 1;
  138. }
  139. }
  140. .fullscreen-btn {
  141. position: absolute;
  142. top: 16px;
  143. right: 16px;
  144. width: 40px;
  145. height: 40px;
  146. background: rgba(0, 0, 0, 0.6);
  147. border: none;
  148. border-radius: 8px;
  149. color: white;
  150. cursor: pointer;
  151. display: flex;
  152. align-items: center;
  153. justify-content: center;
  154. opacity: 0;
  155. transition: all 0.3s ease;
  156. backdrop-filter: blur(8px);
  157. z-index: 10;
  158. &:hover {
  159. background: rgba(0, 0, 0, 0.8);
  160. transform: scale(1.05);
  161. }
  162. &:active {
  163. transform: scale(0.95);
  164. }
  165. .video-wrapper:hover & {
  166. opacity: 1;
  167. }
  168. .video-container:fullscreen & {
  169. top: 20px;
  170. right: 20px;
  171. width: 48px;
  172. height: 48px;
  173. }
  174. }
  175. @media (max-width: 768px) {
  176. .video-container {
  177. height: 100vh;
  178. }
  179. .fullscreen-btn {
  180. width: 36px;
  181. height: 36px;
  182. top: 12px;
  183. right: 12px;
  184. svg { width: 14px; height: 14px; }
  185. }
  186. }
  187. @media (prefers-reduced-motion: reduce) {
  188. * {
  189. transition: none !important;
  190. animation: none !important;
  191. }
  192. }
  193. </style>