Commit b73e5e103db93a7edfad3a1b51906a27efa1a046
1 parent
3252e971
fix():修复播放器组件轮播和全部关闭销毁播放器冲突问题
Showing
2 changed files
with
340 additions
and
334 deletions
web_src/src/components/DeviceList1078.vue
| @@ -148,6 +148,7 @@ export default { | @@ -148,6 +148,7 @@ export default { | ||
| 148 | // --- 侧边栏折叠逻辑 --- | 148 | // --- 侧边栏折叠逻辑 --- |
| 149 | updateSidebarState() { | 149 | updateSidebarState() { |
| 150 | this.sidebarState = !this.sidebarState; | 150 | this.sidebarState = !this.sidebarState; |
| 151 | + // 触发 resize 事件让播放器自适应 | ||
| 151 | setTimeout(() => { | 152 | setTimeout(() => { |
| 152 | const event = new Event('resize'); | 153 | const event = new Event('resize'); |
| 153 | window.dispatchEvent(event); | 154 | window.dispatchEvent(event); |
| @@ -180,37 +181,39 @@ export default { | @@ -180,37 +181,39 @@ export default { | ||
| 180 | }, | 181 | }, |
| 181 | 182 | ||
| 182 | // ========================================== | 183 | // ========================================== |
| 183 | - // 【核心修复】1. 左键点击播放逻辑 | 184 | + // 1. 左键点击播放逻辑 |
| 184 | // ========================================== | 185 | // ========================================== |
| 185 | nodeClick(data, node) { | 186 | nodeClick(data, node) { |
| 186 | if (this.isCarouselRunning) { | 187 | if (this.isCarouselRunning) { |
| 187 | this.$message.warning("请先停止轮播再手动播放"); | 188 | this.$message.warning("请先停止轮播再手动播放"); |
| 188 | return; | 189 | return; |
| 189 | } | 190 | } |
| 190 | - // 判断是否为叶子节点(通道),没有子节点视为通道 | 191 | + // 判断是否为叶子节点(通道) |
| 191 | if (!data.children || data.children.length === 0) { | 192 | if (!data.children || data.children.length === 0) { |
| 192 | this.playSingleChannel(data); | 193 | this.playSingleChannel(data); |
| 193 | } | 194 | } |
| 194 | }, | 195 | }, |
| 195 | 196 | ||
| 196 | - // 单路播放 API 请求 | ||
| 197 | playSingleChannel(data) { | 197 | playSingleChannel(data) { |
| 198 | - // 假设 data.code 格式为 "deviceId_sim_channel" | ||
| 199 | - // 根据您的接口调整这里的解析逻辑 | 198 | + // data.code 格式假设为 "deviceId_sim_channel" |
| 200 | let stream = data.code.replace('-', '_'); // 容错处理 | 199 | let stream = data.code.replace('-', '_'); // 容错处理 |
| 201 | let arr = stream.split("_"); | 200 | let arr = stream.split("_"); |
| 202 | 201 | ||
| 203 | - // 假设 ID 结构是: id_sim_channel,取后两段 | ||
| 204 | - if(arr.length < 3) return; | 202 | + // 容错: 确保能解析出 sim 和 channel |
| 203 | + if (arr.length < 3) { | ||
| 204 | + console.warn("Invalid channel code:", data.code); | ||
| 205 | + return; | ||
| 206 | + } | ||
| 205 | 207 | ||
| 206 | this.$axios.get(`/api/jt1078/query/send/request/io/${arr[1]}/${arr[2]}`).then(res => { | 208 | this.$axios.get(`/api/jt1078/query/send/request/io/${arr[1]}/${arr[2]}`).then(res => { |
| 207 | - if(res.data.code === 0 || res.data.code === 200) { | ||
| 208 | - const url = res.data.data.ws_flv; // 或者 wss_flv | 209 | + if (res.data.code === 0 || res.data.code === 200) { |
| 210 | + // 兼容 http/https 协议 | ||
| 211 | + const url = (location.protocol === "https:") ? res.data.data.wss_flv : res.data.data.ws_flv; | ||
| 209 | const idx = this.windowClickIndex - 1; | 212 | const idx = this.windowClickIndex - 1; |
| 210 | 213 | ||
| 211 | - // 更新播放地址和信息 | 214 | + // 使用 $set 确保数组响应式更新 |
| 212 | this.$set(this.videoUrl, idx, url); | 215 | this.$set(this.videoUrl, idx, url); |
| 213 | - this.$set(this.videoDataList, idx, { ...data, videoUrl: url }); | 216 | + this.$set(this.videoDataList, idx, {...data, videoUrl: url}); |
| 214 | 217 | ||
| 215 | // 自动跳到下一个窗口 | 218 | // 自动跳到下一个窗口 |
| 216 | const maxWindow = parseInt(this.windowNum) || 4; | 219 | const maxWindow = parseInt(this.windowNum) || 4; |
| @@ -224,18 +227,15 @@ export default { | @@ -224,18 +227,15 @@ export default { | ||
| 224 | }, | 227 | }, |
| 225 | 228 | ||
| 226 | // ========================================== | 229 | // ========================================== |
| 227 | - // 【核心修复】2. 右键菜单逻辑 | 230 | + // 2. 右键菜单逻辑 |
| 228 | // ========================================== | 231 | // ========================================== |
| 229 | nodeContextmenu(event, data, node) { | 232 | nodeContextmenu(event, data, node) { |
| 230 | - // 只有设备节点(有子级)才显示菜单,通道节点不显示 | 233 | + // 只有父设备节点(有子级)才显示菜单 |
| 231 | if (data.children && data.children.length > 0) { | 234 | if (data.children && data.children.length > 0) { |
| 232 | this.rightClickNode = node; | 235 | this.rightClickNode = node; |
| 233 | this.contextMenuVisible = true; | 236 | this.contextMenuVisible = true; |
| 234 | this.contextMenuLeft = event.clientX; | 237 | this.contextMenuLeft = event.clientX; |
| 235 | this.contextMenuTop = event.clientY; | 238 | this.contextMenuTop = event.clientY; |
| 236 | - | ||
| 237 | - // 阻止默认浏览器右键菜单 | ||
| 238 | - // 注意:VehicleList 组件内部也需要处理 @contextmenu.prevent | ||
| 239 | } | 239 | } |
| 240 | }, | 240 | }, |
| 241 | 241 | ||
| @@ -243,7 +243,6 @@ export default { | @@ -243,7 +243,6 @@ export default { | ||
| 243 | this.contextMenuVisible = false; | 243 | this.contextMenuVisible = false; |
| 244 | }, | 244 | }, |
| 245 | 245 | ||
| 246 | - // 处理右键菜单命令(一键播放该设备) | ||
| 247 | handleContextCommand(command) { | 246 | handleContextCommand(command) { |
| 248 | this.hideContextMenu(); | 247 | this.hideContextMenu(); |
| 249 | if (command === 'playback') { | 248 | if (command === 'playback') { |
| @@ -262,21 +261,20 @@ export default { | @@ -262,21 +261,20 @@ export default { | ||
| 262 | else this.windowNum = '4'; | 261 | else this.windowNum = '4'; |
| 263 | 262 | ||
| 264 | // 3. 构造批量请求参数 | 263 | // 3. 构造批量请求参数 |
| 265 | - // 假设后端接受的格式处理 | ||
| 266 | const ids = channels.map(c => { | 264 | const ids = channels.map(c => { |
| 267 | - // 假设 code 是 id_sim_channel | 265 | + // 假设 code 是 id_sim_channel,后端需要 sim-channel |
| 268 | const parts = c.code.replaceAll('_', '-').split('-'); | 266 | const parts = c.code.replaceAll('_', '-').split('-'); |
| 269 | - // 取 sim-channel 部分 | ||
| 270 | return parts.slice(1).join('-'); | 267 | return parts.slice(1).join('-'); |
| 271 | }); | 268 | }); |
| 272 | 269 | ||
| 273 | this.$axios.post('/api/jt1078/query/beachSend/request/io', ids).then(res => { | 270 | this.$axios.post('/api/jt1078/query/beachSend/request/io', ids).then(res => { |
| 274 | - if(res.data && res.data.data) { | 271 | + if (res.data && res.data.data) { |
| 275 | const list = res.data.data || []; | 272 | const list = res.data.data || []; |
| 276 | list.forEach((item, i) => { | 273 | list.forEach((item, i) => { |
| 277 | if (channels[i]) { | 274 | if (channels[i]) { |
| 278 | - this.$set(this.videoUrl, i, item.ws_flv); | ||
| 279 | - this.$set(this.videoDataList, i, { ...channels[i], videoUrl: item.ws_flv }); | 275 | + const url = (location.protocol === "https:") ? item.wss_flv : item.ws_flv; |
| 276 | + this.$set(this.videoUrl, i, url); | ||
| 277 | + this.$set(this.videoDataList, i, {...channels[i], videoUrl: url}); | ||
| 280 | } | 278 | } |
| 281 | }); | 279 | }); |
| 282 | } else { | 280 | } else { |
| @@ -297,10 +295,12 @@ export default { | @@ -297,10 +295,12 @@ export default { | ||
| 297 | async checkCarouselPermission(actionName) { | 295 | async checkCarouselPermission(actionName) { |
| 298 | if (!this.isCarouselRunning) return true; | 296 | if (!this.isCarouselRunning) return true; |
| 299 | try { | 297 | try { |
| 300 | - await this.$confirm(`正在轮播,"${actionName}"将停止轮播,是否继续?`,'提示',{ type: 'warning' }); | 298 | + await this.$confirm(`正在轮播,"${actionName}"将停止轮播,是否继续?`, '提示', {type: 'warning'}); |
| 301 | this.stopCarousel(); | 299 | this.stopCarousel(); |
| 302 | return true; | 300 | return true; |
| 303 | - } catch (e) { return false; } | 301 | + } catch (e) { |
| 302 | + return false; | ||
| 303 | + } | ||
| 304 | }, | 304 | }, |
| 305 | 305 | ||
| 306 | async closeAllVideo() { | 306 | async closeAllVideo() { |
| @@ -313,16 +313,17 @@ export default { | @@ -313,16 +313,17 @@ export default { | ||
| 313 | if (!(await this.checkCarouselPermission('关闭当前窗口'))) return; | 313 | if (!(await this.checkCarouselPermission('关闭当前窗口'))) return; |
| 314 | const idx = Number(this.windowClickIndex) - 1; | 314 | const idx = Number(this.windowClickIndex) - 1; |
| 315 | if (this.videoUrl && this.videoUrl[idx]) { | 315 | if (this.videoUrl && this.videoUrl[idx]) { |
| 316 | - this.$confirm(`确认关闭窗口 [${this.windowClickIndex}] ?`, '提示', { type: 'warning' }) | 316 | + this.$confirm(`确认关闭窗口 [${this.windowClickIndex}] ?`, '提示', {type: 'warning'}) |
| 317 | .then(() => { | 317 | .then(() => { |
| 318 | this.$set(this.videoUrl, idx, null); | 318 | this.$set(this.videoUrl, idx, null); |
| 319 | this.$set(this.videoDataList, idx, null); | 319 | this.$set(this.videoDataList, idx, null); |
| 320 | - }).catch(()=>{}); | 320 | + }).catch(() => { |
| 321 | + }); | ||
| 321 | } | 322 | } |
| 322 | }, | 323 | }, |
| 323 | 324 | ||
| 324 | // ========================================== | 325 | // ========================================== |
| 325 | - // 4. 轮播逻辑 (保持之前定义的逻辑) | 326 | + // 4. 轮播逻辑 (补全部分) |
| 326 | // ========================================== | 327 | // ========================================== |
| 327 | openCarouselConfig() { | 328 | openCarouselConfig() { |
| 328 | if (this.isCarouselRunning) { | 329 | if (this.isCarouselRunning) { |
| @@ -334,25 +335,182 @@ export default { | @@ -334,25 +335,182 @@ export default { | ||
| 334 | 335 | ||
| 335 | stopCarousel() { | 336 | stopCarousel() { |
| 336 | this.isCarouselRunning = false; | 337 | this.isCarouselRunning = false; |
| 337 | - if (this.carouselTimer) clearTimeout(this.carouselTimer); | 338 | + if (this.carouselTimer) { |
| 339 | + clearTimeout(this.carouselTimer); | ||
| 340 | + this.carouselTimer = null; | ||
| 341 | + } | ||
| 342 | + this.$message.info("轮播已停止"); | ||
| 338 | }, | 343 | }, |
| 339 | 344 | ||
| 340 | async startCarousel(config) { | 345 | async startCarousel(config) { |
| 341 | this.carouselConfig = config; | 346 | this.carouselConfig = config; |
| 347 | + | ||
| 348 | + // 1. 筛选目标设备 | ||
| 342 | let targetNodes = []; | 349 | let targetNodes = []; |
| 350 | + if (config.sourceType === 'all_online') { | ||
| 351 | + const collectOnline = (nodes) => { | ||
| 352 | + nodes.forEach(node => { | ||
| 353 | + if (node.abnormalStatus === 1) targetNodes.push(node); | ||
| 354 | + if (node.children && node.children.length > 0) collectOnline(node.children); | ||
| 355 | + }); | ||
| 356 | + }; | ||
| 357 | + collectOnline(this.deviceTreeData); | ||
| 358 | + } else { | ||
| 359 | + targetNodes = config.selectedNodes.filter(n => n.abnormalStatus === 1); | ||
| 360 | + } | ||
| 361 | + | ||
| 362 | + if (targetNodes.length === 0) { | ||
| 363 | + this.$message.warning("当前范围内没有在线设备可供轮播"); | ||
| 364 | + return; | ||
| 365 | + } | ||
| 343 | 366 | ||
| 344 | - // ... (保留您之前的 startCarousel 完整逻辑,这里简略以节省篇幅,请确保您之前的代码已粘贴进来) ... | ||
| 345 | - // 如果没有之前的代码,请告诉我,我再发一遍完整的 startCarousel | ||
| 346 | - // 这里简单模拟一个启动 | 367 | + // 2. 初始化状态 |
| 368 | + this.carouselDeviceList = targetNodes; | ||
| 369 | + this.channelBuffer = []; | ||
| 370 | + this.deviceCursor = 0; | ||
| 347 | this.isCarouselRunning = true; | 371 | this.isCarouselRunning = true; |
| 348 | - this.$message.success("轮播已启动 (需要补全 startCarousel 完整逻辑)"); | 372 | + this.isWithinSchedule = true; |
| 373 | + | ||
| 374 | + this.$message.success(`轮播已启动,共 ${targetNodes.length} 台在线设备`); | ||
| 375 | + | ||
| 376 | + // 3. 立即执行第一轮 | ||
| 377 | + await this.executeFirstRound(); | ||
| 378 | + }, | ||
| 379 | + | ||
| 380 | + async executeFirstRound() { | ||
| 381 | + if (this.windowNum !== this.carouselConfig.layout) { | ||
| 382 | + this.windowNum = this.carouselConfig.layout; | ||
| 383 | + } | ||
| 384 | + | ||
| 385 | + const batch = await this.fetchNextBatchData(); | ||
| 386 | + if (batch) { | ||
| 387 | + this.applyVideoBatch(batch); | ||
| 388 | + this.runCarouselLoop(); | ||
| 389 | + } else { | ||
| 390 | + this.$message.error("首轮加载失败,尝试重试..."); | ||
| 391 | + this.runCarouselLoop(); | ||
| 392 | + } | ||
| 393 | + }, | ||
| 394 | + | ||
| 395 | + runCarouselLoop() { | ||
| 396 | + if (!this.isCarouselRunning) return; | ||
| 397 | + | ||
| 398 | + const {runMode, timeRange, interval} = this.carouselConfig; | ||
| 399 | + | ||
| 400 | + // 1. 检查定时 | ||
| 401 | + if (runMode === 'schedule') { | ||
| 402 | + if (!this.checkTimeRange(timeRange[0], timeRange[1])) { | ||
| 403 | + this.isWithinSchedule = false; | ||
| 404 | + if (this.videoUrl.some(v => v)) this.closeAllVideoNoConfirm(); | ||
| 405 | + this.carouselTimer = setTimeout(() => this.runCarouselLoop(), 10000); | ||
| 406 | + return; | ||
| 407 | + } | ||
| 408 | + this.isWithinSchedule = true; | ||
| 409 | + } | ||
| 410 | + | ||
| 411 | + // 2. 计算时间轴 | ||
| 412 | + const PRELOAD_TIME = 15; | ||
| 413 | + const intervalSec = Math.max(interval, 30); | ||
| 414 | + const waitTime = (intervalSec - PRELOAD_TIME) * 1000; | ||
| 415 | + | ||
| 416 | + // 3. 计时循环 | ||
| 417 | + this.carouselTimer = setTimeout(async () => { | ||
| 418 | + if (!this.isCarouselRunning) return; | ||
| 419 | + | ||
| 420 | + const nextBatch = await this.fetchNextBatchData(); | ||
| 421 | + | ||
| 422 | + this.carouselTimer = setTimeout(() => { | ||
| 423 | + if (!this.isCarouselRunning) return; | ||
| 424 | + if (nextBatch) this.applyVideoBatch(nextBatch); | ||
| 425 | + this.runCarouselLoop(); | ||
| 426 | + }, PRELOAD_TIME * 1000); | ||
| 427 | + | ||
| 428 | + }, waitTime); | ||
| 429 | + }, | ||
| 430 | + | ||
| 431 | + applyVideoBatch({urls, infos}) { | ||
| 432 | + this.videoUrl = new Array(parseInt(this.windowNum)).fill(null); | ||
| 433 | + setTimeout(() => { | ||
| 434 | + urls.forEach((url, index) => { | ||
| 435 | + setTimeout(() => { | ||
| 436 | + this.$set(this.videoUrl, index, url); | ||
| 437 | + this.$set(this.videoDataList, index, infos[index]); | ||
| 438 | + }, index * 100); | ||
| 439 | + }); | ||
| 440 | + }, 200); | ||
| 441 | + }, | ||
| 442 | + | ||
| 443 | + async fetchNextBatchData() { | ||
| 444 | + let pageSize = parseInt(this.windowNum) || 4; | ||
| 445 | + if (isNaN(pageSize)) pageSize = 4; | ||
| 446 | + | ||
| 447 | + // 填充缓冲区 | ||
| 448 | + let safetyCounter = 0; | ||
| 449 | + while (this.channelBuffer.length < pageSize && safetyCounter < 100) { | ||
| 450 | + safetyCounter++; | ||
| 451 | + if (this.deviceCursor >= this.carouselDeviceList.length) { | ||
| 452 | + this.deviceCursor = 0; | ||
| 453 | + } | ||
| 454 | + | ||
| 455 | + const device = this.carouselDeviceList[this.deviceCursor]; | ||
| 456 | + if (device && device.children && device.children.length > 0) { | ||
| 457 | + const codes = device.children | ||
| 458 | + .filter(child => !child.disabled) | ||
| 459 | + .map(child => child.code); | ||
| 460 | + this.channelBuffer.push(...codes); | ||
| 461 | + } | ||
| 462 | + this.deviceCursor++; | ||
| 463 | + } | ||
| 464 | + | ||
| 465 | + if (this.channelBuffer.length === 0) return null; | ||
| 466 | + | ||
| 467 | + const currentCodes = this.channelBuffer.splice(0, pageSize); | ||
| 468 | + const streamParams = currentCodes.map(c => c.replaceAll('_', '-').split('-').slice(1).join('-')); | ||
| 469 | + | ||
| 470 | + try { | ||
| 471 | + const res = await this.$axios.post('/api/jt1078/query/beachSend/request/io', streamParams, {timeout: 20000}); | ||
| 472 | + if (res.data && res.data.data) { | ||
| 473 | + const resultList = res.data.data; | ||
| 474 | + const urls = new Array(pageSize).fill(''); | ||
| 475 | + const infos = new Array(pageSize).fill(null); | ||
| 476 | + | ||
| 477 | + resultList.forEach((item, i) => { | ||
| 478 | + if (i < currentCodes.length) { | ||
| 479 | + const url = (location.protocol === "https:") ? item.wss_flv : item.ws_flv; | ||
| 480 | + urls[i] = url; | ||
| 481 | + infos[i] = { | ||
| 482 | + code: currentCodes[i], | ||
| 483 | + name: `通道 ${i + 1}`, | ||
| 484 | + videoUrl: url | ||
| 485 | + }; | ||
| 486 | + } | ||
| 487 | + }); | ||
| 488 | + return {urls, infos}; | ||
| 489 | + } | ||
| 490 | + } catch (e) { | ||
| 491 | + console.error("批量请求流地址失败", e); | ||
| 492 | + } | ||
| 493 | + return null; | ||
| 349 | }, | 494 | }, |
| 350 | 495 | ||
| 351 | - // ... 其他轮播辅助函数 (fetchNextBatchData, applyVideoBatch 等) ... | ||
| 352 | - // 请确保这些函数存在,否则轮播会报错 | ||
| 353 | - fetchNextBatchData() {}, | ||
| 354 | - runCarouselLoop() {}, | ||
| 355 | - applyVideoBatch() {}, | 496 | + checkTimeRange(startStr, endStr) { |
| 497 | + if (!startStr || !endStr) return true; | ||
| 498 | + const now = new Date(); | ||
| 499 | + const current = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds(); | ||
| 500 | + const parse = (str) => { | ||
| 501 | + const [h, m, s] = str.split(':').map(Number); | ||
| 502 | + return h * 3600 + m * 60 + s; | ||
| 503 | + }; | ||
| 504 | + const start = parse(startStr); | ||
| 505 | + const end = parse(endStr); | ||
| 506 | + if (end < start) return current >= start || current <= end; | ||
| 507 | + return current >= start && current <= end; | ||
| 508 | + }, | ||
| 509 | + | ||
| 510 | + closeAllVideoNoConfirm() { | ||
| 511 | + this.videoUrl = new Array(parseInt(this.windowNum)).fill(null); | ||
| 512 | + this.videoDataList = new Array(parseInt(this.windowNum)).fill(null); | ||
| 513 | + }, | ||
| 356 | 514 | ||
| 357 | handleBeforeUnload(e) { | 515 | handleBeforeUnload(e) { |
| 358 | if (this.isCarouselRunning) { | 516 | if (this.isCarouselRunning) { |
| @@ -419,7 +577,10 @@ export default { | @@ -419,7 +577,10 @@ export default { | ||
| 419 | cursor: pointer; | 577 | cursor: pointer; |
| 420 | color: #606266; | 578 | color: #606266; |
| 421 | } | 579 | } |
| 422 | -.fold-btn:hover { color: #409EFF; } | 580 | + |
| 581 | +.fold-btn:hover { | ||
| 582 | + color: #409EFF; | ||
| 583 | +} | ||
| 423 | 584 | ||
| 424 | .header-right-info { | 585 | .header-right-info { |
| 425 | margin-left: auto; | 586 | margin-left: auto; |
| @@ -442,19 +603,24 @@ export default { | @@ -442,19 +603,24 @@ export default { | ||
| 442 | position: fixed; | 603 | position: fixed; |
| 443 | background: #fff; | 604 | background: #fff; |
| 444 | border: 1px solid #EBEEF5; | 605 | border: 1px solid #EBEEF5; |
| 445 | - box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); | 606 | + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1); |
| 446 | z-index: 3000; | 607 | z-index: 3000; |
| 447 | border-radius: 4px; | 608 | border-radius: 4px; |
| 448 | padding: 5px 0; | 609 | padding: 5px 0; |
| 449 | min-width: 120px; | 610 | min-width: 120px; |
| 450 | } | 611 | } |
| 612 | + | ||
| 451 | .menu-item { | 613 | .menu-item { |
| 452 | padding: 8px 15px; | 614 | padding: 8px 15px; |
| 453 | font-size: 14px; | 615 | font-size: 14px; |
| 454 | color: #606266; | 616 | color: #606266; |
| 455 | cursor: pointer; | 617 | cursor: pointer; |
| 456 | } | 618 | } |
| 457 | -.menu-item:hover { background: #ecf5ff; color: #409EFF; } | 619 | + |
| 620 | +.menu-item:hover { | ||
| 621 | + background: #ecf5ff; | ||
| 622 | + color: #409EFF; | ||
| 623 | +} | ||
| 458 | 624 | ||
| 459 | .carousel-status { | 625 | .carousel-status { |
| 460 | margin-left: 15px; | 626 | margin-left: 15px; |
web_src/src/components/common/EasyPlayer.vue
| @@ -17,9 +17,9 @@ | @@ -17,9 +17,9 @@ | ||
| 17 | </div> | 17 | </div> |
| 18 | </div> | 18 | </div> |
| 19 | 19 | ||
| 20 | - <div :id="uniqueId" ref="container" class="player-box"></div> | 20 | + <div :id="uniqueId" :key="uniqueId" ref="container" class="player-box"></div> |
| 21 | 21 | ||
| 22 | - <div v-if="!hasUrl" class="idle-mask"> | 22 | + <div v-show="!hasUrl" class="idle-mask"> |
| 23 | <div class="idle-text">无信号</div> | 23 | <div class="idle-text">无信号</div> |
| 24 | </div> | 24 | </div> |
| 25 | 25 | ||
| @@ -53,6 +53,7 @@ export default { | @@ -53,6 +53,7 @@ export default { | ||
| 53 | }, | 53 | }, |
| 54 | data() { | 54 | data() { |
| 55 | return { | 55 | return { |
| 56 | + // 初始 ID | ||
| 56 | uniqueId: `player-box-${Date.now()}-${Math.floor(Math.random() * 1000)}`, | 57 | uniqueId: `player-box-${Date.now()}-${Math.floor(Math.random() * 1000)}`, |
| 57 | playerInstance: null, | 58 | playerInstance: null, |
| 58 | 59 | ||
| @@ -68,19 +69,17 @@ export default { | @@ -68,19 +69,17 @@ export default { | ||
| 68 | controlTimer: null, | 69 | controlTimer: null, |
| 69 | retryCount: 0, | 70 | retryCount: 0, |
| 70 | 71 | ||
| 71 | - // 【配置区域】在这里修改 true/false 即可生效 | ||
| 72 | controlsConfig: { | 72 | controlsConfig: { |
| 73 | - showBottomBar: true, // 是否显示底栏 | ||
| 74 | - showSpeed: false, // 【关键】设为 false 隐藏底部网速 | ||
| 75 | - showCodeSelect: false, // 【关键】设为 false 隐藏解码选择 | ||
| 76 | - | ||
| 77 | - showPlay: true, // 播放暂停按钮 | ||
| 78 | - showAudio: true, // 音量按钮 | ||
| 79 | - showStretch: true, // 拉伸按钮 | ||
| 80 | - showScreenshot: true, // 截图按钮 | ||
| 81 | - showRecord: true, // 录制按钮 | ||
| 82 | - showZoom: true, // 电子放大 | ||
| 83 | - showFullscreen: true, // 全屏按钮 | 73 | + showBottomBar: true, |
| 74 | + showSpeed: false, | ||
| 75 | + showCodeSelect: false, | ||
| 76 | + showPlay: true, | ||
| 77 | + showAudio: true, | ||
| 78 | + showStretch: true, | ||
| 79 | + showScreenshot: true, | ||
| 80 | + showRecord: true, | ||
| 81 | + showZoom: true, | ||
| 82 | + showFullscreen: true, | ||
| 84 | } | 83 | } |
| 85 | }; | 84 | }; |
| 86 | }, | 85 | }, |
| @@ -91,14 +90,12 @@ export default { | @@ -91,14 +90,12 @@ export default { | ||
| 91 | if (typeof url === 'string') return url.length > 0; | 90 | if (typeof url === 'string') return url.length > 0; |
| 92 | return !!url.videoUrl; | 91 | return !!url.videoUrl; |
| 93 | }, | 92 | }, |
| 94 | - // 生成控制 CSS 类名 | ||
| 95 | playerClassOptions() { | 93 | playerClassOptions() { |
| 96 | const c = this.controlsConfig; | 94 | const c = this.controlsConfig; |
| 97 | return { | 95 | return { |
| 98 | 'hide-bottom-bar': !c.showBottomBar, | 96 | 'hide-bottom-bar': !c.showBottomBar, |
| 99 | - 'hide-speed': !c.showSpeed, // 对应下方 CSS | ||
| 100 | - 'hide-code-select': !c.showCodeSelect, // 对应下方 CSS | ||
| 101 | - | 97 | + 'hide-speed': !c.showSpeed, |
| 98 | + 'hide-code-select': !c.showCodeSelect, | ||
| 102 | 'hide-btn-play': !c.showPlay, | 99 | 'hide-btn-play': !c.showPlay, |
| 103 | 'hide-btn-audio': !c.showAudio, | 100 | 'hide-btn-audio': !c.showAudio, |
| 104 | 'hide-btn-stretch': !c.showStretch, | 101 | 'hide-btn-stretch': !c.showStretch, |
| @@ -113,25 +110,35 @@ export default { | @@ -113,25 +110,35 @@ export default { | ||
| 113 | initialPlayUrl: { | 110 | initialPlayUrl: { |
| 114 | handler(newUrl) { | 111 | handler(newUrl) { |
| 115 | const url = typeof newUrl === 'string' ? newUrl : (newUrl && newUrl.videoUrl) || ''; | 112 | const url = typeof newUrl === 'string' ? newUrl : (newUrl && newUrl.videoUrl) || ''; |
| 113 | + | ||
| 116 | if (url) { | 114 | if (url) { |
| 115 | + // 有地址:准备播放 | ||
| 117 | this.isLoading = true; | 116 | this.isLoading = true; |
| 118 | this.isError = false; | 117 | this.isError = false; |
| 119 | - this.$nextTick(() => { | ||
| 120 | - if (this.playerInstance) this.destroy(); | ||
| 121 | - setTimeout(() => { | 118 | + |
| 119 | + if (this.playerInstance) { | ||
| 120 | + // 实例已存在(比如轮播切换下一路),直接切换地址,不要销毁 | ||
| 121 | + this.play(url); | ||
| 122 | + } else { | ||
| 123 | + // 实例不存在(比如从全部关闭状态恢复),创建并播放 | ||
| 124 | + this.$nextTick(() => { | ||
| 122 | this.create(); | 125 | this.create(); |
| 123 | - this.play(url); | ||
| 124 | - }, 50); | ||
| 125 | - }); | 126 | + setTimeout(() => this.play(url), 100); |
| 127 | + }); | ||
| 128 | + } | ||
| 126 | } else { | 129 | } else { |
| 130 | + // 地址为空(全部关闭):彻底销毁 | ||
| 127 | this.destroy(); | 131 | this.destroy(); |
| 128 | this.isLoading = false; | 132 | this.isLoading = false; |
| 133 | + this.isError = false; | ||
| 129 | } | 134 | } |
| 130 | }, | 135 | }, |
| 131 | immediate: true | 136 | immediate: true |
| 132 | }, | 137 | }, |
| 133 | hasAudio() { | 138 | hasAudio() { |
| 134 | - if (this.hasUrl) this.destroyAndReplay(this.initialPlayUrl); | 139 | + if (this.hasUrl && this.playerInstance) { |
| 140 | + this.destroyAndReplay(this.initialPlayUrl); | ||
| 141 | + } | ||
| 135 | } | 142 | } |
| 136 | }, | 143 | }, |
| 137 | beforeDestroy() { | 144 | beforeDestroy() { |
| @@ -156,16 +163,20 @@ export default { | @@ -156,16 +163,20 @@ export default { | ||
| 156 | 163 | ||
| 157 | create() { | 164 | create() { |
| 158 | if (this.playerInstance) return; | 165 | if (this.playerInstance) return; |
| 159 | - const container = this.$refs.container; | ||
| 160 | - if (!container) return; | ||
| 161 | - container.innerHTML = ''; | ||
| 162 | 166 | ||
| 163 | - if (container.clientWidth === 0 && this.retryCount < 5) { | ||
| 164 | - this.retryCount++; | ||
| 165 | - setTimeout(() => this.create(), 200); | 167 | + const container = this.$refs.container; |
| 168 | + // 容错:如果容器不存在,或者刚才被销毁还没渲染出来,延迟重试 | ||
| 169 | + if (!container || container.clientWidth === 0) { | ||
| 170 | + if (this.retryCount < 10) { // 增加重试次数 | ||
| 171 | + this.retryCount++; | ||
| 172 | + setTimeout(() => this.create(), 100); | ||
| 173 | + } | ||
| 166 | return; | 174 | return; |
| 167 | } | 175 | } |
| 168 | 176 | ||
| 177 | + // 再次确保清空内容 | ||
| 178 | + container.innerHTML = ''; | ||
| 179 | + | ||
| 169 | if (!window.EasyPlayerPro) return; | 180 | if (!window.EasyPlayerPro) return; |
| 170 | 181 | ||
| 171 | try { | 182 | try { |
| @@ -178,10 +189,8 @@ export default { | @@ -178,10 +189,8 @@ export default { | ||
| 178 | WCS: true, | 189 | WCS: true, |
| 179 | hasAudio: this.hasAudio, | 190 | hasAudio: this.hasAudio, |
| 180 | isLive: true, | 191 | isLive: true, |
| 181 | - loading: false, | ||
| 182 | - isBand: true, // 保持开启以获取数据 | ||
| 183 | - | ||
| 184 | - // btns 配置只能控制原生有开关的按钮 | 192 | + loading: false, // 使用我们的自定义 loading |
| 193 | + isBand: true, | ||
| 185 | btns: { | 194 | btns: { |
| 186 | play: c.showPlay, | 195 | play: c.showPlay, |
| 187 | audio: c.showAudio, | 196 | audio: c.showAudio, |
| @@ -200,9 +209,11 @@ export default { | @@ -200,9 +209,11 @@ export default { | ||
| 200 | }); | 209 | }); |
| 201 | 210 | ||
| 202 | this.playerInstance.on('play', () => { | 211 | this.playerInstance.on('play', () => { |
| 212 | + console.log(`[${this.uniqueId}] 播放成功`); | ||
| 203 | this.hasStarted = true; | 213 | this.hasStarted = true; |
| 204 | this.isLoading = false; | 214 | this.isLoading = false; |
| 205 | this.isError = false; | 215 | this.isError = false; |
| 216 | + // 播放成功后显示一下控制栏 | ||
| 206 | this.showControls = true; | 217 | this.showControls = true; |
| 207 | if (this.controlTimer) clearTimeout(this.controlTimer); | 218 | if (this.controlTimer) clearTimeout(this.controlTimer); |
| 208 | this.controlTimer = setTimeout(() => { | 219 | this.controlTimer = setTimeout(() => { |
| @@ -212,61 +223,73 @@ export default { | @@ -212,61 +223,73 @@ export default { | ||
| 212 | 223 | ||
| 213 | this.playerInstance.on('error', (err) => { | 224 | this.playerInstance.on('error', (err) => { |
| 214 | console.error('Player Error:', err); | 225 | console.error('Player Error:', err); |
| 215 | - this.triggerError('流媒体连接失败'); | 226 | + this.triggerError('视频流连接异常'); |
| 216 | }); | 227 | }); |
| 217 | 228 | ||
| 218 | } catch (e) { | 229 | } catch (e) { |
| 219 | - console.error("Create Error:", e); | 230 | + console.error("Create Instance Error:", e); |
| 231 | + // 如果创建报错,可能是容器脏了,执行销毁逻辑刷新DOM | ||
| 232 | + this.destroy(); | ||
| 220 | } | 233 | } |
| 221 | }, | 234 | }, |
| 222 | 235 | ||
| 223 | play(url) { | 236 | play(url) { |
| 224 | if (!url) return; | 237 | if (!url) return; |
| 238 | + | ||
| 225 | if (!this.playerInstance) { | 239 | if (!this.playerInstance) { |
| 226 | this.create(); | 240 | this.create(); |
| 227 | setTimeout(() => this.play(url), 200); | 241 | setTimeout(() => this.play(url), 200); |
| 228 | return; | 242 | return; |
| 229 | } | 243 | } |
| 244 | + | ||
| 230 | this.isLoading = true; | 245 | this.isLoading = true; |
| 231 | this.isError = false; | 246 | this.isError = false; |
| 232 | this.errorMessage = ''; | 247 | this.errorMessage = ''; |
| 233 | 248 | ||
| 249 | + // 设置超时检测 | ||
| 234 | setTimeout(() => { | 250 | setTimeout(() => { |
| 235 | if (this.isLoading) this.triggerError('连接超时,请重试'); | 251 | if (this.isLoading) this.triggerError('连接超时,请重试'); |
| 236 | }, this.loadTimeout); | 252 | }, this.loadTimeout); |
| 237 | 253 | ||
| 238 | this.playerInstance.play(url).catch(e => { | 254 | this.playerInstance.play(url).catch(e => { |
| 255 | + console.warn("Play error:", e); | ||
| 239 | this.triggerError('请求播放失败'); | 256 | this.triggerError('请求播放失败'); |
| 240 | }); | 257 | }); |
| 241 | }, | 258 | }, |
| 242 | 259 | ||
| 260 | + // 【核心修复】彻底销毁并刷新DOM ID | ||
| 243 | destroy() { | 261 | destroy() { |
| 262 | + // 1. 重置状态 | ||
| 244 | this.hasStarted = false; | 263 | this.hasStarted = false; |
| 245 | this.showControls = false; | 264 | this.showControls = false; |
| 246 | this.netSpeed = '0KB/s'; | 265 | this.netSpeed = '0KB/s'; |
| 266 | + this.isLoading = false; | ||
| 267 | + this.isError = false; | ||
| 268 | + | ||
| 247 | const container = this.$refs.container; | 269 | const container = this.$refs.container; |
| 248 | - if (container) { | ||
| 249 | - const video = container.querySelector('video'); | ||
| 250 | - if (video) { | ||
| 251 | - video.pause(); | ||
| 252 | - video.src = ""; | ||
| 253 | - video.load(); | ||
| 254 | - video.remove(); | ||
| 255 | - } | ||
| 256 | - container.innerHTML = ''; | ||
| 257 | - } | 270 | + |
| 271 | + // 2. 销毁实例 | ||
| 258 | if (this.playerInstance) { | 272 | if (this.playerInstance) { |
| 259 | try { | 273 | try { |
| 260 | this.playerInstance.destroy(); | 274 | this.playerInstance.destroy(); |
| 261 | - } catch (e) { | ||
| 262 | - } | 275 | + } catch(e) {} |
| 263 | this.playerInstance = null; | 276 | this.playerInstance = null; |
| 264 | } | 277 | } |
| 278 | + | ||
| 279 | + // 3. 暴力清理旧 DOM | ||
| 280 | + if (container) { | ||
| 281 | + container.innerHTML = ''; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + // 4. 【关键】更新 uniqueId | ||
| 285 | + // 这会强制 Vue 在下一次渲染时移除当前的 div,并创建一个全新的 div | ||
| 286 | + // 从而彻底解决 "EasyPlayerPro err container" 错误 | ||
| 287 | + this.uniqueId = `player-box-${Date.now()}-${Math.floor(Math.random() * 1000)}`; | ||
| 265 | }, | 288 | }, |
| 266 | 289 | ||
| 267 | destroyAndReplay(url) { | 290 | destroyAndReplay(url) { |
| 268 | - this.isLoading = true; | ||
| 269 | this.destroy(); | 291 | this.destroy(); |
| 292 | + this.isLoading = true; | ||
| 270 | this.$nextTick(() => { | 293 | this.$nextTick(() => { |
| 271 | this.create(); | 294 | this.create(); |
| 272 | if (url) { | 295 | if (url) { |
| @@ -289,276 +312,93 @@ export default { | @@ -289,276 +312,93 @@ export default { | ||
| 289 | }, | 312 | }, |
| 290 | 313 | ||
| 291 | setControls(config) { | 314 | setControls(config) { |
| 292 | - this.controlsConfig = {...this.controlsConfig, ...config}; | 315 | + this.controlsConfig = { ...this.controlsConfig, ...config }; |
| 293 | } | 316 | } |
| 294 | } | 317 | } |
| 295 | }; | 318 | }; |
| 296 | </script> | 319 | </script> |
| 297 | 320 | ||
| 298 | <style scoped> | 321 | <style scoped> |
| 299 | -/* -------------------------------------------------- | ||
| 300 | - 这里是组件内部样式,仅处理非 EasyPlayer 插件的部分 | ||
| 301 | - -------------------------------------------------- | ||
| 302 | -*/ | 322 | +/* 基础布局 */ |
| 303 | .player-wrapper { | 323 | .player-wrapper { |
| 304 | - width: 100%; | ||
| 305 | - height: 100%; | ||
| 306 | - display: flex; | ||
| 307 | - flex-direction: column; | ||
| 308 | - position: relative; | ||
| 309 | - background: #000; | ||
| 310 | - overflow: hidden; | 324 | + width: 100%; height: 100%; display: flex; flex-direction: column; |
| 325 | + position: relative; background: #000; overflow: hidden; | ||
| 311 | } | 326 | } |
| 312 | - | ||
| 313 | .player-box { | 327 | .player-box { |
| 314 | - flex: 1; | ||
| 315 | - width: 100%; | ||
| 316 | - height: 100%; | ||
| 317 | - background: #000; | ||
| 318 | - position: relative; | ||
| 319 | - z-index: 1; | 328 | + flex: 1; width: 100%; height: 100%; background: #000; position: relative; z-index: 1; |
| 320 | } | 329 | } |
| 321 | 330 | ||
| 322 | /* 顶部栏 */ | 331 | /* 顶部栏 */ |
| 323 | .custom-top-bar { | 332 | .custom-top-bar { |
| 324 | - position: absolute; | ||
| 325 | - top: 0; | ||
| 326 | - left: 0; | ||
| 327 | - width: 100%; | ||
| 328 | - height: 40px; | ||
| 329 | - background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0)); | ||
| 330 | - z-index: 200; | ||
| 331 | - display: flex; | ||
| 332 | - justify-content: space-between; | ||
| 333 | - align-items: center; | ||
| 334 | - padding: 0 15px; | ||
| 335 | - box-sizing: border-box; | ||
| 336 | - pointer-events: none; | ||
| 337 | - transition: opacity 0.3s ease; | ||
| 338 | -} | ||
| 339 | - | ||
| 340 | -.custom-top-bar.hide-bar { | ||
| 341 | - opacity: 0; | ||
| 342 | -} | ||
| 343 | - | ||
| 344 | -.top-bar-left .video-title { | ||
| 345 | - color: #fff; | ||
| 346 | - font-size: 14px; | ||
| 347 | - font-weight: bold; | ||
| 348 | -} | ||
| 349 | - | ||
| 350 | -.top-bar-right .net-speed { | ||
| 351 | - color: #00ff00; | ||
| 352 | - font-size: 12px; | ||
| 353 | - font-family: monospace; | ||
| 354 | -} | ||
| 355 | - | ||
| 356 | -/* 蒙层 */ | ||
| 357 | -.status-mask { | ||
| 358 | - position: absolute; | ||
| 359 | - top: 0; | ||
| 360 | - left: 0; | ||
| 361 | - width: 100%; | ||
| 362 | - height: 100%; | ||
| 363 | - background-color: #000; | ||
| 364 | - z-index: 50; | ||
| 365 | - display: flex; | ||
| 366 | - flex-direction: column; | ||
| 367 | - align-items: center; | ||
| 368 | - justify-content: center; | ||
| 369 | -} | ||
| 370 | - | 333 | + position: absolute; top: 0; left: 0; width: 100%; height: 40px; |
| 334 | + background: linear-gradient(to bottom, rgba(0,0,0,0.8), rgba(0,0,0,0)); | ||
| 335 | + z-index: 200; display: flex; justify-content: space-between; align-items: center; | ||
| 336 | + padding: 0 15px; box-sizing: border-box; pointer-events: none; transition: opacity 0.3s ease; | ||
| 337 | +} | ||
| 338 | +.custom-top-bar.hide-bar { opacity: 0; } | ||
| 339 | +.top-bar-left .video-title { color: #fff; font-size: 14px; font-weight: bold; } | ||
| 340 | +.top-bar-right .net-speed { color: #00ff00; font-size: 12px; font-family: monospace; } | ||
| 341 | + | ||
| 342 | +/* 无信号遮罩 | ||
| 343 | + z-index 必须高于 player-box (z-index 1) | ||
| 344 | + 并且背景设为纯黑,以盖住可能残留的画面 | ||
| 345 | +*/ | ||
| 371 | .idle-mask { | 346 | .idle-mask { |
| 372 | - position: absolute; | ||
| 373 | - top: 0; | ||
| 374 | - left: 0; | ||
| 375 | - width: 100%; | ||
| 376 | - height: 100%; | ||
| 377 | - background-color: #000; | ||
| 378 | - z-index: 15; | ||
| 379 | - display: flex; | ||
| 380 | - align-items: center; | ||
| 381 | - justify-content: center; | ||
| 382 | -} | ||
| 383 | - | ||
| 384 | -.idle-text { | ||
| 385 | - color: #555; | ||
| 386 | - font-size: 14px; | ||
| 387 | -} | ||
| 388 | - | ||
| 389 | -.status-text { | ||
| 390 | - color: #fff; | ||
| 391 | - margin-top: 15px; | ||
| 392 | - font-size: 14px; | ||
| 393 | - letter-spacing: 1px; | 347 | + position: absolute; top: 0; left: 0; width: 100%; height: 100%; |
| 348 | + background-color: #000; z-index: 10; | ||
| 349 | + display: flex; align-items: center; justify-content: center; | ||
| 394 | } | 350 | } |
| 351 | +.idle-text { color: #555; font-size: 14px; } | ||
| 395 | 352 | ||
| 396 | -.error-content { | ||
| 397 | - display: flex; | ||
| 398 | - flex-direction: column; | ||
| 399 | - align-items: center; | ||
| 400 | - justify-content: center; | ||
| 401 | -} | ||
| 402 | - | ||
| 403 | -.error-text { | ||
| 404 | - color: #ff6d6d; | ||
| 405 | -} | ||
| 406 | - | ||
| 407 | -/* Loading 动画 */ | ||
| 408 | -.spinner-box { | ||
| 409 | - width: 50px; | ||
| 410 | - height: 50px; | ||
| 411 | - display: flex; | ||
| 412 | - justify-content: center; | ||
| 413 | - align-items: center; | 353 | +/* 状态蒙层 */ |
| 354 | +.status-mask { | ||
| 355 | + position: absolute; top: 0; left: 0; width: 100%; height: 100%; | ||
| 356 | + background-color: #000; z-index: 50; | ||
| 357 | + display: flex; flex-direction: column; align-items: center; justify-content: center; | ||
| 414 | } | 358 | } |
| 359 | +.status-text { color: #fff; margin-top: 15px; font-size: 14px; letter-spacing: 1px; } | ||
| 360 | +.error-content { display: flex; flex-direction: column; align-items: center; justify-content: center; } | ||
| 361 | +.error-text { color: #ff6d6d; } | ||
| 415 | 362 | ||
| 363 | +/* Loading */ | ||
| 364 | +.spinner-box { width: 50px; height: 50px; display: flex; justify-content: center; align-items: center; } | ||
| 416 | .simple-spinner { | 365 | .simple-spinner { |
| 417 | - width: 40px; | ||
| 418 | - height: 40px; | ||
| 419 | - border: 3px solid rgba(255, 255, 255, 0.2); | ||
| 420 | - border-radius: 50%; | ||
| 421 | - border-top-color: #409EFF; | ||
| 422 | - animation: spin 0.8s linear infinite; | ||
| 423 | -} | ||
| 424 | - | ||
| 425 | -@keyframes spin { | ||
| 426 | - to { | ||
| 427 | - transform: rotate(360deg); | ||
| 428 | - } | 366 | + width: 40px; height: 40px; border: 3px solid rgba(255, 255, 255, 0.2); |
| 367 | + border-radius: 50%; border-top-color: #409EFF; animation: spin 0.8s linear infinite; | ||
| 429 | } | 368 | } |
| 369 | +@keyframes spin { to { transform: rotate(360deg); } } | ||
| 430 | </style> | 370 | </style> |
| 431 | 371 | ||
| 432 | <style> | 372 | <style> |
| 433 | -/* 1. 控制网速显示显隐 */ | ||
| 434 | -.player-wrapper.hide-speed .easyplayer-speed { | ||
| 435 | - display: none !important; | ||
| 436 | -} | ||
| 437 | - | ||
| 438 | -/* 2. 控制解码面板显隐 */ | ||
| 439 | -.player-wrapper.hide-code-select .easyplayer-controls-code-wrap { | ||
| 440 | - display: none !important; | ||
| 441 | -} | ||
| 442 | - | ||
| 443 | -/* 3. 控制其他按钮显隐 (基于您提供的 controlsConfig) */ | ||
| 444 | -.player-wrapper.hide-bottom-bar .easyplayer-controls { | ||
| 445 | - display: none !important; | ||
| 446 | -} | ||
| 447 | - | ||
| 448 | -/* 播放按钮 */ | ||
| 449 | -.player-wrapper.hide-btn-play .easyplayer-play, | ||
| 450 | -.player-wrapper.hide-btn-play .easyplayer-pause { | ||
| 451 | - display: none !important; | ||
| 452 | -} | ||
| 453 | - | ||
| 454 | -/* 音量按钮 */ | ||
| 455 | -.player-wrapper.hide-btn-audio .easyplayer-audio-box { | ||
| 456 | - display: none !important; | ||
| 457 | -} | ||
| 458 | - | ||
| 459 | -/* 截图按钮 */ | ||
| 460 | -.player-wrapper.hide-btn-screenshot .easyplayer-screenshot { | ||
| 461 | - display: none !important; | ||
| 462 | -} | ||
| 463 | - | ||
| 464 | -/* 全屏按钮 */ | ||
| 465 | -.player-wrapper.hide-btn-fullscreen .easyplayer-fullscreen, | ||
| 466 | -.player-wrapper.hide-btn-fullscreen .easyplayer-fullscreen-exit { | ||
| 467 | - display: none !important; | ||
| 468 | -} | ||
| 469 | - | ||
| 470 | - | ||
| 471 | -/* --- 修正录制按钮样式 (强制应用) --- */ | ||
| 472 | -.player-wrapper.hide-btn-record .easyplayer-record, | ||
| 473 | -.player-wrapper.hide-btn-record .easyplayer-record-stop { | ||
| 474 | - display: none !important; | ||
| 475 | -} | ||
| 476 | - | ||
| 477 | -/* 强制覆盖录制按钮位置 */ | 373 | +.player-wrapper.hide-speed .easyplayer-speed { display: none !important; } |
| 374 | +.player-wrapper.hide-code-select .easyplayer-controls-code-wrap { display: none !important; } | ||
| 375 | +.player-wrapper.hide-bottom-bar .easyplayer-controls { display: none !important; } | ||
| 376 | +.player-wrapper.hide-btn-play .easyplayer-play, .player-wrapper.hide-btn-play .easyplayer-pause { display: none !important; } | ||
| 377 | +.player-wrapper.hide-btn-audio .easyplayer-audio-box { display: none !important; } | ||
| 378 | +.player-wrapper.hide-btn-screenshot .easyplayer-screenshot { display: none !important; } | ||
| 379 | +.player-wrapper.hide-btn-fullscreen .easyplayer-fullscreen, .player-wrapper.hide-btn-fullscreen .easyplayer-fullscreen-exit { display: none !important; } | ||
| 380 | +.player-wrapper.hide-btn-record .easyplayer-record, .player-wrapper.hide-btn-record .easyplayer-record-stop { display: none !important; } | ||
| 478 | .player-wrapper .easyplayer-recording { | 381 | .player-wrapper .easyplayer-recording { |
| 479 | - display: none; | ||
| 480 | - position: absolute !important; | ||
| 481 | - top: 50px !important; | ||
| 482 | - left: 50% !important; | ||
| 483 | - transform: translateX(-50%) !important; | ||
| 484 | - z-index: 200 !important; | ||
| 485 | - background: rgba(255, 0, 0, 0.6); | ||
| 486 | - border-radius: 4px; | ||
| 487 | - padding: 4px 12px; | ||
| 488 | -} | ||
| 489 | - | ||
| 490 | -.player-wrapper .easyplayer-recording[style*="block"] { | ||
| 491 | - display: flex !important; | ||
| 492 | - align-items: center !important; | ||
| 493 | - justify-content: center !important; | ||
| 494 | -} | ||
| 495 | - | ||
| 496 | -.player-wrapper .easyplayer-recording-time { | ||
| 497 | - margin: 0 8px; | ||
| 498 | - font-size: 14px; | ||
| 499 | - color: #fff; | ||
| 500 | -} | ||
| 501 | - | ||
| 502 | -.player-wrapper .easyplayer-recording-stop { | ||
| 503 | - height: auto !important; | ||
| 504 | - cursor: pointer; | ||
| 505 | -} | ||
| 506 | - | ||
| 507 | - | ||
| 508 | -/* --- 修正拉伸按钮样式 (SVG图标) --- */ | ||
| 509 | -.player-wrapper.hide-btn-stretch .easyplayer-stretch { | ||
| 510 | - display: none !important; | ||
| 511 | -} | ||
| 512 | - | ||
| 513 | -.player-wrapper .easyplayer-stretch { | ||
| 514 | - font-size: 0 !important; | ||
| 515 | - width: 34px !important; | ||
| 516 | - height: 100% !important; | ||
| 517 | - display: flex !important; | ||
| 518 | - align-items: center; | ||
| 519 | - justify-content: center; | ||
| 520 | - cursor: pointer; | ||
| 521 | -} | ||
| 522 | - | 382 | + display: none; position: absolute !important; top: 50px !important; left: 50% !important; |
| 383 | + transform: translateX(-50%) !important; z-index: 200 !important; | ||
| 384 | + background: rgba(255, 0, 0, 0.6); border-radius: 4px; padding: 4px 12px; | ||
| 385 | +} | ||
| 386 | +.player-wrapper .easyplayer-recording[style*="block"] { display: flex !important; align-items: center !important; justify-content: center !important; } | ||
| 387 | +.player-wrapper .easyplayer-recording-time { margin: 0 8px; font-size: 14px; color: #fff; } | ||
| 388 | +.player-wrapper .easyplayer-recording-stop { height: auto !important; cursor: pointer; } | ||
| 389 | +.player-wrapper.hide-btn-stretch .easyplayer-stretch { display: none !important; } | ||
| 390 | +.player-wrapper .easyplayer-stretch { font-size: 0 !important; width: 34px !important; height: 100% !important; display: flex !important; align-items: center; justify-content: center; cursor: pointer; } | ||
| 523 | .player-wrapper .easyplayer-stretch::after { | 391 | .player-wrapper .easyplayer-stretch::after { |
| 524 | - content: ''; | ||
| 525 | - display: block; | ||
| 526 | - width: 20px; | ||
| 527 | - height: 20px; | 392 | + content: ''; display: block; width: 20px; height: 20px; |
| 528 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M128 128h298.667v85.333H195.2l201.6 201.6-60.373 60.374-201.6-201.6v231.466H49.067V49.067h78.933V128zM896 896H597.333v-85.333H828.8l-201.6-201.6 60.373-60.374 201.6 201.6V518.933h85.334v377.067h-78.934V896z' fill='%23ffffff'/%3E%3C/svg%3E"); | 393 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M128 128h298.667v85.333H195.2l201.6 201.6-60.373 60.374-201.6-201.6v231.466H49.067V49.067h78.933V128zM896 896H597.333v-85.333H828.8l-201.6-201.6 60.373-60.374 201.6 201.6V518.933h85.334v377.067h-78.934V896z' fill='%23ffffff'/%3E%3C/svg%3E"); |
| 529 | - background-repeat: no-repeat; | ||
| 530 | - background-position: center; | ||
| 531 | - background-size: contain; | ||
| 532 | - opacity: 0.8; | ||
| 533 | -} | ||
| 534 | - | ||
| 535 | -.player-wrapper .easyplayer-stretch:hover::after { | ||
| 536 | - opacity: 1; | 394 | + background-repeat: no-repeat; background-position: center; background-size: contain; opacity: 0.8; |
| 537 | } | 395 | } |
| 538 | - | ||
| 539 | - | ||
| 540 | -/* --- 修正电子放大样式 --- */ | ||
| 541 | -.player-wrapper.hide-btn-zoom .easyplayer-zoom, | ||
| 542 | -.player-wrapper.hide-btn-zoom .easyplayer-zoom-stop { | ||
| 543 | - display: none !important; | ||
| 544 | -} | ||
| 545 | - | 396 | +.player-wrapper .easyplayer-stretch:hover::after { opacity: 1; } |
| 397 | +.player-wrapper.hide-btn-zoom .easyplayer-zoom, .player-wrapper.hide-btn-zoom .easyplayer-zoom-stop { display: none !important; } | ||
| 546 | .player-wrapper .easyplayer-zoom-controls { | 398 | .player-wrapper .easyplayer-zoom-controls { |
| 547 | - position: absolute !important; | ||
| 548 | - top: 50px !important; | ||
| 549 | - left: 50% !important; | ||
| 550 | - transform: translateX(-50%); | ||
| 551 | - z-index: 199 !important; | ||
| 552 | - background: rgba(0, 0, 0, 0.6); | ||
| 553 | - border-radius: 20px; | ||
| 554 | - padding: 0 10px; | ||
| 555 | -} | ||
| 556 | - | ||
| 557 | - | ||
| 558 | -/* --- 整体显隐控制 --- */ | ||
| 559 | -.player-wrapper.force-hide-controls .easyplayer-controls { | ||
| 560 | - opacity: 0 !important; | ||
| 561 | - visibility: hidden !important; | ||
| 562 | - transition: opacity 0.3s ease; | 399 | + position: absolute !important; top: 50px !important; left: 50% !important; |
| 400 | + transform: translateX(-50%); z-index: 199 !important; | ||
| 401 | + background: rgba(0,0,0,0.6); border-radius: 20px; padding: 0 10px; | ||
| 563 | } | 402 | } |
| 403 | +.player-wrapper.force-hide-controls .easyplayer-controls { opacity: 0 !important; visibility: hidden !important; transition: opacity 0.3s ease; } | ||
| 564 | </style> | 404 | </style> |