fix:播放器使用ezuikit.js、报警逻辑处理

This commit is contained in:
吉浩茹
2025-10-14 19:58:59 +08:00
parent 59fe43413f
commit 2b5dff94af
36 changed files with 14296 additions and 543 deletions

View File

@ -77,7 +77,7 @@ page {
top: 0;
left: 0;
right: 0;
height: 40px;
height: 50px;
z-index: 1000;
background-color: #ffffff;
padding: 15rpx 20rpx;
@ -111,7 +111,7 @@ page {
.tabbar-content {
flex: 1;
// padding: 0 20rpx; /* 增加底部padding为tabbar留出空间 */
margin-top: 100rpx; /* 为固定头部留出空间,增加距离 */
margin-top: 130rpx; /* 为固定头部留出空间,增加距离 */
overflow-y: auto;
display: flex;
flex-direction: column;
@ -121,6 +121,11 @@ page {
// #endif
}
.header-title {
font-weight: normal;
color: #333;
}
/* 非tabbar页面内容区域 */
.non-tabbar-content {
flex: 1;

View File

@ -319,6 +319,12 @@ export default {
// 空调控制相关
acControlLoading: false, // 空调控制按钮加载状态
// 数据获取定时器
// 上一次数据记录,用于检测变动
previousData: {
temperature: null,
humidity: null,
pm25: null
},
dataFetchInterval: null
}
},
@ -689,78 +695,114 @@ export default {
// 检查从接口获取的数据的报警条件
checkAlertsFromApiData(apiData) {
const currentTime = this.formatDateTime(new Date())
const { temperature, humidity, pm25 } = apiData
const currentTime = this.formatDateTime(new Date())
// 1. 温度报警:使用环境控制设置的区间
if (temperature !== undefined && temperature !== 0 && this.temperatureRange.min !== 0 && this.temperatureRange.max !== 0) {
if (temperature < this.temperatureRange.min || temperature > this.temperatureRange.max) {
const alert = {
content: `当前温度${temperature}°C超出控制范围${this.temperatureRange.min}°C-${this.temperatureRange.max}°C`,
category: "温度超出范围",
alertTime: currentTime,
level: "中危",
action: "检查温度",
actionTime: currentTime,
deviceId: "WSD_001"
}
this.logAlert(alert)
}
}
// 检查各个数据是否有变动,分别处理对应的报警逻辑
const temperatureChanged = this.previousData.temperature !== temperature;
const humidityChanged = this.previousData.humidity !== humidity;
const pm25Changed = this.previousData.pm25 !== pm25;
// 2. 湿度报警:使用环境控制设置的区间
if (humidity !== undefined && humidity !== 0 && this.humidityRange.min !== 0 && this.humidityRange.max !== 0) {
if (humidity < this.humidityRange.min || humidity > this.humidityRange.max) {
const alert = {
content: `当前湿度${humidity}%超出控制范围${this.humidityRange.min}%-${this.humidityRange.max}%`,
category: "湿度超出范围",
alertTime: currentTime,
level: "中危",
action: "检查湿度",
actionTime: currentTime,
deviceId: "WSD_001"
}
this.logAlert(alert)
}
}
// 3. 温度偏差报警:|温湿度计温度 - 空调设定温度| / 空调设定温度 > 30%
if (temperature !== undefined && temperature !== 0 && this.targetTemperature !== 0) {
const temperatureDiff = Math.abs(temperature - this.targetTemperature)
const deviationPercent = (temperatureDiff / this.targetTemperature) * 100
// 只有温度有变化时才检查温度相关报警
if (temperatureChanged) {
console.log('📊 检测到温度数据变化,检查温度相关报警')
if (deviationPercent > 30) {
const alert = {
content: `当前温度${temperature}°C与设定${this.targetTemperature}°C偏差${deviationPercent.toFixed(1)}%`,
category: "温度偏差过大",
alertTime: currentTime,
level: "中危",
action: "调整空调设定温度",
actionTime: currentTime,
deviceId: "AC_001"
// 1. 温度报警:使用环境控制设置的区间
if (temperature !== undefined && temperature !== 0 && this.temperatureRange.min !== 0 && this.temperatureRange.max !== 0) {
if (temperature < this.temperatureRange.min || temperature > this.temperatureRange.max) {
const alert = {
content: `当前温度${temperature}°C超出控制范围${this.temperatureRange.min}°C-${this.temperatureRange.max}°C`,
category: "温度超出范围",
alertTime: currentTime,
level: "中危",
action: "检查温度",
actionTime: currentTime,
deviceId: "WSD_001"
}
this.logAlert(alert)
}
this.logAlert(alert)
}
// 3. 温度偏差报警:|温湿度计温度 - 空调设定温度| / 空调设定温度 > 30%
if (temperature !== undefined && temperature !== 0 && this.targetTemperature !== 0) {
const temperatureDiff = Math.abs(temperature - this.targetTemperature)
const deviationPercent = (temperatureDiff / this.targetTemperature) * 100
if (deviationPercent > 30) {
const alert = {
content: `当前温度${temperature}°C与设定${this.targetTemperature}°C偏差${deviationPercent.toFixed(1)}%`,
category: "温度偏差过大",
alertTime: currentTime,
level: "中危",
action: "调整空调设定温度",
actionTime: currentTime,
deviceId: "AC_001"
}
this.logAlert(alert)
}
}
} else {
console.log('📊 温度数据无变化,跳过温度报警检查')
}
// 4. 湿度偏差报警:|温湿度计湿度 - 空调设定湿度| / 空调设定湿度 > 30%
if (humidity !== undefined && humidity !== 0 && this.targetHumidity !== 0) {
const humidityDiff = Math.abs(humidity - this.targetHumidity)
const deviationPercent = (humidityDiff / this.targetHumidity) * 100
// 只有湿度有变化时才检查湿度相关报警
if (humidityChanged) {
console.log('📊 检测到湿度数据变化,检查湿度相关报警')
if (deviationPercent > 30) {
const alert = {
content: `当前湿度${humidity}%与设定${this.targetHumidity}%偏差${deviationPercent.toFixed(1)}%`,
category: "湿度偏差过大",
alertTime: currentTime,
level: "中危",
action: "调整空调设定湿度",
actionTime: currentTime,
deviceId: "AC_001"
// 2. 湿度报警:使用环境控制设置的区间
if (humidity !== undefined && humidity !== 0 && this.humidityRange.min !== 0 && this.humidityRange.max !== 0) {
if (humidity < this.humidityRange.min || humidity > this.humidityRange.max) {
const alert = {
content: `当前湿度${humidity}%超出控制范围${this.humidityRange.min}%-${this.humidityRange.max}%`,
category: "湿度超出范围",
alertTime: currentTime,
level: "中危",
action: "检查湿度",
actionTime: currentTime,
deviceId: "WSD_001"
}
this.logAlert(alert)
}
this.logAlert(alert)
}
// 4. 湿度偏差报警:|温湿度计湿度 - 空调设定湿度| / 空调设定湿度 > 30%
if (humidity !== undefined && humidity !== 0 && this.targetHumidity !== 0) {
const humidityDiff = Math.abs(humidity - this.targetHumidity)
const deviationPercent = (humidityDiff / this.targetHumidity) * 100
if (deviationPercent > 30) {
const alert = {
content: `当前湿度${humidity}%与设定${this.targetHumidity}%偏差${deviationPercent.toFixed(1)}%`,
category: "湿度偏差过大",
alertTime: currentTime,
level: "中危",
action: "调整空调设定湿度",
actionTime: currentTime,
deviceId: "AC_001"
}
this.logAlert(alert)
}
}
} else {
console.log('📊 湿度数据无变化,跳过湿度报警检查')
}
// PM25相关报警检查如果有的话
if (pm25Changed) {
console.log('📊 检测到PM25数据变化')
// 这里可以添加PM25相关的报警逻辑
}
// 更新上一次的数据
this.updatePreviousData(apiData)
},
// 更新上一次的数据
updatePreviousData(apiData) {
const { temperature, humidity, pm25 } = apiData
this.previousData.temperature = temperature
this.previousData.humidity = humidity
this.previousData.pm25 = pm25
},
// 记录报警到控制台并调用创建告警接口
@ -1369,17 +1411,10 @@ export default {
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.header-title {
font-size: 32rpx;
font-weight: 600;
color: #2c3e50;
text-align: center;
}
/* 内容区域 */
.tabbar-content {
padding: 20rpx 20rpx 120rpx; /* 增加底部padding为tabbar留出空间 */
margin-top: 100rpx; /* 为固定头部留出空间,增加距离 */
margin-top: 150rpx; /* 为固定头部留出空间,增加距离 */
min-height: calc(100vh - 200rpx); /* 调整最小高度计算 */
}

View File

@ -235,13 +235,6 @@ export default {
overflow: hidden;
}
.header-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
}
/* 查询区域样式 */
.query-section {
background: white;

View File

@ -1081,10 +1081,10 @@ export default {
}
.header-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
// font-size: 32rpx;
// font-weight: bold;
// color: #333;
// text-align: center;
}
.tabbar-content {

View File

@ -1,61 +1,71 @@
<template>
<view class="visual-monitoring-page">
<!-- 固定头部 - 有视频时隐藏 -->
<view class="fixed-header">
<!-- <view class="fixed-header">
<text class="header-title">移动式检修车间</text>
</view>
</view> -->
<!-- 内容区域 -->
<view class="tabbar-content">
<!-- <demo /> -->
<view class="no-data-container" v-if="!ezstate">
<view class="no-data-icon">📹</view>
<text class="no-data-text">暂无监控数据</text>
<!-- <view class="no-data-icon">📹</view>
<text class="no-data-text">暂无监控数据</text> -->
</view>
<!-- 视频播放区域 - 保持16:9比例 -->
<view v-else-if="ezstate" :key="videoData" class="video-wrapper">
<view class="video-content">
<!-- 使用简化版播放器 -->
<EzvizVideoPlayer
ref="playerVideoRef"
:show-debug="debugMode"
@playStateChange="handlePlayStateChange"
></EzvizVideoPlayer>
</view>
<!-- 视频播放区域 -->
<view v-else-if="ezstate" class="video-wrapper">
<!-- H5平台使用iframe -->
<!-- #ifdef H5 -->
<iframe
:src="iframeUrl"
class="video-iframe"
frameborder="0"
allow="autoplay; fullscreen"
></iframe>
<!-- #endif -->
<!-- APP平台使用web-view -->
<!-- #ifdef APP-PLUS -->
<web-view
:src="webviewUrl"
@message="handleWebviewMessage"
class="video-webview"
></web-view>
<!-- #endif -->
</view>
<!-- API测试按钮 -->
<view class="test-section" v-if="!ezstate">
<!-- <view class="test-section" v-if="!ezstate">
<button @click="checkDevice" class="test-btn">检查设备状态</button>
<button @click="toggleDebug" class="test-btn">
{{ debugMode ? '关闭调试' : '开启调试' }}
</button>
<button @click="getVideoData" class="test-btn">启动视频播放</button>
</view>
</view> -->
</view>
</view>
</template>
<script>
// 改用简化版播放器
import EzvizVideoPlayer from '@/components/EzvizVideoPlayerSimple.vue'
import tokenManager from '@/utils/ezvizTokenManager.js'
import deviceChecker from '@/utils/ezvizDeviceChecker.js'
export default {
components: {
EzvizVideoPlayer
},
data() {
return {
ezstate:false,
debugMode: true, // 默认开启调试模式
ezstate: false,
debugMode: true,
videoLoaded: false,
isRecording: false,
isPlaying: true, // 播放状态
isPlaying: true,
// 播放器URL配置
iframeUrl: '', // H5平台使用
webviewUrl: '', // APP平台使用
// 设备配置
playUrl: "ezopen://open.ys7.com/FT1718031/1.hd.live",
accessToken: '',
cameraStatus: {
text: '离线',
class: 'offline'
@ -172,30 +182,20 @@ export default {
},
async getVideoData() {
console.log('getVideoData')
console.log('🎬 开始初始化视频播放器...')
try {
let ezuikitInfo = {}
// 使用TokenManager自动获取AccessToken
try {
console.log('🔑 开始获取AccessToken...')
const accessToken = await tokenManager.getValidAccessToken()
ezuikitInfo = {
accessToken: accessToken,
play_url: "ezopen://open.ys7.com/FT1718031/1.hd.live"
}
console.log('✅ 使用自动获取的AccessToken:', accessToken.substring(0, 20) + '...')
this.accessToken = await tokenManager.getValidAccessToken()
console.log('✅ AccessToken获取成功:', this.accessToken.substring(0, 20) + '...')
} catch (error) {
console.error('❌ 自动获取AccessToken失败:', error)
// 如果自动获取失败,使用备用token(需要手动更新)
// 使用备用token
console.log('🔄 使用备用AccessToken')
ezuikitInfo = {
accessToken: "at.4q22023n62a4knwpcx1yxavda1sfqfo5-3ns0ca16sb-1wgwwc3-aj2mctqys",
play_url: "ezopen://open.ys7.com/FT1718031/1.hd.live"
}
this.accessToken = "at.4q22023n62a4knwpcx1yxavda1sfqfo5-3ns0ca16sb-1wgwwc3-aj2mctqys"
uni.showToast({
title: 'AccessToken自动获取失败使用备用token',
@ -204,26 +204,29 @@ export default {
})
}
// 先启用视频状态,让组件渲染
this.ezstate = true
// 等待组件渲染完成后初始化播放器
await this.$nextTick()
// 构建播放器URL
const token = encodeURIComponent(this.accessToken)
const url = encodeURIComponent(this.playUrl)
// 确保ref存在后再调用
if (this.$refs.playerVideoRef) {
this.$refs.playerVideoRef.initEzuikit(ezuikitInfo)
} else {
console.error('❌ 播放器组件未找到')
uni.showToast({
title: '播放器组件加载失败',
icon: 'error',
duration: 2000
})
}
// 根据平台构建不同的URL
// #ifdef H5
// H5平台使用完整的URL路径
this.iframeUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}`
console.log('📱 H5平台 - iframeUrl:', this.iframeUrl)
// #endif
// #ifdef APP-PLUS
// APP平台使用本地路径
this.webviewUrl = `/static/html/ezviz-iframe.html?accessToken=${token}&playUrl=${url}`
console.log('📱 APP平台 - webviewUrl:', this.webviewUrl)
// #endif
// 启用视频状态
this.ezstate = true
console.log('✅ 播放器URL配置完成')
} catch (error) {
console.error('初始化视频失败:', error)
console.error('初始化视频失败:', error)
uni.showToast({
title: '视频初始化失败',
icon: 'none',
@ -232,21 +235,27 @@ export default {
}
},
// 处理webview消息APP平台
handleWebviewMessage(event) {
console.log('📨 收到webview消息:', event)
// 可以在这里处理从播放器HTML页面发来的消息
},
// 停止视频播放器
stopVideoPlayer() {
try {
// 检查播放器组件是否存在
if (this.$refs.playerVideoRef) {
// 调用播放器组件的停止方法
this.$refs.playerVideoRef.stopPlayer()
} else {
console.log('⚠️ 播放器组件不存在,无需停止')
}
console.log('🛑 停止视频播放器')
// 重置页面状态
this.ezstate = false
this.videoLoaded = false
this.isPlaying = false
// 清空URL
this.iframeUrl = ''
this.webviewUrl = ''
console.log('✅ 播放器已停止')
} catch (error) {
console.error('❌ 停止视频播放器失败:', error)
}
@ -265,50 +274,43 @@ export default {
/* 内容区域 */
.tabbar-content {
width: 100%;
height: calc(100vh - 100rpx); /* 减去底部tabbar */
height: 100%;
padding: 30rpx;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 30rpx;
box-sizing: border-box;
margin-top: 10px;
}
/* 视频外层容器 */
.video-wrapper {
width: 100%;
height: 200px;
display: flex;
justify-content: center;
align-items: flex-start;
height: calc(100vh - 180rpx); /* 减去头部和底部tabbar */
// min-height: 400px;
// display: flex;
// justify-content: center;
// align-items: stretch;
// border-radius: 16rpx;
// overflow: hidden;
// box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
// background: #000;
}
/* 视频内容区域 - 保持16:9宽高比不变形 */
.video-content {
/* H5平台的iframe样式 */
.video-iframe {
width: 100%;
max-width: 100%;
position: relative;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
height: 100%;
border: none;
// background: #000;
}
/* APP平台的webview样式 */
.video-webview {
width: 100%;
height: 100%;
background: #000;
background-color: #0056b3;
/* 使用padding-top技巧保持16:9宽高比 */
&::before {
content: '';
display: block;
padding-top: 56.25%; /* 16:9 = 9/16 = 0.5625 = 56.25% */
}
/* 播放器绝对定位填充容器 */
:deep(.simple-video-player) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
/* 无数据状态 */

View File

@ -0,0 +1,371 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>萤石云播放器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
/* background: #1a1a1a; */
position: relative;
}
#player-iframe {
width: 100%;
height: 50%;
border: none;
display: block;
object-fit: contain; /* 保持视频比例,不变形 */
}
/* 简化的控制按钮样式 */
.control-buttons {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 16px;
z-index: 999;
pointer-events: auto;
}
.control-btn {
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 25px;
padding: 12px 20px;
min-width: 100px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
color: white;
font-size: 14px;
font-weight: 500;
text-align: center;
}
.control-btn:active {
background: rgba(0, 0, 0, 0.8);
}
.play-btn {
background: rgba(76, 175, 80, 0.8);
border-color: rgba(76, 175, 80, 0.9);
}
.play-btn:active {
background: rgba(76, 175, 80, 0.9);
}
.refresh-btn {
background: rgba(33, 150, 243, 0.8);
border-color: rgba(33, 150, 243, 0.9);
}
.refresh-btn:active {
background: rgba(33, 150, 243, 0.9);
}
/* 按钮图标样式 */
.btn-icon {
margin-right: 6px;
font-size: 16px;
display: inline-block;
}
.btn-text {
font-weight: 500;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 14px;
text-align: center;
background: rgba(0, 0, 0, 0.7);
padding: 15px 30px;
border-radius: 8px;
z-index: 10;
}
.loading::after {
content: '';
display: block;
width: 20px;
height: 20px;
margin: 10px auto 0;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 简化的暂停占位符样式 */
.pause-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50%;
background: #000000;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 5;
}
.pause-content {
text-align: center;
color: white;
}
.pause-title {
font-size: 18px;
font-weight: 400;
color: white;
}
</style>
</head>
<body>
<div class="loading" id="loading">正在加载播放器...</div>
<iframe id="player-iframe" allow="autoplay; fullscreen"></iframe>
<!-- 暂停时的占位符 -->
<div class="pause-placeholder" id="pause-placeholder">
<div class="pause-content">
<div class="pause-title">监控已暂停</div>
</div>
</div>
<!-- 控制按钮 -->
<div class="control-buttons" id="control-buttons" style="display: none;">
<div class="control-btn play-btn" id="play-btn">
<span class="btn-icon" id="play-icon">▶️</span>
<span class="btn-text" id="play-text">播放</span>
</div>
<div class="control-btn refresh-btn" id="refresh-btn">
<span class="btn-icon">🔄</span>
<span class="btn-text">刷新</span>
</div>
</div>
<script>
function log(message) {
console.log('[iframe播放器] ' + message);
}
// 从URL参数获取配置
function getConfig() {
const params = new URLSearchParams(window.location.search);
return {
accessToken: params.get('accessToken'),
playUrl: params.get('playUrl')
};
}
// 初始化播放器
function init() {
log('初始化开始');
const configData = getConfig();
if (!configData.accessToken || !configData.playUrl) {
log('配置参数不完整');
document.getElementById('loading').textContent = '配置参数错误';
return;
}
// 保存配置到全局变量
config = {
accessToken: configData.accessToken,
playUrl: configData.playUrl
};
log('AccessToken: ' + config.accessToken.substring(0, 20) + '...');
log('PlayUrl: ' + config.playUrl);
try {
// 使用萤石云官方iframe播放器内存占用小
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
'url=' + encodeURIComponent(config.playUrl) +
'&accessToken=' + encodeURIComponent(config.accessToken) +
'&width=100%' +
'&height=100%' +
'&autoplay=1' +
'&audio=1' +
'&controls=1';
log('iframe URL: ' + iframeUrl);
const iframe = document.getElementById('player-iframe');
iframe.src = iframeUrl;
// 设置初始播放状态
isPlaying = true;
document.getElementById('play-text').textContent = '暂停';
document.getElementById('play-icon').textContent = '⏸️';
// 隐藏loading显示控制按钮
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
document.getElementById('control-buttons').style.display = 'flex';
log('播放器加载完成,显示控制按钮');
}, 2000);
} catch (error) {
log('初始化失败: ' + error.message);
document.getElementById('loading').textContent = '初始化失败';
}
}
// 全局状态
let isPlaying = true;
let config = null;
// 播放/暂停控制
function togglePlay() {
log('切换播放状态: ' + (isPlaying ? '暂停' : '播放'));
const iframe = document.getElementById('player-iframe');
const playText = document.getElementById('play-text');
const playIcon = document.getElementById('play-icon');
const pausePlaceholder = document.getElementById('pause-placeholder');
if (isPlaying) {
// 暂停清空iframe显示占位符
iframe.src = 'about:blank';
pausePlaceholder.style.display = 'flex';
playText.textContent = '播放';
playIcon.textContent = '▶️';
isPlaying = false;
log('已暂停播放,显示占位符');
} else {
// 播放重新设置iframe URL隐藏占位符
if (config) {
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
'url=' + encodeURIComponent(config.playUrl) +
'&accessToken=' + encodeURIComponent(config.accessToken) +
'&width=100%' +
'&height=100%' +
'&autoplay=1' +
'&audio=1' +
'&controls=1';
iframe.src = iframeUrl;
pausePlaceholder.style.display = 'none';
playText.textContent = '暂停';
playIcon.textContent = '⏸️';
isPlaying = true;
log('已开始播放,隐藏占位符');
}
}
}
// 刷新播放器
function refreshPlayer() {
log('刷新播放器');
const iframe = document.getElementById('player-iframe');
const playText = document.getElementById('play-text');
const playIcon = document.getElementById('play-icon');
const pausePlaceholder = document.getElementById('pause-placeholder');
// 先清空,显示加载状态
iframe.src = 'about:blank';
pausePlaceholder.style.display = 'none';
// 延迟重新加载
setTimeout(() => {
if (config) {
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
'url=' + encodeURIComponent(config.playUrl) +
'&accessToken=' + encodeURIComponent(config.accessToken) +
'&width=100%' +
'&height=100%' +
'&autoplay=1' +
'&audio=1' +
'&controls=1';
iframe.src = iframeUrl;
playText.textContent = '暂停';
playIcon.textContent = '⏸️';
isPlaying = true;
log('刷新完成');
}
}, 500);
}
// 页面加载完成后初始化
window.onload = function() {
log('页面加载完成');
// 绑定按钮事件
document.getElementById('play-btn').addEventListener('click', togglePlay);
document.getElementById('refresh-btn').addEventListener('click', refreshPlayer);
init();
};
// 页面卸载时清理资源
window.onbeforeunload = function() {
log('页面即将卸载,清理播放器资源');
try {
const iframe = document.getElementById('player-iframe');
if (iframe) {
iframe.src = 'about:blank'; // 清空iframe源
log('播放器资源已清理');
}
} catch (error) {
log('清理播放器资源失败: ' + error.message);
}
};
// 页面隐藏时暂停播放
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
log('页面隐藏,暂停播放');
try {
const iframe = document.getElementById('player-iframe');
if (iframe) {
iframe.style.display = 'none';
}
} catch (error) {
log('暂停播放失败: ' + error.message);
}
} else {
log('页面显示,恢复播放');
try {
const iframe = document.getElementById('player-iframe');
if (iframe) {
iframe.style.display = 'block';
}
} catch (error) {
log('恢复播放失败: ' + error.message);
}
}
});
</script>
</body>
</html>

143
src/static/html/ezuikit.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler:handler,args:args})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}JSPlayerModule(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){Module["__embind_initialize_bindings"]();initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler:handler,args:args})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}JSPlayerModule(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){Module["__embind_initialize_bindings"]();initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,354 @@
.BMap_noprint button[title="倾斜"] {
display: none;
}
.BMap_noprint button[title="恢复"] {
display: none;
}
/* .anchorBL{
display:none;
} */
.BMap_cpyCtrl {
display: none;
}
.inspect-event-item {
padding-left: 12.5px;
position: relative;
border-left: 1px solid #d9d9d9;
margin-left: 20.5px;
padding-bottom: 16px;
color: #595959;
}
.inspect-event-item:last-child {
border-left: 1px solid transparent;
}
.inspect-event-item-header-wrap {
margin-top: -4px;
}
.inspect-event-item:first-child .inspect-event-item-header-wrap{
padding-top: 0;
}
.inspect-event-item-header {
width: 240px;
height: 32px;
background: #F5F5F5;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.inspect-event-item-header-left {
display: flex;
align-items: center;
padding-left: 9px;
}
.inspect-event-item-header-right, .inspect-event-item-body-info-opr {
padding-right: 8px;
}
.inspect-event-item-body-info-opr-icon{
color: #595959;
cursor: pointer;
}
.inspect-event-item-body-info-opr-icon:hover{
color: #407AFF;
}
.inspect-event-item-header-toggle {
width: 16px;
height: 32px;
cursor: pointer;
display: block;
padding: 8px 0;
box-sizing: border-box;
}
.inspect-event-item-time {
padding-left: 9px;
}
.inspect-event-item-status-wrap {
position: absolute;
height: 28px;
background: #fff;
top: 4px;
left: -5.5px;
}
.inspect-event-item:first-child .inspect-event-item-status-wrap {
top: 0;
padding-top: 5px;
}
.inspect-event-item-status {
width: 10px;
height: 10px;
background: #407AFF;
border-radius: 100%;
display: inline-block;
}
.storage .inspect-event-item-status {
background: #FAAD14;
}
.storage-error .inspect-event-item-status{
background: #FF4D4F;
}
.storage-error .inspect-event-item-header-left,
.storage-error .inspect-event-item-time {
color: #FF4D4F;
}
.inspect-event-item-time {
font-size: 12px;
color: #262626;
}
.inspect-event-item-body {
width: 240px;
background: #FAFAFA;
padding-top: 8px;
}
.inspect-event-item-img {
width: 224px;
height: 126px;
margin: 0 8px;
display: block;
cursor: pointer;
}
.inspect-event-item-body-info {
display: flex;
align-items: center;
justify-content: space-between;
}
.inspect-event-item-body-info-tag {
max-width: 140px;
background: #FFF1F0;
border-radius: 2px;
text-align: center;
margin: 8px;
height: 24px;
line-height: 24px;
padding: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.inspect-event-item-body-info-tag-label{
font-size: 12px;
color: #FF4D4F;
}
.inspect-event-detail-wrap {
width: 0;
overflow: auto;
padding: 0;
box-sizing: border-box;
position: absolute;
top: 0;
right: 0;
z-index: 9;
background: #ffffff;
transition: width 0.15s ease 0s;
}
.inspect-event-detail-wrap.show {
width: 290px;
}
.inspectEventDetail-header {
display: flex;
align-items: center;
padding: 16px 16px;
}
.inspectEventDetail-back {
fill: #595959;
cursor: pointer;
margin-right: 8px;
display: flex;
align-items: center;
}
.inspectEventDetail-type {
height: 22px;
font-size: 14px;
color: #262626;
line-height: 22px;
font-weight: bold;
}
.inspectEventDetail-content {
padding: 0 16px 24px;
box-sizing: border-box;
}
.inspectEventDetail-content-info {
font-size: 14px;
color: #262626;
line-height: 22px;
overflow: hidden;
}
.inspectEventDetail-content-info-item-title {
margin: 24px 0 4px;
}
.inspectEventDetail-content-info-item-value {
color: #595959;
}
.inspectEventDetail-content-info-item-title-required:before {
display: inline-block;
margin-right: 4px;
color: #ff4d4f;
font-size: 14px;
font-family: SimSun,sans-serif;
line-height: 1;
content: "*";
}
.inspectEventDetail-footer {
position: absolute;
bottom: 0;
left: 0;
background: #fff;
padding: 16px 0;
right: 0;
text-align: center;
}
.inspectEventDetail-footer-btn {
margin: 0 4px;
}
.inspectEventDetail-content-img-tips,
.inspectEventDetail-content-video-tips {
font-size: 14px;
color: #407AFF;
line-height: 22px;
margin: 0 0 8px;
}
.inspectEventDetail-content-img-detail,
.inspectEventDetail-content-video-detail {
display: block;
width: 224px;
height: 126px;
cursor: pointer;
}
.video-stroage-exceptional-status-tips-wrap {
width: 100%;
height: 100%;
color: #fff;
text-align: center;
cursor: default;
}
.video-stroage-exceptional-status-icon {
font-size: 16px;
padding: 40px 0 0px;
display: flex;
justify-content: center;
}
.video-stroage-exceptional-status-tips-error .video-stroage-exceptional-status-icon{
padding: 40px 0 8px;
}
.video-stroage-exceptional-status-tips {
font-size: 14px;
line-height: 22px;
}
.inspectEventDetail-content-video-timer {
display: flex;
align-items: center;
}
.video-recording-time-wrap {
height: 24px;
background: #FF5C5C;
border-radius: 12px;
padding: 0 24px;
line-height: 24px;
display: flex;
align-items: center;
color: #fff;
}
.video-recording-time {
font-size: 14px;
color: #FFFFFF;
letter-spacing: 0;
font-weight: 400;
margin-left: 8px;
}
.inspectEventDetail-stop-btn {
margin-left: 12px;
}
.inspectEventDetail-delete-confirm,
.inspectEventDetail-back-confirm {
width: 288px;
padding: 12px 8px;
box-sizing: border-box;
}
.inspectEventDetail-delete-confirm-title,
.inspectEventDetail-back-confirm-title {
display: flex;
}
.inspectEventDetail-delete-confirm-title-label,
.inspectEventDetail-back-confirm-title-label {
font-size: 16px;
color: #262626;
line-height: 24px;
font-weight: bold;
}
.inspectEventDetail-delete-confirm-title svg,
.inspectEventDetail-back-confirm-title svg {
color: #FAAD14 ;
margin-right: 16px;
}
.inspectEventDetail-delete-confirm-btns,
.inspectEventDetail-back-confirm-btns {
margin: 24px 0 0;
text-align: right;
}
.inspectEventDetail-delete-confirm-btns .ezuikit-btn,
.inspectEventDetail-back-confirm-btns .ezuikit-btn {
margin: 0 4px;
}
/* .inspect-event-box及其子元素滚动条效果设置 */
.inspect-event-box::-webkit-scrollbar,
.inspect-event-box *::-webkit-scrollbar {
width: 10px
}
.inspect-event-box::-webkit-scrollbar-thumb,
.inspect-event-box *::-webkit-scrollbar-thumb {
border-radius: 10px!important;
-webkit-box-shadow: inset 0 0 5px #8C8C8C!important;
background: #8C8C8C!important;
border: 3px solid #fff!important
}
.inspect-event-box::-webkit-scrollbar-track,
.inspect-event-box *::-webkit-scrollbar-track {
border-radius: 0
}

View File

@ -0,0 +1,167 @@
.footer-controls .theme-icon-item {
margin: 0 1%;
}
.footer-controls.themeEditing .theme-icon-item {
position: relative;
/* margin: 0 8px; */
}
.footer-controls .footer-controls-left {
margin-left: 12px;
}
.footer-controls.themeEditing .footer-controls-left .theme-icon-item {
/* margin-left: 12px; */
}
.footer-controls.themeEditing .footer-controls-right .theme-icon-item {
/* margin-right: 12px; */
}
.footer-controls .theme-icon-item .icon-move {
display: none;
}
.footer-controls.themeEditing .theme-icon-item:hover .icon-move {
display: block;
}
.footer-controls.themeEditing
.footer-controls-left
.theme-icon-item:first-child
.icon-move.left {
display: none;
}
.footer-controls.themeEditing
.footer-controls-left
.theme-icon-item:nth-last-child(1)
.icon-move.right {
display: none;
}
.footer-controls .footer-controls-right {
margin-right: 12px;
}
.footer-controls.themeEditing
.footer-controls-right
.theme-icon-item:first-child
.icon-move.left {
display: none;
}
.footer-controls.themeEditing
.footer-controls-right
.theme-icon-item:nth-last-child(1)
.icon-move.right {
display: none;
}
.footer-controls .theme-icon-item-icon {
position: relative;
}
.header-controls .theme-icon-item {
margin: 0 1%;
}
.header-controls.themeEditing .theme-icon-item {
position: relative;
/* margin: 0 8px; */
}
.header-controls.themeEditing .header-controls-left {
margin-left: 12px;
}
.header-controls.themeEditing .header-controls-right {
margin-right: 12px;
}
.header-controls .theme-icon-item .icon-move {
display: none;
}
.header-controls.themeEditing .theme-icon-item:hover .icon-move {
display: block;
}
.header-controls.themeEditing
.header-controls-left
.theme-icon-item:first-child
.icon-move.left {
display: none;
}
.header-controls.themeEditing
.header-controls-left
.theme-icon-item:nth-last-child(1)
.icon-move.right {
display: none;
}
.header-controls.themeEditing
.header-controls-right
.theme-icon-item:first-child
.icon-move.left {
display: none;
}
.header-controls.themeEditing
.header-controls-right
.theme-icon-item:nth-last-child(1)
.icon-move.right {
display: none;
}
.time-area {
position: absolute;
color: #ffffff;
width: 68px;
height: 24px;
line-height: 24px;
background: rgba(0, 0, 0, 0.5);
border-radius: 12px;
display: none;
align-content: center;
left: calc(50% - 34px);
top: -30px;
align-items: center;
justify-content: space-around;
padding-left: 4px;
padding-right: 6px;
z-index: 9999999;
user-select: none;
}
.time-area .dot {
display: inline-block;
width: 8px;
height: 8px;
background: red;
border-radius: 100%;
margin: 0 4px 1px 4px;
}
/* .footer-controls.themeEditing .theme-icon-item {
position: relative;
margin: 0 8px;
}
.footer-controls.themeEditing .footer-controls-left .theme-icon-item:nth-child(1) .ezuikit-theme-icon >span:first-child{
display: none!important;
}
.footer-controls.themeEditing .footer-controls-left .theme-icon-item:nth-last-child(2) .ezuikit-theme-icon >span:nth-child(3){
display: none!important;
}
.footer-controls.themeEditing .theme-icon-item:hover .ezuikit-theme-icon {
display: block!important;
}
.footer-controls.themeEditing .footer-controls-right .theme-icon-item:nth-child(1) .ezuikit-theme-icon >span:first-child{
display: none!important;
}
.footer-controls.themeEditing .footer-controls-right .theme-icon-item:nth-last-child(1) .ezuikit-theme-icon >span:nth-child(3){
display: none!important;
} */

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1920px" height="1080px" viewBox="0 0 1920 1080" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>视频背景图</title>
<defs>
<rect id="path-1" x="0" y="0" width="1920" height="1080"></rect>
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-2">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
<linearGradient x1="6.42733681%" y1="61.4046072%" x2="84.9937231%" y2="19.1492851%" id="linearGradient-4">
<stop stop-color="#9DC2FE" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#648FFC" stop-opacity="0.129889642" offset="100%"></stop>
</linearGradient>
<linearGradient x1="91.8908498%" y1="82.8715916%" x2="45.1536245%" y2="-4.31442764%" id="linearGradient-5">
<stop stop-color="#9DC2FE" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#648FFC" stop-opacity="0.129889642" offset="100%"></stop>
</linearGradient>
<linearGradient x1="94.5535203%" y1="72.2238976%" x2="-8.96417241%" y2="9.01768502%" id="linearGradient-6">
<stop stop-color="#9DC2FE" stop-opacity="0" offset="0%"></stop>
<stop stop-color="#648FFC" stop-opacity="0.129889642" offset="100%"></stop>
</linearGradient>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Cam-Thumbnail">
<mask id="mask-3" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Cam-Mask" fill="#18191C" filter="url(#filter-2)" xlink:href="#path-1"></use>
<circle id="椭圆形" fill="url(#linearGradient-4)" mask="url(#mask-3)" cx="1868.8" cy="870.4" r="394.24"></circle>
<circle id="椭圆形备份-2" fill="url(#linearGradient-5)" mask="url(#mask-3)" cx="0" cy="143.36" r="394.24"></circle>
<ellipse id="椭圆形备份-3" fill-opacity="0.45" fill="url(#linearGradient-6)" mask="url(#mask-3)" cx="427.52" cy="350.72" rx="161.28" ry="158.72"></ellipse>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 225 126" style="enable-background:new 0 0 225 126;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F5F5F5;}
.st1{filter:url(#Adobe_OpacityMaskFilter);}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
.st3{mask:url(#mask-2_1_);}
.st4{fill:#BFBFBF;}
.st5{filter:url(#Adobe_OpacityMaskFilter_1_);}
.st6{mask:url(#mask-2_2_);}
</style>
<title>加载失败@3x</title>
<rect x="0.7" class="st0" width="224" height="126"/>
<defs>
<filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="95.1" y="48.4" width="18.7" height="29.2">
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
</filter>
</defs>
<mask maskUnits="userSpaceOnUse" x="95.1" y="48.4" width="18.7" height="29.2" id="mask-2_1_">
<g class="st1">
<rect id="path-1_1_" x="0.7" class="st2" width="224" height="126"/>
</g>
</mask>
<g class="st3">
<g id="形状结合" transform="translate(94.326510, 48.000000)">
<path class="st4" d="M16.7,0.4c0.1,0,0.2,0,0.2,0l-0.6,2.5L5,2.9c-0.9,0-1.7,0.7-1.7,1.6l0,0.1v12.5l6.4-4.5c1-0.7,2.3-0.7,3.3,0
l0.2,0.1l6.4,5l-0.8,2.6l-7.1-5.6c-0.1-0.1-0.3-0.1-0.4-0.1l-0.1,0l-7.8,5.5v5.2c0,0.9,0.7,1.6,1.6,1.7l0.2,0l11.7,0l-0.7,2.5
L5,29.6c-2.3,0-4.1-1.8-4.2-4l0-0.2V4.6c0-2.3,1.8-4.1,4-4.2l0.2,0H16.7z"/>
</g>
</g>
<defs>
<filter id="Adobe_OpacityMaskFilter_1_" filterUnits="userSpaceOnUse" x="112.8" y="48.4" width="17.3" height="29.2">
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
</filter>
</defs>
<mask maskUnits="userSpaceOnUse" x="112.8" y="48.4" width="17.3" height="29.2" id="mask-2_2_">
<g class="st5">
<rect id="path-1_2_" x="0.7" class="st2" width="224" height="126"/>
</g>
</mask>
<g class="st6">
<g transform="translate(121.479530, 63.000000) rotate(-360.000000) translate(-121.479530, -63.000000) translate(111.979530, 48.000000)">
<path class="st4" d="M13.9,0.4c2.3,0,4.1,1.8,4.2,4l0,0.2v20.7c0,2.3-1.8,4.1-4,4.2l-0.2,0l-13.1,0l0.7-2.5l12.3,0
c0.9,0,1.7-0.7,1.7-1.6l0-0.1V4.6c0-0.9-0.8-1.7-1.8-1.7l-12.6,0l0.6-2.5L13.9,0.4z M4.4,17.7l6.8,5.3c0.5,0.4,0.6,1.2,0.2,1.8
c-0.4,0.5-1.1,0.6-1.6,0.3L9.6,25l-6-4.7L4.4,17.7z M8.6,5.4c2.5,0,4.6,2.1,4.6,4.6s-2.1,4.6-4.6,4.6S4,12.5,4,10S6,5.4,8.6,5.4z
M8.6,7.9c-1.2,0-2.1,0.9-2.1,2.1s0.9,2.1,2.1,2.1c1.2,0,2.1-0.9,2.1-2.1S9.7,7.9,8.6,7.9z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,19 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
(factory(global.jQuery));
}(this, (function ($) {
'use strict';
$.fn.datepicker.languages['en-US'] = {
format: 'yyyy-mm-dd',
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
daysShort: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
daysMin: ['Su', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
weekStart: 1,
yearFirst: true,
yearSuffix: ''
};
})));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
/*!
* Datepicker v1.0.10
* https://fengyuanchen.github.io/datepicker
*
* Copyright 2014-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2020-09-29T14:46:09.037Z
*/
.datepicker-container{background-color:#fff;direction:ltr;font-size:12px;left:0;line-height:30px;position:fixed;-webkit-tap-highlight-color:transparent;top:0;-ms-touch-action:none;touch-action:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:210px;z-index:-1}.datepicker-container:after,.datepicker-container:before{border:5px solid transparent;content:" ";display:block;height:0;position:absolute;width:0}.datepicker-dropdown{border:1px solid #ccc;-webkit-box-shadow:0 3px 6px #ccc;box-shadow:0 3px 6px #ccc;-webkit-box-sizing:content-box;box-sizing:content-box;position:absolute;z-index:1}.datepicker-inline{position:static}.datepicker-top-left,.datepicker-top-right{border-top-color:#39f}.datepicker-top-left:after,.datepicker-top-left:before,.datepicker-top-right:after,.datepicker-top-right:before{border-top:0;left:10px;top:-5px}.datepicker-top-left:before,.datepicker-top-right:before{border-bottom-color:#39f}.datepicker-top-left:after,.datepicker-top-right:after{border-bottom-color:#fff;top:-4px}.datepicker-bottom-left,.datepicker-bottom-right{border-bottom-color:#39f}.datepicker-bottom-left:after,.datepicker-bottom-left:before,.datepicker-bottom-right:after,.datepicker-bottom-right:before{border-bottom:0;bottom:-5px;left:10px}.datepicker-bottom-left:before,.datepicker-bottom-right:before{border-top-color:#39f}.datepicker-bottom-left:after,.datepicker-bottom-right:after{border-top-color:#fff;bottom:-4px}.datepicker-bottom-right:after,.datepicker-bottom-right:before,.datepicker-top-right:after,.datepicker-top-right:before{left:auto;right:10px}.datepicker-panel>ul{margin:0;padding:0;width:102%}.datepicker-panel>ul:after,.datepicker-panel>ul:before{content:" ";display:table}.datepicker-panel>ul:after{clear:both}.datepicker-panel>ul>li{background-color:#fff;cursor:pointer;float:left;height:30px;list-style:none;margin:0;padding:0;text-align:center;width:30px}.datepicker-panel>ul>li:hover{background-color:#e5f2ff}.datepicker-panel>ul>li.muted,.datepicker-panel>ul>li.muted:hover{color:#999}.datepicker-panel>ul>li.highlighted{background-color:#e5f2ff}.datepicker-panel>ul>li.highlighted:hover{background-color:#cce5ff}.datepicker-panel>ul>li.picked,.datepicker-panel>ul>li.picked:hover{color:#39f}.datepicker-panel>ul>li.disabled,.datepicker-panel>ul>li.disabled:hover{background-color:#fff;color:#ccc;cursor:default}.datepicker-panel>ul>li.disabled.highlighted,.datepicker-panel>ul>li.disabled:hover.highlighted{background-color:#e5f2ff}.datepicker-panel>ul>li[data-view="month next"],.datepicker-panel>ul>li[data-view="month prev"],.datepicker-panel>ul>li[data-view="year next"],.datepicker-panel>ul>li[data-view="year prev"],.datepicker-panel>ul>li[data-view="years next"],.datepicker-panel>ul>li[data-view="years prev"],.datepicker-panel>ul>li[data-view=next]{font-size:18px}.datepicker-panel>ul>li[data-view="month current"],.datepicker-panel>ul>li[data-view="year current"],.datepicker-panel>ul>li[data-view="years current"]{width:150px}.datepicker-panel>ul[data-view=months]>li,.datepicker-panel>ul[data-view=years]>li{height:52.5px;line-height:52.5px;width:52.5px}.datepicker-panel>ul[data-view=week]>li,.datepicker-panel>ul[data-view=week]>li:hover{background-color:#fff;cursor:default}.datepicker-hide{display:none}
.datepicker-container {
border-radius: 4px;
}
.datepicker-panel>ul>li {
border-radius: 100%;
color: rgba(0,0,0,0.65);
}
.datepicker-panel>ul>li.picked, .datepicker-panel>ul>li.picked:hover {
color: #39f;
}
.datepicker-inline {
position: absolute;
z-index: 999999;
bottom: 48px;
right: 24px;
height: 250px;
top: auto;
left: auto;
}
.datepicker-panel>ul>li.picked {
background: #1890ff;
color: #fff;
}
.datepicker-dropdown {
box-shadow: none;
}

View File

@ -0,0 +1,19 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
(factory(global.jQuery));
}(this, (function ($) {
'use strict';
$.fn.datepicker.languages['zh-CN'] = {
format: 'yyyy年mm月dd日',
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
daysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
daysMin: ['日', '一', '二', '三', '四', '五', '六'],
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
weekStart: 1,
yearFirst: true,
yearSuffix: '年'
};
})));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,170 @@
.speed-select {
text-align: center;
display: block;
background: rgb(255, 255, 255);
box-shadow: rgb(0 0 0 / 10%) 0px 3px 20px 0px;
border-radius: 2px;
padding: 0px;
width: 100px;
position: absolute;
bottom: 48px;
left: 50%;
transform: translateX(-50%);
color: rgba(0, 0, 0, 0.85);
user-select: none;
}
.speed-select > li {
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
padding: 0 10px;
box-sizing: border-box;
}
.speed-select li:hover {
background: #1890ff1a;
}
.speed-select ::after {
position: absolute;
bottom: -16px;
left: 40%;
content: " ";
border: 8px solid transparent;
border-top: 8px solid #ffffff;
}
/*避免触发onmouseleave事件*/
.speed-select ::before {
position: absolute;
bottom: -16px;
left: 0;
content: " ";
border: 8px solid transparent;
border-top: 8px solid transparent;
width: 100%;
}
.speed-select.mobile {
display: block;
position: fixed;
z-index: 2;
left: 0;
transform: translateX(0);
width: 90%;
margin: auto;
margin: 0px 5% 50px;
border-radius: 10px;
padding: 10px 0;
}
.hd.speed-select.mobile {
height: 90px;
}
.speed.speed-select.mobile {
height: 180px;
}
.speed-select.mobile .selectOption.active {
color: #648ffc;
}
.speed-select .selectOption.active {
color: #648ffc;
}
.speed-select.mobile .selectOption.cancel {
position: absolute;
background: #ffff;
width: 100%;
margin-top: 30px;
height: 45px;
text-align: center;
line-height: 45px;
list-style: none;
cursor: pointer;
font-size: 16px;
color: rgba(0, 0, 0, 0.85);
border-radius: 10px;
}
.speed-select.mobile ::after {
display: none;
}
.speed-select-mask {
position: fixed;
width: 100%;
background: rgba(0, 0, 0, 0.4);
/* height: 100vh; */
top: 0;
z-index: 1;
left: 0;
bottom: 0;
}
.speed-select.mobile.expend {
bottom: 0;
top: auto;
width: 240px;
right: 0;
left: auto;
height: 100vw;
margin: 0;
background: rgba(0, 0, 0, 0.75);
color: #ffffff;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
padding: 0;
border-radius: 0;
}
.speed-select.mobile.expend li {
color: #ffffff;
}
.speed-select.mobile.expend li:first-child {
margin-top: 40%;
}
.speed-select.mobile.expend .selectOption.cancel {
left: 0;
top: 0;
width: 40px;
font-size: 0;
background: none;
margin-top: 0;
}
.speed-select.mobile.expend .selectOption.cancel::before {
content: "";
display: block;
width: 16px;
height: 1px;
background: #ffffff;
border: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
}
.speed-select.mobile.expend .selectOption.cancel::after {
content: "";
display: block;
width: 1px;
height: 16px;
background: #ffffff;
border: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,343 @@
// We make use of this 'server' variable to provide the address of the
// REST Janus API. By default, in this example we assume that Janus is
// co-located with the web server hosting the HTML pages but listening
// on a different port (8088, the default for HTTP in Janus), which is
// why we make use of the 'window.location.hostname' base address. Since
// Janus can also do HTTPS, and considering we don't really want to make
// use of HTTP for Janus if your demos are served on HTTPS, we also rely
// on the 'window.location.protocol' prefix to build the variable, in
// particular to also change the port used to contact Janus (8088 for
// HTTP and 8089 for HTTPS, if enabled).
// In case you place Janus behind an Apache frontend (as we did on the
// online demos at http://janus.conf.meetecho.com) you can just use a
// relative path for the variable, e.g.:
//
// var server = "/janus";
//
// which will take care of this on its own.
//
//
// If you want to use the WebSockets frontend to Janus, instead, you'll
// have to pass a different kind of address, e.g.:
//
// var server = "ws://" + window.location.hostname + ":8188";
//
// Of course this assumes that support for WebSockets has been built in
// when compiling the server. WebSockets support has not been tested
// as much as the REST API, so handle with care!
//
//
// If you have multiple options available, and want to let the library
// autodetect the best way to contact your server (or pool of servers),
// you can also pass an array of servers, e.g., to provide alternative
// means of access (e.g., try WebSockets first and, if that fails, fall
// back to plain HTTP) or just have failover servers:
//
// var server = [
// "ws://" + window.location.hostname + ":8188",
// "/janus"
// ];
//
// This will tell the library to try connecting to each of the servers
// in the presented order. The first working server will be used for
// the whole session.
//
var server = null;
if(window.location.protocol === 'http:')
//server = "http://" + window.location.hostname + ":9020/janus";
// yujianbo
server = "https://" + "10.80.21.211" + ":9022/janus";
else
//server = "https://" + window.location.hostname + ":9022/janus";
// -yujianbo
server = "https://" + "10.80.21.211" + ":9022/janus";
var janus = null;
var tts = null;
var opaqueId = "tts-"+Janus.randomString(12);
var spinner = null;
// Initialize the library (all console debuggers enabled)
Janus.init({debug: "all", callback: function() {
window.stopTalk = function (){
janus.destroy();
}
// debugger;
window.startTalk = function() {
// Make sure the browser supports WebRTC
if(!Janus.isWebrtcSupported()) {
bootbox.alert("No WebRTC support... ");
return;
}
// if($('#tts_url').val().length == 0){
// bootbox.alert("Please input tts url... ");
// return;
// }
// $(this).attr('disabled', true).unbind('click');
// Create session
janus = new Janus(
{
server: window.EZUIKit.opt.rtcUrl,
// No "iceServers" is provided, meaning janus.js will use a default STUN server
// Here are some examples of how an iceServers field may look like to support TURN
// iceServers: [{urls: "turn:yourturnserver.com:3478", username: "janususer", credential: "januspwd"}],
// iceServers: [{urls: "turn:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}],
// iceServers: [{urls: "turns:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}],
// Should the Janus API require authentication, you can specify either the API secret or user token here too
// token: "mytoken",
// or
// apisecret: "serversecret",
success: function() {
// Attach to tts plugin
janus.attach(
{
plugin: "rtcgw.plugin.tts",
opaqueId: opaqueId,
success: function(pluginHandle) {
// $('#details').remove();
tts = pluginHandle;
Janus.log("Plugin attached! (" + tts.getPlugin() + ", id=" + tts.getId() + ")");
// Negotiate WebRTC
//var url = "tts://61.130.6.23:8664/talk://D13781761:0:1:cas.ys7.com:6500?97fbd2a75fa94b7682c994d3d1fac8ca:ut.5porslgu79e9r7ca48z32k8abgl3rp58-77bhb6i7xr-1kmumtg-jkhy7pvfr:0:3"
//var url = "tts://10.86.15.209:8664/talk://D13781761:0:1:cas.ys7.com:6500?32db2578ba7c4a84be22ecc0bcd0f8db:ut.5lqpkhim5m7cdk2y5w60g7hm9vd7i3v0-3d2pwhxe2t-11wx2ge-sh4yazbll:0:3"
//var url = "tts://10.86.15.209:8664/talk://D13781761:0:1:cas.ys7.com:6500"
//test12.ys.com
//var url = "tts://10.86.15.209:8664/talk://D08197169:0:1:cas.ys7.com:6500"
//test10.ys.com
//var url = "tts://10.86.29.210:8664/talk://D08197169:0:1:cas.ys7.com:6500"
var url = window.EZUIKit.opt.talkLink;
console.log("ttsUlr",url);
var body = { "request": "start", "url": url, "codec": "opus", "dir": "sendrecv", "audio_debug": 1};
//tts.send({"message": body});
Janus.debug("Trying a createOffer too (audio/video sendrecv)");
tts.createOffer(
{
// No media provided: by default, it's sendrecv for audio and video
media: { audio: true, video: false, data: false }, // Audio only
// If you want to test simulcasting (Chrome and Firefox only), then
// pass a ?simulcast=true when opening this demo page: it will turn
// the following 'simulcast' property to pass to janus.js to true
simulcast: false,
simulcast2: false,
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
tts.send({"message": body, "jsep": jsep});
if(typeof window.EZUIKit.handleTalkSuccess !== 'undefined'){
window.EZUIKit.handleTalkSuccess();
}
},
error: function(error) {
Janus.error("WebRTC error:", error);
// bootbox.alert("WebRTC error... " + JSON.stringify(error));
if(typeof window.EZUIKit['handleTalkError']?.[window.__CURRENT_PLAYER_TALK_ID] !== 'undefined'){
window.EZUIKit['handleTalkError']?.[window.__CURRENT_PLAYER_TALK_ID](error);
}
}
});
// $('#start').removeAttr('disabled').html("Stop")
// .click(function() {
// $(this).attr('disabled', true);
// janus.destroy();
// });
},
error: function(error) {
console.error(" -- Error attaching plugin...", error);
bootbox.alert("Error attaching plugin... " + error);
if(window.EZUIKit['handleTalkError']?.[window.__CURRENT_PLAYER_TALK_ID] !== 'undefined'){
window.EZUIKit['handleTalkError']?.[window.__CURRENT_PLAYER_TALK_ID](error);
}
},
consentDialog: function(on) {
Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now");
if(on) {
// Darken screen and show hint
// $.blockUI({
// message: '<div><img src="up_arrow.png"/></div>',
// css: {
// border: 'none',
// padding: '15px',
// backgroundColor: 'transparent',
// color: '#aaa',
// top: '10px',
// left: (navigator.mozGetUserMedia ? '-100px' : '300px')
// } });
} else {
// Restore screen
// $.unblockUI();
}
},
iceState: function(state) {
Janus.log("ICE state changed to " + state);
},
mediaState: function(medium, on) {
Janus.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
},
webrtcState: function(on) {
Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
// $("#audioleft").parent().unblock();
},
slowLink: function(uplink, lost) {
Janus.warn("Janus reports problems " + (uplink ? "sending" : "receiving") +
" packets on this PeerConnection (" + lost + " lost packets)");
},
onmessage: function(msg, jsep) {
Janus.debug(" ::: Got a message :::");
Janus.debug(msg);
if(jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well...");
Janus.debug(jsep);
tts.handleRemoteJsep({jsep: jsep});
}
var result = msg["result"];
if(result !== null && result !== undefined) {
if(result === "done") {
// The plugin closed
bootbox.alert("The TTS Test is over");
if(spinner !== null && spinner !== undefined)
spinner.stop();
spinner = null;
// $('#myaudio').remove();
//$('#waitingvideo').remove();
// $('#peeraudio').remove();
return;
}
if(result === "msg"){
if(typeof window.EZUIKit.handleTalkMessage !== 'undefined'){
window.EZUIKit.handleTalkMessage(msg);
}
}
// Any loss?
var status = result["status"];
if(status === "slow_link") {
//~ var bitrate = result["bitrate"];
//~ toastr.warning("The bitrate has been cut to " + (bitrate/1000) + "kbps", "Packet loss?", {timeOut: 2000});
toastr.warning("Janus apparently missed many packets we sent, maybe we should reduce the bitrate", "Packet loss?", {timeOut: 2000});
}
}
},
onlocalstream: function(stream) {
Janus.debug(" ::: Got a local stream :::");
Janus.debug(stream);
// if($('#myaudio').length === 0) {
// $('#audios').removeClass('hide').show();
// $('#audioleft').append('<audio id="myaudio" autoplay controls muted>Your browser does not support audio tag</audio>');
// }
// Janus.attachMediaStream(document.getElementById("myaudio"), stream);
//$("#myaudio").get(0).muted = "muted";
if(tts.webrtcStuff.pc.iceConnectionState !== "completed" &&
tts.webrtcStuff.pc.iceConnectionState !== "connected") {
// $("#audioleft").parent().block({
// message: '<b>Publishing...</b>',
// css: {
// border: 'none',
// backgroundColor: 'transparent',
// color: 'white'
// }
// });
// No remote video yet
//$('#audioright').append('<video class="rounded centered" id="waitingvideo" width=320 height=240 />');
if(spinner == null) {
var target = document.getElementById('audioright');
//spinner = new Spinner({top:100}).spin(target);
} else {
spinner.spin();
}
}
var audioTracks = stream.getAudioTracks();
if(audioTracks === null || audioTracks === undefined || audioTracks.length === 0) {
// $('#myaudio').hide();
} else {
// $('#myaudio').removeClass('hide').show();
// document.getElementById('myaudio').play();
}
},
onremotestream: function(stream) {
Janus.debug(" ::: Got a remote stream :::");
Janus.debug(stream);
// if($('#peeraudio').length === 0) {
// $('#audios').removeClass('hide').show();
// // $('#audioright').append('<audio id="peeraudio" autoplay controls>Your browser does not support audio tag</audio>');
// // Show the video, hide the spinner and show the resolution when we get a playing event
// var audio = $('<audio id="peeraudio" autoplay controls playsinline preload="preload" loop="true"></audio>');
// audio = audio.get(0);
// audio.setAttribute("id", 'peeraudio');
// audio.setAttribute("preload","preload");
// // 自动播放解决苹果不兼容autoplay属性
// audio.setAttribute("loop",true);
// $('#audioright').append(audio);
// $("#peeraudio").bind("playing", function () {
// //$('#waitingvideo').remove();
// $('#peeraudio').removeClass('hide').show();
// if(spinner !== null && spinner !== undefined)
// spinner.stop();
// spinner = null;
// });
// }
Janus.attachMediaStream(document.getElementById("peeraudio"), stream);
var audioTracks = stream.getAudioTracks();
if(audioTracks === null || audioTracks === undefined || audioTracks.length === 0) {
// $('#peeraudio').hide();
} else {
// $('#peeraudio').removeClass('hide').show();
document.getElementById('peeraudio').play();
}
},
ondataopen: function(data) {
Janus.log("The DataChannel is available!");
},
ondata: function(data) {
Janus.debug("We got data from the DataChannel! " + data);
},
oncleanup: function() {
Janus.log(" ::: Got a cleanup notification :::");
if(spinner !== null && spinner !== undefined)
spinner.stop();
spinner = null;
// $('#myaudio').remove();
// //$('#waitingvideo').remove();
// $("#audioleft").parent().unblock();
// $('#peeraudio').remove();
}
});
},
error: function(error) {
Janus.error(error);
if(window.EZUIKit['handleTalkError']?.[window.__CURRENT_PLAYER_TALK_ID] !== 'undefined'){
window.EZUIKit['handleTalkError']?.[window.__CURRENT_PLAYER_TALK_ID](error);
}
},
destroyed: function() {
// window.location.reload();
}
});
}
}});
function checkEnter(event) {
var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
if(theCode == 13) {
sendData();
return false;
} else {
return true;
}
}
// Helper to parse query string
function getQueryStringValue(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

View File

@ -1,371 +1,185 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>萤石云播放器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
/* background: #1a1a1a; */
position: relative;
}
#player-iframe {
width: 100%;
height: 50%;
border: none;
display: block;
object-fit: contain; /* 保持视频比例,不变形 */
}
/* 简化的控制按钮样式 */
.control-buttons {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 16px;
z-index: 999;
pointer-events: auto;
}
.control-btn {
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 25px;
padding: 12px 20px;
min-width: 100px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
color: white;
font-size: 14px;
font-weight: 500;
text-align: center;
}
.control-btn:active {
background: rgba(0, 0, 0, 0.8);
}
.play-btn {
background: rgba(76, 175, 80, 0.8);
border-color: rgba(76, 175, 80, 0.9);
}
.play-btn:active {
background: rgba(76, 175, 80, 0.9);
}
.refresh-btn {
background: rgba(33, 150, 243, 0.8);
border-color: rgba(33, 150, 243, 0.9);
}
.refresh-btn:active {
background: rgba(33, 150, 243, 0.9);
}
/* 按钮图标样式 */
.btn-icon {
margin-right: 6px;
font-size: 16px;
display: inline-block;
}
.btn-text {
font-weight: 500;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 14px;
text-align: center;
background: rgba(0, 0, 0, 0.7);
padding: 15px 30px;
border-radius: 8px;
z-index: 10;
}
.loading::after {
content: '';
display: block;
width: 20px;
height: 20px;
margin: 10px auto 0;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 简化的暂停占位符样式 */
.pause-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50%;
background: #000000;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 5;
}
.pause-content {
text-align: center;
color: white;
}
.pause-title {
font-size: 18px;
font-weight: 400;
color: white;
}
</style>
</head>
<body>
<div class="loading" id="loading">正在加载播放器...</div>
<iframe id="player-iframe" allow="autoplay; fullscreen"></iframe>
<!-- 暂停时的占位符 -->
<div class="pause-placeholder" id="pause-placeholder">
<div class="pause-content">
<div class="pause-title">监控已暂停</div>
</div>
</div>
<!-- 控制按钮 -->
<div class="control-buttons" id="control-buttons" style="display: none;">
<div class="control-btn play-btn" id="play-btn">
<span class="btn-icon" id="play-icon">▶️</span>
<span class="btn-text" id="play-text">播放</span>
</div>
<div class="control-btn refresh-btn" id="refresh-btn">
<span class="btn-icon">🔄</span>
<span class="btn-text">刷新</span>
</div>
</div>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>移动式检修车间</title>
<script src="./ezuikit.js"></script>
<style>
html,
body {
padding: 0;
margin: 0;
}
#video-container {
width: 100%;
height: 100vh;
}
</style>
</head>
<script>
function log(message) {
console.log('[iframe播放器] ' + message);
}
// 从URL参数获取配置
function getConfig() {
const params = new URLSearchParams(window.location.search);
return {
accessToken: params.get('accessToken'),
playUrl: params.get('playUrl')
};
}
// 初始化播放器
function init() {
log('初始化开始');
const configData = getConfig();
if (!configData.accessToken || !configData.playUrl) {
log('配置参数不完整');
document.getElementById('loading').textContent = '配置参数错误';
return;
}
// 保存配置到全局变量
config = {
accessToken: configData.accessToken,
playUrl: configData.playUrl
};
log('AccessToken: ' + config.accessToken.substring(0, 20) + '...');
log('PlayUrl: ' + config.playUrl);
try {
// 使用萤石云官方iframe播放器内存占用小
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
'url=' + encodeURIComponent(config.playUrl) +
'&accessToken=' + encodeURIComponent(config.accessToken) +
'&width=100%' +
'&height=100%' +
'&autoplay=1' +
'&audio=1' +
'&controls=1';
log('iframe URL: ' + iframeUrl);
const iframe = document.getElementById('player-iframe');
iframe.src = iframeUrl;
// 设置初始播放状态
isPlaying = true;
document.getElementById('play-text').textContent = '暂停';
document.getElementById('play-icon').textContent = '⏸️';
// 隐藏loading显示控制按钮
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
document.getElementById('control-buttons').style.display = 'flex';
log('播放器加载完成,显示控制按钮');
}, 2000);
} catch (error) {
log('初始化失败: ' + error.message);
document.getElementById('loading').textContent = '初始化失败';
}
}
// 全局状态
let isPlaying = true;
let config = null;
// 播放/暂停控制
function togglePlay() {
log('切换播放状态: ' + (isPlaying ? '暂停' : '播放'));
const iframe = document.getElementById('player-iframe');
const playText = document.getElementById('play-text');
const playIcon = document.getElementById('play-icon');
const pausePlaceholder = document.getElementById('pause-placeholder');
if (isPlaying) {
// 暂停清空iframe显示占位符
iframe.src = 'about:blank';
pausePlaceholder.style.display = 'flex';
playText.textContent = '播放';
playIcon.textContent = '▶️';
isPlaying = false;
log('已暂停播放,显示占位符');
} else {
// 播放重新设置iframe URL隐藏占位符
if (config) {
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
'url=' + encodeURIComponent(config.playUrl) +
'&accessToken=' + encodeURIComponent(config.accessToken) +
'&width=100%' +
'&height=100%' +
'&autoplay=1' +
'&audio=1' +
'&controls=1';
iframe.src = iframeUrl;
pausePlaceholder.style.display = 'none';
playText.textContent = '暂停';
playIcon.textContent = '⏸️';
isPlaying = true;
log('已开始播放,隐藏占位符');
}
}
}
// 刷新播放器
function refreshPlayer() {
log('刷新播放器');
const iframe = document.getElementById('player-iframe');
const playText = document.getElementById('play-text');
const playIcon = document.getElementById('play-icon');
const pausePlaceholder = document.getElementById('pause-placeholder');
// 先清空,显示加载状态
iframe.src = 'about:blank';
pausePlaceholder.style.display = 'none';
// 延迟重新加载
setTimeout(() => {
if (config) {
const iframeUrl = 'https://open.ys7.com/ezopen/h5/iframe?' +
'url=' + encodeURIComponent(config.playUrl) +
'&accessToken=' + encodeURIComponent(config.accessToken) +
'&width=100%' +
'&height=100%' +
'&autoplay=1' +
'&audio=1' +
'&controls=1';
iframe.src = iframeUrl;
playText.textContent = '暂停';
playIcon.textContent = '⏸️';
isPlaying = true;
log('刷新完成');
}
}, 500);
}
// 页面加载完成后初始化
window.onload = function() {
log('页面加载完成');
// 绑定按钮事件
document.getElementById('play-btn').addEventListener('click', togglePlay);
document.getElementById('refresh-btn').addEventListener('click', refreshPlayer);
init();
};
// 页面卸载时清理资源
window.onbeforeunload = function() {
log('页面即将卸载,清理播放器资源');
try {
const iframe = document.getElementById('player-iframe');
if (iframe) {
iframe.src = 'about:blank'; // 清空iframe源
log('播放器资源已清理');
}
} catch (error) {
log('清理播放器资源失败: ' + error.message);
}
};
// 页面隐藏时暂停播放
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
log('页面隐藏,暂停播放');
try {
const iframe = document.getElementById('player-iframe');
if (iframe) {
iframe.style.display = 'none';
}
} catch (error) {
log('暂停播放失败: ' + error.message);
}
} else {
log('页面显示,恢复播放');
try {
const iframe = document.getElementById('player-iframe');
if (iframe) {
iframe.style.display = 'block';
}
} catch (error) {
log('恢复播放失败: ' + error.message);
}
}
});
</script>
</body>
</html>
<body>
<div className="demo">
<div id="video-container"></div>
</div>
<script>
var player;
const width = document.body.clientWidth || 375;
// 从URL参数获取配置
function getConfig() {
const params = new URLSearchParams(window.location.search);
const config = {
accessToken: params.get('accessToken'),
playUrl: params.get('playUrl')
};
console.log('📡 获取URL参数:', {
accessToken: config.accessToken ? config.accessToken.substring(0, 20) + '...' : '未提供',
playUrl: config.playUrl || '未提供'
});
return config;
}
// 获取配置
const config = getConfig();
// 验证参数
if (!config.accessToken || !config.playUrl) {
console.error('❌ 缺少必要参数请在URL中提供 accessToken 和 playUrl');
alert('缺少必要参数!\n需要: ?accessToken=xxx&playUrl=ezopen://...');
} else {
console.log('✅ 参数验证通过,开始创建播放器');
// 使用URL参数创建播放器
player = new EZUIKit.EZUIKitPlayer({
id: "video-container", // 视频容器ID
url: config.playUrl,
accessToken: config.accessToken,
template: "voice", // simple: 极简版; pcLive: 预览; pcRec: 回放; security: 安防版; voice: 语音版;
// 官方demo token
// template: "voice",
// url: "ezopen://open.ys7.com/BC7900686/1.live",
// accessToken: "ra.84wglyar4c3r6hozd6u92ser0r854lbi-5c1dgl7mi6-1q2swlg-b0hpmwcqg",
width: width,
height: (width * 9) / 16,
language: "zh", // zh | en
env: {
// https://open.ys7.com/help/1772?h=domain
// domain默认是 https://open.ys7.com, 如果是私有化部署或海外的环境请配置对应的domain
domain: "https://open.ys7.com",
},
// 日志打印设置
loggerOptions: {
level: "INFO", // INFO LOG WARN ERROR
name: "ezuikit",
showTime: true,
},
// 视频流的信息回调类型
streamInfoCBType: 1,
staticPath: "./ezuikit_static", // 使用本地静态资源
// 错误处理
handleError: (error) => {
console.error('❌ 播放器错误:', error);
}
});
// 监听视频信息
player.eventEmitter.on(
EZUIKit.EZUIKitPlayer.EVENTS.videoInfo,
(info) => {
console.log("📹 videoinfo", info);
},
);
// 监听音频信息
player.eventEmitter.on(
EZUIKit.EZUIKitPlayer.EVENTS.audioInfo,
(info) => {
console.log("🔊 audioInfo", info);
},
);
// 首帧渲染成功
player.eventEmitter.on(
EZUIKit.EZUIKitPlayer.EVENTS.firstFrameDisplay,
() => {
console.log("✅ firstFrameDisplay - 首帧渲染成功");
},
);
// 流信息回调
player.eventEmitter.on(
EZUIKit.EZUIKitPlayer.EVENTS.streamInfoCB,
(info) => {
console.log("📡 streamInfoCB ", info);
},
);
console.log('🎬 播放器初始化完成');
}
// 控制函数
function fullScreen() {
if (player) {
player.fullScreen();
}
}
function play() {
if (player) {
var playPromise = player.play();
playPromise.then((data) => {
console.log("播放成功", data);
});
}
}
function stop() {
if (player) {
var stopPromise = player.stop();
stopPromise.then((data) => {
console.log("停止成功", data);
});
}
}
function openSound() {
if (player) {
var openSoundPromise = player.openSound();
openSoundPromise.then((data) => {
console.log("打开声音成功", data);
});
}
}
function closeSound() {
if (player) {
var closeSoundPromise = player.closeSound();
closeSoundPromise.then((data) => {
console.log("关闭声音成功", data);
});
}
}
function destroy() {
if (player) {
player.destroy();
console.log('🗑️ 播放器已销毁');
}
player = null;
}
// 页面卸载时清理
window.onbeforeunload = function() {
destroy();
};
</script>
</body>
</html>