stream-test.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template>
  2. <div class="page-container">
  3. <div class="page-header">
  4. <el-button :icon="ArrowLeft" @click="goBack">返回</el-button>
  5. <span class="title">Cloudflare Stream 测试</span>
  6. </div>
  7. <!-- 配置区域 -->
  8. <div class="config-section">
  9. <el-form :model="config" label-width="auto" inline>
  10. <el-form-item label="播放器类型">
  11. <el-select v-model="config.playerType" style="width: 200px">
  12. <el-option label="Cloudflare iframe" value="cloudflare-iframe" />
  13. <el-option label="HLS.js" value="hls" />
  14. <el-option label="原生 Video" value="native" />
  15. </el-select>
  16. </el-form-item>
  17. <el-form-item label="Video ID">
  18. <el-input v-model="config.videoId" placeholder="Cloudflare Stream Video ID" style="width: 300px" />
  19. </el-form-item>
  20. <el-form-item label="自定义域名">
  21. <el-input
  22. v-model="config.customerDomain"
  23. placeholder="customer-xxx.cloudflarestream.com"
  24. style="width: 300px"
  25. />
  26. </el-form-item>
  27. <el-form-item>
  28. <el-button type="primary" @click="loadVideo">加载视频</el-button>
  29. </el-form-item>
  30. </el-form>
  31. <el-divider />
  32. <el-form label-width="auto" inline>
  33. <el-form-item label="或输入 HLS 地址">
  34. <el-input v-model="config.hlsUrl" placeholder="https://xxx/manifest/video.m3u8" style="width: 500px" />
  35. </el-form-item>
  36. <el-form-item>
  37. <el-button type="success" @click="loadHlsUrl">播放 HLS</el-button>
  38. </el-form-item>
  39. </el-form>
  40. </div>
  41. <!-- 播放器区域 -->
  42. <div class="player-section">
  43. <VideoPlayer
  44. ref="playerRef"
  45. :player-type="currentPlayerType"
  46. :video-id="currentVideoId"
  47. :customer-domain="config.customerDomain"
  48. :src="currentSrc"
  49. :use-iframe="config.playerType === 'cloudflare-iframe'"
  50. :autoplay="config.autoplay"
  51. :muted="config.muted"
  52. :controls="true"
  53. @play="onPlay"
  54. @pause="onPause"
  55. @error="onError"
  56. />
  57. </div>
  58. <!-- 控制按钮 -->
  59. <div class="control-section">
  60. <el-space wrap>
  61. <el-button type="primary" @click="handlePlay">播放</el-button>
  62. <el-button @click="handlePause">暂停</el-button>
  63. <el-button type="danger" @click="handleStop">停止</el-button>
  64. <el-button @click="handleScreenshot">截图</el-button>
  65. <el-button @click="handleFullscreen">全屏</el-button>
  66. <el-divider direction="vertical" />
  67. <el-switch v-model="config.muted" active-text="静音" inactive-text="有声" />
  68. <el-switch v-model="config.autoplay" active-text="自动播放" inactive-text="手动播放" />
  69. </el-space>
  70. </div>
  71. <!-- 使用说明 -->
  72. <div class="info-section">
  73. <el-alert title="Cloudflare Stream 接入说明" type="info" :closable="false">
  74. <template #default>
  75. <ol>
  76. <li>在 Cloudflare Dashboard 中上传视频或创建直播</li>
  77. <li>
  78. 获取 Video ID(格式如:
  79. <code>ea95132c15732412d22c1476fa83f27a</code>
  80. </li>
  81. <li>
  82. 获取自定义域名(格式如:
  83. <code>customer-xxx.cloudflarestream.com</code>
  84. </li>
  85. <li>填入上方表单并点击加载视频</li>
  86. </ol>
  87. <p style="margin-top: 10px">
  88. <strong>HLS 地址格式:</strong>
  89. <code>https://customer-xxx.cloudflarestream.com/{video_id}/manifest/video.m3u8</code>
  90. </p>
  91. <p>
  92. <strong>iframe 地址格式:</strong>
  93. <code>https://customer-xxx.cloudflarestream.com/{video_id}/iframe</code>
  94. </p>
  95. </template>
  96. </el-alert>
  97. </div>
  98. <!-- 日志区域 -->
  99. <div class="log-section">
  100. <h4>事件日志</h4>
  101. <div class="log-content">
  102. <div v-for="(log, index) in logs" :key="index" class="log-item" :class="log.type">
  103. <span class="time">{{ log.time }}</span>
  104. <span class="message">{{ log.message }}</span>
  105. </div>
  106. </div>
  107. </div>
  108. </div>
  109. </template>
  110. <script setup lang="ts">
  111. import { ref, reactive, computed } from 'vue'
  112. import { useRouter } from 'vue-router'
  113. import { ArrowLeft } from '@element-plus/icons-vue'
  114. import VideoPlayer from '@/components/VideoPlayer.vue'
  115. const router = useRouter()
  116. const playerRef = ref<InstanceType<typeof VideoPlayer>>()
  117. const config = reactive({
  118. playerType: 'cloudflare-iframe' as 'cloudflare-iframe' | 'hls' | 'native',
  119. videoId: '',
  120. customerDomain: '',
  121. hlsUrl: '',
  122. autoplay: false,
  123. muted: true
  124. })
  125. const currentVideoId = ref('')
  126. const currentSrc = ref('')
  127. const currentPlayerType = computed(() => {
  128. if (config.playerType === 'cloudflare-iframe') return 'cloudflare'
  129. return config.playerType
  130. })
  131. interface LogItem {
  132. time: string
  133. type: 'info' | 'success' | 'error'
  134. message: string
  135. }
  136. const logs = ref<LogItem[]>([])
  137. function addLog(message: string, type: LogItem['type'] = 'info') {
  138. const time = new Date().toLocaleTimeString()
  139. logs.value.unshift({ time, type, message })
  140. if (logs.value.length > 50) {
  141. logs.value.pop()
  142. }
  143. }
  144. function loadVideo() {
  145. if (!config.videoId) {
  146. addLog('请输入 Video ID', 'error')
  147. return
  148. }
  149. currentVideoId.value = config.videoId
  150. if (config.playerType === 'hls') {
  151. const domain = config.customerDomain || 'customer-xxx.cloudflarestream.com'
  152. currentSrc.value = `https://${domain}/${config.videoId}/manifest/video.m3u8`
  153. } else {
  154. currentSrc.value = ''
  155. }
  156. addLog(`加载视频: ${config.videoId}`, 'success')
  157. }
  158. function loadHlsUrl() {
  159. if (!config.hlsUrl) {
  160. addLog('请输入 HLS 地址', 'error')
  161. return
  162. }
  163. config.playerType = 'hls'
  164. currentSrc.value = config.hlsUrl
  165. currentVideoId.value = ''
  166. addLog(`加载 HLS: ${config.hlsUrl}`, 'success')
  167. }
  168. function handlePlay() {
  169. playerRef.value?.play()
  170. addLog('播放', 'info')
  171. }
  172. function handlePause() {
  173. playerRef.value?.pause()
  174. addLog('暂停', 'info')
  175. }
  176. function handleStop() {
  177. playerRef.value?.stop()
  178. addLog('停止', 'info')
  179. }
  180. function handleScreenshot() {
  181. playerRef.value?.screenshot()
  182. addLog('截图', 'info')
  183. }
  184. function handleFullscreen() {
  185. playerRef.value?.fullscreen()
  186. addLog('全屏', 'info')
  187. }
  188. function onPlay() {
  189. addLog('视频开始播放', 'success')
  190. }
  191. function onPause() {
  192. addLog('视频已暂停', 'info')
  193. }
  194. function onError(error: any) {
  195. addLog(`播放错误: ${JSON.stringify(error)}`, 'error')
  196. }
  197. function goBack() {
  198. router.push('/camera')
  199. }
  200. </script>
  201. <style lang="scss" scoped>
  202. .page-container {
  203. padding: 20px;
  204. }
  205. .page-header {
  206. display: flex;
  207. align-items: center;
  208. background-color: #fff;
  209. border-radius: 4px;
  210. margin-bottom: 20px;
  211. .title {
  212. margin-left: 15px;
  213. font-size: 16px;
  214. font-weight: 600;
  215. }
  216. }
  217. .config-section {
  218. background-color: #fff;
  219. border-radius: 4px;
  220. }
  221. .player-section {
  222. height: 480px;
  223. margin-bottom: 20px;
  224. border-radius: 4px;
  225. overflow: hidden;
  226. }
  227. .control-section {
  228. background-color: #fff;
  229. border-radius: 4px;
  230. margin-bottom: 20px;
  231. }
  232. .info-section {
  233. margin-bottom: 20px;
  234. code {
  235. background-color: #f5f5f5;
  236. padding: 2px 6px;
  237. border-radius: 4px;
  238. font-size: 12px;
  239. }
  240. ol {
  241. padding-left: 20px;
  242. margin: 10px 0;
  243. }
  244. }
  245. .log-section {
  246. background-color: #fff;
  247. border-radius: 4px;
  248. h4 {
  249. margin-bottom: 10px;
  250. font-size: 14px;
  251. }
  252. .log-content {
  253. max-height: 200px;
  254. overflow-y: auto;
  255. background-color: #fafafa;
  256. border-radius: 4px;
  257. padding: 10px;
  258. }
  259. .log-item {
  260. font-size: 12px;
  261. padding: 4px 0;
  262. border-bottom: 1px solid #eee;
  263. &:last-child {
  264. border-bottom: none;
  265. }
  266. .time {
  267. color: #999;
  268. margin-right: 10px;
  269. }
  270. &.success .message {
  271. color: #67c23a;
  272. }
  273. &.error .message {
  274. color: #f56c6c;
  275. }
  276. }
  277. }
  278. </style>