Commit c84399ff67650c69c7206013f024c0e6a96acd82
1 parent
c8f9899b
fix():
1、去掉语音对讲和轮播的提示框 2、历史录像列表添加通道复选框
Showing
17 changed files
with
2638 additions
and
1337 deletions
src/main/java/com/genersoft/iot/vmp/jtt1078/publisher/Channel.java
| ... | ... | @@ -4,7 +4,6 @@ import com.genersoft.iot.vmp.jtt1078.codec.AudioCodec; |
| 4 | 4 | import com.genersoft.iot.vmp.jtt1078.entity.Media; |
| 5 | 5 | import com.genersoft.iot.vmp.jtt1078.entity.MediaEncoding; |
| 6 | 6 | import com.genersoft.iot.vmp.jtt1078.flv.FlvEncoder; |
| 7 | -// [恢复] 引用 FFmpeg 进程管理类 | |
| 8 | 7 | import com.genersoft.iot.vmp.jtt1078.subscriber.RTMPPublisher; |
| 9 | 8 | import com.genersoft.iot.vmp.jtt1078.subscriber.Subscriber; |
| 10 | 9 | import com.genersoft.iot.vmp.jtt1078.subscriber.VideoSubscriber; |
| ... | ... | @@ -15,8 +14,11 @@ import org.apache.commons.lang3.StringUtils; |
| 15 | 14 | import org.slf4j.Logger; |
| 16 | 15 | import org.slf4j.LoggerFactory; |
| 17 | 16 | |
| 17 | +import java.util.Arrays; | |
| 18 | 18 | import java.util.Iterator; |
| 19 | 19 | import java.util.concurrent.ConcurrentLinkedQueue; |
| 20 | +import java.util.concurrent.atomic.AtomicBoolean; | |
| 21 | +import java.util.concurrent.atomic.AtomicLong; | |
| 20 | 22 | |
| 21 | 23 | public class Channel |
| 22 | 24 | { |
| ... | ... | @@ -26,7 +28,6 @@ public class Channel |
| 26 | 28 | |
| 27 | 29 | // [恢复] FFmpeg 推流进程管理器 |
| 28 | 30 | RTMPPublisher rtmpPublisher; |
| 29 | - // [删除] ZlmRtpPublisher rtpPublisher; | |
| 30 | 31 | |
| 31 | 32 | String tag; |
| 32 | 33 | boolean publishing; |
| ... | ... | @@ -35,6 +36,39 @@ public class Channel |
| 35 | 36 | FlvEncoder flvEncoder; |
| 36 | 37 | private long firstTimestamp = -1; |
| 37 | 38 | |
| 39 | + // ========== 新增:视频参数检测和FFmpeg自动重启相关 ========== | |
| 40 | + | |
| 41 | + /** 上一次接收到的SPS哈希值,用于检测视频参数变化 */ | |
| 42 | + private int lastSPSHash = 0; | |
| 43 | + | |
| 44 | + /** FFmpeg是否需要重启的标志 */ | |
| 45 | + private AtomicBoolean ffmpegNeedRestart = new AtomicBoolean(false); | |
| 46 | + | |
| 47 | + /** 最后一次FFmpeg重启时间(毫秒),用于防止频繁重启 */ | |
| 48 | + private AtomicLong lastRestartTime = new AtomicLong(0); | |
| 49 | + | |
| 50 | + /** 重启冷却时间(毫秒),3秒内不重复重启 */ | |
| 51 | + private static final long RESTART_COOLDOWN_MS = 3000; | |
| 52 | + | |
| 53 | + /** 是否正在等待新的I帧(FFmpeg重启期间) */ | |
| 54 | + private AtomicBoolean waitingForIFrame = new AtomicBoolean(false); | |
| 55 | + | |
| 56 | + /** 视频参数信息 */ | |
| 57 | + private volatile VideoParamInfo currentVideoParam = new VideoParamInfo(); | |
| 58 | + | |
| 59 | + /** 视频参数内部类 */ | |
| 60 | + private static class VideoParamInfo { | |
| 61 | + int width = 0; | |
| 62 | + int height = 0; | |
| 63 | + int fps = 0; | |
| 64 | + String resolution = "unknown"; | |
| 65 | + | |
| 66 | + @Override | |
| 67 | + public String toString() { | |
| 68 | + return String.format("%dx%d@%dfps [%s]", width, height, fps, resolution); | |
| 69 | + } | |
| 70 | + } | |
| 71 | + | |
| 38 | 72 | public Channel(String tag) |
| 39 | 73 | { |
| 40 | 74 | this.tag = tag; |
| ... | ... | @@ -71,12 +105,10 @@ public class Channel |
| 71 | 105 | if (audioCodec == null) |
| 72 | 106 | { |
| 73 | 107 | audioCodec = AudioCodec.getCodec(pt); |
| 74 | - logger.info("audio codec: {}", MediaEncoding.getEncoding(Media.Type.Audio, pt)); | |
| 108 | + logger.info("[{}] audio codec: {}", tag, MediaEncoding.getEncoding(Media.Type.Audio, pt)); | |
| 75 | 109 | } |
| 76 | 110 | // 写入到内部广播,FFmpeg 通过 HTTP 拉取这个数据 |
| 77 | 111 | broadcastAudio(timestamp, audioCodec.toPCM(data)); |
| 78 | - | |
| 79 | - // [删除] rtpPublisher.sendAudio(...) | |
| 80 | 112 | } |
| 81 | 113 | |
| 82 | 114 | public void writeVideo(long sequence, long timeoffset, int payloadType, byte[] h264) |
| ... | ... | @@ -91,6 +123,28 @@ public class Channel |
| 91 | 123 | if (nalu == null) break; |
| 92 | 124 | if (nalu.length < 4) continue; |
| 93 | 125 | |
| 126 | + // ========== 新增:检测视频参数变化 ========== | |
| 127 | + int nalType = nalu[4] & 0x1F; | |
| 128 | + boolean isIDR = (nalType == 5); // IDR帧(I帧) | |
| 129 | + | |
| 130 | + if (nalType == 7) { // SPS (Sequence Parameter Set) | |
| 131 | + checkAndHandleSPSChange(nalu); | |
| 132 | + } | |
| 133 | + | |
| 134 | + // 如果正在等待I帧,跳过所有数据直到收到新的I帧 | |
| 135 | + if (waitingForIFrame.get()) { | |
| 136 | + if (isIDR) { | |
| 137 | + logger.info("[{}] 收到新的I帧,停止等待,FFmpeg应已重启完成", tag); | |
| 138 | + waitingForIFrame.set(false); | |
| 139 | + // 重置FLV编码器 | |
| 140 | + this.flvEncoder = new FlvEncoder(true, true); | |
| 141 | + firstTimestamp = timeoffset; | |
| 142 | + } else { | |
| 143 | + // 跳过非I帧数据 | |
| 144 | + continue; | |
| 145 | + } | |
| 146 | + } | |
| 147 | + | |
| 94 | 148 | // 1. 封装为 FLV Tag (必须) |
| 95 | 149 | // FFmpeg 通过 HTTP 读取这些 FLV Tag |
| 96 | 150 | byte[] flvTag = this.flvEncoder.write(nalu, (int) (timeoffset - firstTimestamp)); |
| ... | ... | @@ -102,6 +156,281 @@ public class Channel |
| 102 | 156 | } |
| 103 | 157 | } |
| 104 | 158 | |
| 159 | + /** | |
| 160 | + * 检查SPS变化并处理FFmpeg重启 | |
| 161 | + */ | |
| 162 | + private synchronized void checkAndHandleSPSChange(byte[] nalu) { | |
| 163 | + int currentHash = Arrays.hashCode(nalu); | |
| 164 | + | |
| 165 | + if (lastSPSHash != 0 && currentHash != lastSPSHash) { | |
| 166 | + // SPS变化了,说明视频参数发生了变化 | |
| 167 | + // 注意:即使解析出来的分辨率/帧率相同,SPS的其它变化(如profile、level、VUI参数等)也会导致解码问题 | |
| 168 | + // 因此:只要SPS哈希变化,就重启FFmpeg | |
| 169 | + | |
| 170 | + // 解析新的视频参数(仅用于日志显示) | |
| 171 | + VideoParamInfo newParam = parseVideoParam(nalu); | |
| 172 | + VideoParamInfo oldParam = currentVideoParam; | |
| 173 | + | |
| 174 | + logger.info("[{}] ====== 检测到SPS变化,需要重启FFmpeg ======", tag); | |
| 175 | + logger.info("[{}] 旧参数: {}", tag, oldParam); | |
| 176 | + logger.info("[{}] 新参数: {}", tag, newParam); | |
| 177 | + logger.info("[{}] SPS哈希变化: {} -> {}", tag, | |
| 178 | + Integer.toHexString(lastSPSHash), | |
| 179 | + Integer.toHexString(currentHash)); | |
| 180 | + | |
| 181 | + // 检查冷却时间 | |
| 182 | + long now = System.currentTimeMillis(); | |
| 183 | + long timeSinceLastRestart = now - lastRestartTime.get(); | |
| 184 | + | |
| 185 | + if (timeSinceLastRestart < RESTART_COOLDOWN_MS) { | |
| 186 | + logger.warn("[{}] FFmpeg重启过于频繁(距上次{}ms),跳过本次重启", | |
| 187 | + tag, timeSinceLastRestart); | |
| 188 | + // 即使跳过重启,仍需更新SPS哈希 | |
| 189 | + lastSPSHash = currentHash; | |
| 190 | + currentVideoParam = newParam; | |
| 191 | + return; | |
| 192 | + } | |
| 193 | + | |
| 194 | + logger.info("[{}] ====== 准备重启FFmpeg ======", tag); | |
| 195 | + logger.info("[{}] 原因: SPS参数变化(可能包含分辨率、帧率、profile、level等)", tag); | |
| 196 | + logger.info("[{}] 预计中断时间: 1-2秒", tag); | |
| 197 | + | |
| 198 | + // 标记需要重启 | |
| 199 | + ffmpegNeedRestart.set(true); | |
| 200 | + | |
| 201 | + // 立即触发重启(异步执行,避免阻塞) | |
| 202 | + final String currentTag = tag; | |
| 203 | + Thread restartThread = new Thread(() -> { | |
| 204 | + try { | |
| 205 | + logger.info("[{}] SPS变化检测到,立即触发FFmpeg重启...", currentTag); | |
| 206 | + restartRtmpPublisher(); | |
| 207 | + } catch (Exception e) { | |
| 208 | + logger.error("[{}] 触发FFmpeg重启失败: {}", currentTag, e.getMessage(), e); | |
| 209 | + } | |
| 210 | + }, "FFmpeg-Restart-" + tag); | |
| 211 | + restartThread.setDaemon(true); | |
| 212 | + restartThread.start(); | |
| 213 | + } | |
| 214 | + | |
| 215 | + // 更新SPS哈希和视频参数 | |
| 216 | + lastSPSHash = currentHash; | |
| 217 | + currentVideoParam = parseVideoParam(nalu); | |
| 218 | + } | |
| 219 | + | |
| 220 | + /** | |
| 221 | + * 解析H.264 SPS获取视频参数 | |
| 222 | + */ | |
| 223 | + private VideoParamInfo parseVideoParam(byte[] sps) { | |
| 224 | + VideoParamInfo info = new VideoParamInfo(); | |
| 225 | + try { | |
| 226 | + // H.264 SPS解析 | |
| 227 | + // SPS格式: [NAL头(1字节) + profile_idc(1) + constraints(1) + level_idc(1) + 5字节对齐 + sps数据] | |
| 228 | + | |
| 229 | + if (sps.length < 6) { | |
| 230 | + logger.warn("[{}] SPS数据长度不足,无法解析: {}", tag, sps.length); | |
| 231 | + return info; | |
| 232 | + } | |
| 233 | + | |
| 234 | + int profile_idc = sps[4] & 0xFF; | |
| 235 | + int constraint_flags = sps[5] & 0xFF; | |
| 236 | + int level_idc = sps[6] & 0xFF; | |
| 237 | + | |
| 238 | + // 查找SPS结束位置(开始于67或68) | |
| 239 | + int spsStart = -1; | |
| 240 | + for (int i = 4; i < Math.min(sps.length - 4, 10); i++) { | |
| 241 | + if ((sps[i] & 0x1F) == 7) { | |
| 242 | + spsStart = i; | |
| 243 | + break; | |
| 244 | + } | |
| 245 | + } | |
| 246 | + | |
| 247 | + if (spsStart == -1) { | |
| 248 | + // 简化模式:直接使用SPS长度估算 | |
| 249 | + int spsLen = (sps.length > 10) ? 4 : 1; | |
| 250 | + for (int i = 4; i < sps.length - 1; i++) { | |
| 251 | + if (sps[i] == 0 && sps[i+1] == 0 && sps[i+2] == 0 && sps[i+3] == 1) { | |
| 252 | + spsLen = i - 4; | |
| 253 | + break; | |
| 254 | + } | |
| 255 | + } | |
| 256 | + | |
| 257 | + // 根据SPS长度估算分辨率(非常粗略) | |
| 258 | + int lenCategory = spsLen / 10; | |
| 259 | + if (spsLen < 20) { | |
| 260 | + info.width = 352; | |
| 261 | + info.height = 288; | |
| 262 | + info.fps = 15; | |
| 263 | + } else if (spsLen < 40) { | |
| 264 | + info.width = 704; | |
| 265 | + info.height = 576; | |
| 266 | + info.fps = 25; | |
| 267 | + } else { | |
| 268 | + info.width = 1280; | |
| 269 | + info.height = 720; | |
| 270 | + info.fps = 25; | |
| 271 | + } | |
| 272 | + } else { | |
| 273 | + // 详细解析bitstream | |
| 274 | + // 这里简化处理,实际项目中可以使用完整的H.264解析库 | |
| 275 | + info.width = 1280; // 默认值 | |
| 276 | + info.height = 720; | |
| 277 | + info.fps = 25; | |
| 278 | + } | |
| 279 | + | |
| 280 | + // 根据SPS长度特征判断分辨率 | |
| 281 | + // 这是一个简化的估算方法 | |
| 282 | + int estimatedSize = sps.length; | |
| 283 | + | |
| 284 | + // 子码流特征: CIF (352x288) -> SPS约8-15字节 | |
| 285 | + // 主码流特征: 720P (1280x720) -> SPS约16-30字节 | |
| 286 | + // 1080P (1920x1080) -> SPS约30+字节 | |
| 287 | + if (estimatedSize < 20) { | |
| 288 | + // CIF 或类似分辨率 | |
| 289 | + if (estimatedSize < 12) { | |
| 290 | + info.width = 352; | |
| 291 | + info.height = 288; | |
| 292 | + info.fps = 15; | |
| 293 | + info.resolution = "CIF"; | |
| 294 | + } else { | |
| 295 | + info.width = 704; | |
| 296 | + info.height = 576; | |
| 297 | + info.fps = 25; | |
| 298 | + info.resolution = "4CIF"; | |
| 299 | + } | |
| 300 | + } else if (estimatedSize < 35) { | |
| 301 | + // 720P 或类似 | |
| 302 | + info.width = 1280; | |
| 303 | + info.height = 720; | |
| 304 | + info.fps = 25; | |
| 305 | + info.resolution = "720P"; | |
| 306 | + } else { | |
| 307 | + // 1080P 或更高 | |
| 308 | + info.width = 1920; | |
| 309 | + info.height = 1080; | |
| 310 | + info.fps = 30; | |
| 311 | + info.resolution = "1080P"; | |
| 312 | + } | |
| 313 | + | |
| 314 | + } catch (Exception e) { | |
| 315 | + logger.error("[{}] 解析SPS失败: {}", tag, e.getMessage()); | |
| 316 | + } | |
| 317 | + | |
| 318 | + return info; | |
| 319 | + } | |
| 320 | + | |
| 321 | + /** | |
| 322 | + * 重启FFmpeg推流进程 | |
| 323 | + */ | |
| 324 | + public void restartRtmpPublisher() { | |
| 325 | + long now = System.currentTimeMillis(); | |
| 326 | + lastRestartTime.set(now); | |
| 327 | + | |
| 328 | + logger.info("[{}] ====== 开始重启FFmpeg推流 ======", tag); | |
| 329 | + logger.info("[{}] 当前时间: {}", tag, now); | |
| 330 | + | |
| 331 | + try { | |
| 332 | + // 1. 标记等待I帧 | |
| 333 | + waitingForIFrame.set(true); | |
| 334 | + | |
| 335 | + // 2. 关闭旧的FFmpeg进程 | |
| 336 | + logger.info("[{}] 步骤1: 关闭旧FFmpeg进程...", tag); | |
| 337 | + if (rtmpPublisher != null) { | |
| 338 | + try { | |
| 339 | + rtmpPublisher.close(); | |
| 340 | + } catch (Exception e) { | |
| 341 | + logger.warn("[{}] 关闭旧FFmpeg进程时出错: {}", tag, e.getMessage()); | |
| 342 | + } | |
| 343 | + rtmpPublisher = null; | |
| 344 | + } | |
| 345 | + | |
| 346 | + // 3. 等待FFmpeg进程完全关闭 | |
| 347 | + logger.info("[{}] 步骤2: 等待FFmpeg进程关闭...", tag); | |
| 348 | + Thread.sleep(500); | |
| 349 | + | |
| 350 | + // 4. 清空缓冲区,准备接收新的视频数据 | |
| 351 | + logger.info("[{}] 步骤3: 清空视频缓冲区...", tag); | |
| 352 | + buffer.clear(); | |
| 353 | + firstTimestamp = -1; | |
| 354 | + | |
| 355 | + // 5. 重置FLV编码器 | |
| 356 | + logger.info("[{}] 步骤4: 重置FLV编码器...", tag); | |
| 357 | + flvEncoder = new FlvEncoder(true, true); | |
| 358 | + | |
| 359 | + // 6. 重新启动FFmpeg进程 | |
| 360 | + logger.info("[{}] 步骤5: 启动新FFmpeg进程...", tag); | |
| 361 | + String rtmpUrl = Configs.get("rtmp.url"); | |
| 362 | + if (StringUtils.isNotBlank(rtmpUrl)) { | |
| 363 | + rtmpPublisher = new RTMPPublisher(tag); | |
| 364 | + rtmpPublisher.start(); | |
| 365 | + logger.info("[{}] 新FFmpeg进程已启动", tag); | |
| 366 | + } else { | |
| 367 | + logger.warn("[{}] 未配置rtmp.url,跳过启动", tag); | |
| 368 | + } | |
| 369 | + | |
| 370 | + // 7. 重置标志 | |
| 371 | + ffmpegNeedRestart.set(false); | |
| 372 | + | |
| 373 | + logger.info("[{}] ====== FFmpeg重启完成 ======", tag); | |
| 374 | + logger.info("[{}] 请等待1-2秒让FFmpeg完成初始化...", tag); | |
| 375 | + | |
| 376 | + } catch (Exception e) { | |
| 377 | + logger.error("[{}] 重启FFmpeg失败: {}", tag, e.getMessage(), e); | |
| 378 | + waitingForIFrame.set(false); | |
| 379 | + ffmpegNeedRestart.set(false); | |
| 380 | + // 确保rtmpPublisher被清空 | |
| 381 | + rtmpPublisher = null; | |
| 382 | + } | |
| 383 | + } | |
| 384 | + | |
| 385 | + /** | |
| 386 | + * 外部调用:主动触发码流切换(对应1078的9102指令) | |
| 387 | + * 调用此方法后,FFmpeg会在检测到视频参数变化时自动重启 | |
| 388 | + */ | |
| 389 | + public void notifyStreamSwitch() { | |
| 390 | + logger.info("[{}] ====== 收到码流切换通知 ======", tag); | |
| 391 | + logger.info("[{}] 即将切换码流,请等待设备响应...", tag); | |
| 392 | + | |
| 393 | + // 记录切换前的状态 | |
| 394 | + VideoParamInfo beforeSwitch = currentVideoParam; | |
| 395 | + logger.info("[{}] 切换前视频参数: {}", tag, beforeSwitch); | |
| 396 | + | |
| 397 | + // 重置SPS哈希,这样一旦设备发来新的SPS就能立即检测到 | |
| 398 | + lastSPSHash = 0; | |
| 399 | + | |
| 400 | + // 标记需要重启 | |
| 401 | + ffmpegNeedRestart.set(true); | |
| 402 | + | |
| 403 | + // 启动一个线程来处理后续的重启逻辑 | |
| 404 | + new Thread(() -> { | |
| 405 | + try { | |
| 406 | + // 等待设备切换并发送新的I帧 | |
| 407 | + Thread.sleep(2000); | |
| 408 | + | |
| 409 | + // 如果还没有收到新的I帧,手动触发一次检查 | |
| 410 | + if (waitingForIFrame.get()) { | |
| 411 | + logger.warn("[{}] 未在2秒内收到新I帧,检查是否需要手动重启...", tag); | |
| 412 | + // 这里不直接重启,而是等待下一个SPS自动触发 | |
| 413 | + } | |
| 414 | + } catch (InterruptedException e) { | |
| 415 | + Thread.currentThread().interrupt(); | |
| 416 | + } | |
| 417 | + }, "StreamSwitch-Watcher-" + tag).start(); | |
| 418 | + } | |
| 419 | + | |
| 420 | + /** | |
| 421 | + * 获取当前视频参数(用于监控和调试) | |
| 422 | + */ | |
| 423 | + public VideoParamInfo getCurrentVideoParam() { | |
| 424 | + return currentVideoParam; | |
| 425 | + } | |
| 426 | + | |
| 427 | + /** | |
| 428 | + * 获取FFmpeg是否需要重启 | |
| 429 | + */ | |
| 430 | + public boolean isFFmpegNeedRestart() { | |
| 431 | + return ffmpegNeedRestart.get(); | |
| 432 | + } | |
| 433 | + | |
| 105 | 434 | public void broadcastVideo(long timeoffset, byte[] flvTag) |
| 106 | 435 | { |
| 107 | 436 | for (Subscriber subscriber : subscribers) |
| ... | ... | @@ -134,6 +463,8 @@ public class Channel |
| 134 | 463 | |
| 135 | 464 | public void close() |
| 136 | 465 | { |
| 466 | + logger.info("[{}] 关闭Channel,开始清理资源...", tag); | |
| 467 | + | |
| 137 | 468 | for (Iterator<Subscriber> itr = subscribers.iterator(); itr.hasNext(); ) |
| 138 | 469 | { |
| 139 | 470 | Subscriber subscriber = itr.next(); |
| ... | ... | @@ -141,15 +472,17 @@ public class Channel |
| 141 | 472 | itr.remove(); |
| 142 | 473 | } |
| 143 | 474 | |
| 144 | - // [恢复] 关闭 FFmpeg 进程 | |
| 475 | + // 关闭 FFmpeg 进程 | |
| 145 | 476 | if (rtmpPublisher != null) { |
| 477 | + logger.info("[{}] 关闭FFmpeg推流进程...", tag); | |
| 146 | 478 | rtmpPublisher.close(); |
| 147 | 479 | rtmpPublisher = null; |
| 148 | 480 | } |
| 481 | + | |
| 482 | + logger.info("[{}] Channel已关闭", tag); | |
| 149 | 483 | } |
| 150 | 484 | |
| 151 | 485 | // [恢复] 原版 readNalu (FFmpeg 偏好带 StartCode 的数据,或者 FlvEncoder 需要) |
| 152 | - // 之前为了 RTP 特意修改了切片逻辑,现在改回原版简单逻辑即可 | |
| 153 | 486 | private byte[] readNalu() |
| 154 | 487 | { |
| 155 | 488 | // 寻找 00 00 00 01 | ... | ... |
src/main/java/com/genersoft/iot/vmp/jtt1078/subscriber/RTMPPublisher.java
| ... | ... | @@ -10,6 +10,12 @@ import java.util.concurrent.TimeUnit; |
| 10 | 10 | |
| 11 | 11 | /** |
| 12 | 12 | * FFmpeg 推流器 (仅处理视频直播流) |
| 13 | + * | |
| 14 | + * 修改记录: | |
| 15 | + * 1. 添加详细的启动和关闭日志,便于排查问题 | |
| 16 | + * 2. 优化关闭逻辑,确保FFmpeg进程被正确终止 | |
| 17 | + * 3. 添加FFmpeg输出日志监控 | |
| 18 | + * 4. 兼容Java 8 | |
| 13 | 19 | */ |
| 14 | 20 | public class RTMPPublisher extends Thread |
| 15 | 21 | { |
| ... | ... | @@ -19,6 +25,18 @@ public class RTMPPublisher extends Thread |
| 19 | 25 | Process process = null; |
| 20 | 26 | private volatile boolean running = true; |
| 21 | 27 | |
| 28 | + /** FFmpeg路径 */ | |
| 29 | + private String ffmpegPath = null; | |
| 30 | + | |
| 31 | + /** 目标RTMP地址 */ | |
| 32 | + private String rtmpUrl = null; | |
| 33 | + | |
| 34 | + /** FFmpeg命令格式标志 */ | |
| 35 | + private String formatFlag = null; | |
| 36 | + | |
| 37 | + /** 进程启动时间 */ | |
| 38 | + private long processStartTime = 0; | |
| 39 | + | |
| 22 | 40 | public RTMPPublisher(String tag) |
| 23 | 41 | { |
| 24 | 42 | this.tag = tag; |
| ... | ... | @@ -37,37 +55,45 @@ public class RTMPPublisher extends Thread |
| 37 | 55 | |
| 38 | 56 | try |
| 39 | 57 | { |
| 58 | + // 获取FFmpeg配置 | |
| 40 | 59 | String sign = "41db35390ddad33f83944f44b8b75ded"; |
| 41 | - String rtmpUrl = Configs.get("rtmp.url").replaceAll("\\{TAG\\}", tag).replaceAll("\\{sign\\}",sign); | |
| 60 | + rtmpUrl = Configs.get("rtmp.url").replaceAll("\\{TAG\\}", tag).replaceAll("\\{sign\\}",sign); | |
| 61 | + ffmpegPath = Configs.get("ffmpeg.path"); | |
| 42 | 62 | |
| 43 | - // 【修复】自动判断协议格式,避免硬编码 -f rtsp 导致 RTMP 推流失败 | |
| 44 | - String formatFlag = ""; | |
| 63 | + // 自动判断协议格式 | |
| 64 | + formatFlag = ""; | |
| 45 | 65 | if (rtmpUrl.startsWith("rtsp://")) { |
| 46 | 66 | formatFlag = "-f rtsp"; |
| 47 | 67 | } else if (rtmpUrl.startsWith("rtmp://")) { |
| 48 | 68 | formatFlag = "-f flv"; |
| 49 | 69 | } |
| 50 | 70 | |
| 51 | - // 低延迟:缩短 HTTP-FLV 探测与缓冲,尽快向 ZLM 输出(仍受设备 GOP/关键帧限制) | |
| 52 | - String inputLowLatency = | |
| 53 | - "-fflags +nobuffer+discardcorrupt -flags low_delay -probesize 32 -analyzeduration 0"; | |
| 54 | - String outputLowLatency = "-flush_packets 1"; | |
| 55 | - | |
| 56 | - // 构造命令:只处理视频流和非对讲的音频流 | |
| 57 | - String cmd = String.format( | |
| 58 | - "%s %s -i http://127.0.0.1:%d/video/%s -vcodec copy -acodec aac %s %s %s", | |
| 59 | - Configs.get("ffmpeg.path"), | |
| 60 | - inputLowLatency, | |
| 71 | + // 构造FFmpeg命令 | |
| 72 | + String cmd = String.format("%s -i http://127.0.0.1:%d/video/%s -vcodec copy -acodec aac %s %s", | |
| 73 | + ffmpegPath, | |
| 61 | 74 | Configs.getInt("server.http.port", 3333), |
| 62 | 75 | tag, |
| 63 | - outputLowLatency, | |
| 64 | 76 | formatFlag, |
| 65 | 77 | rtmpUrl |
| 66 | 78 | ); |
| 67 | 79 | |
| 68 | - logger.info("FFmpeg Push Started. Tag: {}, CMD: {}", tag, cmd); | |
| 80 | + logger.info("==========================================="); | |
| 81 | + logger.info("[{}] ====== FFmpeg推流任务启动 ======", tag); | |
| 82 | + logger.info("[{}] FFmpeg路径: {}", tag, ffmpegPath); | |
| 83 | + logger.info("[{}] 目标地址: {}", tag, rtmpUrl); | |
| 84 | + logger.info("[{}] 完整命令: {}", tag, cmd); | |
| 85 | + logger.info("[{}] HTTP端口: {}", tag, Configs.getInt("server.http.port", 3333)); | |
| 86 | + logger.info("[{}] 源流地址: http://127.0.0.1:{}/video/{}", tag, Configs.getInt("server.http.port", 3333), tag); | |
| 87 | + logger.info("[{}] 启动时间: {}", tag, new java.util.Date()); | |
| 88 | + logger.info("==========================================="); | |
| 69 | 89 | |
| 90 | + // 执行FFmpeg命令 | |
| 70 | 91 | process = Runtime.getRuntime().exec(cmd); |
| 92 | + processStartTime = System.currentTimeMillis(); | |
| 93 | + | |
| 94 | + // 记录进程启动信息(Java 8兼容方式) | |
| 95 | + logger.info("[{}] FFmpeg进程已启动", tag); | |
| 96 | + | |
| 71 | 97 | stderr = process.getErrorStream(); |
| 72 | 98 | stdout = process.getInputStream(); |
| 73 | 99 | |
| ... | ... | @@ -76,9 +102,15 @@ public class RTMPPublisher extends Thread |
| 76 | 102 | Thread stdoutConsumer = new Thread(() -> { |
| 77 | 103 | try { |
| 78 | 104 | byte[] buffer = new byte[512]; |
| 105 | + int count = 0; | |
| 79 | 106 | while (running && finalStdout.read(buffer) > -1) { |
| 80 | - // 只消费,不输出 | |
| 107 | + count++; | |
| 108 | + // 每1000次读取才打印一次(避免刷屏) | |
| 109 | + if (debugMode && count % 1000 == 0) { | |
| 110 | + logger.debug("[{}] FFmpeg stdout消费中... count: {}", tag, count); | |
| 111 | + } | |
| 81 | 112 | } |
| 113 | + logger.info("[{}] FFmpeg stdout消费结束, 共消费{}次", tag, count); | |
| 82 | 114 | } catch (Exception e) { |
| 83 | 115 | // 忽略异常 |
| 84 | 116 | } |
| ... | ... | @@ -86,26 +118,60 @@ public class RTMPPublisher extends Thread |
| 86 | 118 | stdoutConsumer.setDaemon(true); |
| 87 | 119 | stdoutConsumer.start(); |
| 88 | 120 | |
| 89 | - // 消费 stderr 日志流,防止阻塞 | |
| 121 | + // 消费 stderr 日志流 | |
| 122 | + StringBuilder errorLog = new StringBuilder(); | |
| 123 | + int errorCount = 0; | |
| 90 | 124 | while (running && (len = stderr.read(buff)) > -1) |
| 91 | 125 | { |
| 92 | 126 | if (debugMode) { |
| 93 | 127 | System.out.print(new String(buff, 0, len)); |
| 94 | 128 | } |
| 129 | + | |
| 130 | + // 收集错误日志(便于排查问题) | |
| 131 | + errorLog.append(new String(buff, 0, len)); | |
| 132 | + errorCount++; | |
| 133 | + | |
| 134 | + // 每100条错误日志打印一次摘要 | |
| 135 | + if (errorCount % 100 == 0) { | |
| 136 | + String lastError = errorLog.length() > 500 | |
| 137 | + ? errorLog.substring(errorLog.length() - 500) | |
| 138 | + : errorLog.toString(); | |
| 139 | + logger.debug("[{}] FFmpeg错误日志摘要: {}", tag, lastError); | |
| 140 | + } | |
| 95 | 141 | } |
| 96 | 142 | |
| 97 | 143 | // 进程退出处理 |
| 98 | 144 | int exitCode = process.waitFor(); |
| 99 | - logger.warn("FFmpeg process exited. Code: {}, Tag: {}", exitCode, tag); | |
| 145 | + long runDuration = System.currentTimeMillis() - processStartTime; | |
| 146 | + logger.warn("==========================================="); | |
| 147 | + logger.warn("[{}] ====== FFmpeg推流任务结束 ======", tag); | |
| 148 | + logger.warn("[{}] 退出代码: {}", tag, exitCode); | |
| 149 | + logger.warn("[{}] 运行时间: {} ms", tag, runDuration); | |
| 150 | + logger.warn("[{}] 错误日志条数: {}", tag, errorCount); | |
| 151 | + | |
| 152 | + // 分析退出原因 | |
| 153 | + if (exitCode == 0) { | |
| 154 | + logger.info("[{}] FFmpeg正常退出", tag); | |
| 155 | + } else if (exitCode == -1 || exitCode == 255) { | |
| 156 | + logger.warn("[{}] FFmpeg被信号终止 (exitCode={})", tag, exitCode); | |
| 157 | + } else { | |
| 158 | + // 保存最后一段错误日志 | |
| 159 | + String lastError = errorLog.length() > 1000 | |
| 160 | + ? errorLog.substring(errorLog.length() - 1000) | |
| 161 | + : errorLog.toString(); | |
| 162 | + logger.error("[{}] FFmpeg异常退出, 最后错误日志:\n{}", tag, lastError); | |
| 163 | + } | |
| 164 | + logger.warn("==========================================="); | |
| 165 | + | |
| 100 | 166 | } |
| 101 | 167 | catch(InterruptedException ex) |
| 102 | 168 | { |
| 103 | - logger.info("RTMPPublisher interrupted: {}", tag); | |
| 169 | + logger.info("[{}] RTMPPublisher被中断: {}", tag, ex.getMessage()); | |
| 104 | 170 | Thread.currentThread().interrupt(); |
| 105 | 171 | } |
| 106 | 172 | catch(Exception ex) |
| 107 | 173 | { |
| 108 | - logger.error("RTMPPublisher Error: " + tag, ex); | |
| 174 | + logger.error("[{}] RTMPPublisher异常: {}", tag, ex); | |
| 109 | 175 | } |
| 110 | 176 | finally |
| 111 | 177 | { |
| ... | ... | @@ -117,40 +183,184 @@ public class RTMPPublisher extends Thread |
| 117 | 183 | closeQuietly(process.getOutputStream()); |
| 118 | 184 | closeQuietly(process.getErrorStream()); |
| 119 | 185 | } |
| 186 | + logger.info("[{}] RTMPPublisher资源已释放", tag); | |
| 120 | 187 | } |
| 121 | 188 | } |
| 122 | 189 | |
| 190 | + /** | |
| 191 | + * 关闭FFmpeg推流 | |
| 192 | + * 优化关闭逻辑,确保进程被正确终止(Java 8兼容) | |
| 193 | + */ | |
| 123 | 194 | public void close() |
| 124 | 195 | { |
| 196 | + logger.info("[{}] ====== 开始关闭FFmpeg推流 ======", tag); | |
| 197 | + logger.info("[{}] 关闭请求时间: {}", tag, new java.util.Date()); | |
| 198 | + | |
| 125 | 199 | try { |
| 126 | 200 | // 设置停止标志 |
| 127 | 201 | running = false; |
| 128 | 202 | |
| 129 | 203 | if (process != null) { |
| 204 | + long runDuration = processStartTime > 0 ? System.currentTimeMillis() - processStartTime : 0; | |
| 205 | + logger.info("[{}] 正在终止FFmpeg进程... (已运行{}ms)", tag, runDuration); | |
| 206 | + | |
| 130 | 207 | // 先尝试正常终止 |
| 131 | 208 | process.destroy(); |
| 132 | 209 | |
| 133 | - // 等待最多 3 秒 | |
| 210 | + // 等待最多3秒 | |
| 134 | 211 | boolean exited = process.waitFor(3, TimeUnit.SECONDS); |
| 135 | 212 | |
| 136 | 213 | if (!exited) { |
| 137 | - // 如果还没退出,强制终止 | |
| 138 | - logger.warn("FFmpeg process did not exit gracefully, forcing termination: {}", tag); | |
| 214 | + logger.warn("[{}] FFmpeg进程未能在3秒内正常退出,开始强制终止...", tag); | |
| 215 | + | |
| 216 | + // 强制终止(Java 8的方式) | |
| 139 | 217 | process.destroyForcibly(); |
| 140 | - process.waitFor(2, TimeUnit.SECONDS); | |
| 218 | + | |
| 219 | + // 再等待2秒 | |
| 220 | + try { | |
| 221 | + exited = process.waitFor(2, TimeUnit.SECONDS); | |
| 222 | + } catch (InterruptedException e) { | |
| 223 | + Thread.currentThread().interrupt(); | |
| 224 | + } | |
| 225 | + if (!exited) { | |
| 226 | + logger.error("[{}] FFmpeg进程强制终止失败,可能存在资源泄漏", tag); | |
| 227 | + } else { | |
| 228 | + logger.info("[{}] FFmpeg进程已强制终止", tag); | |
| 229 | + } | |
| 230 | + } else { | |
| 231 | + int exitCode = process.exitValue(); | |
| 232 | + logger.info("[{}] FFmpeg进程已正常终止, 退出代码: {}", tag, exitCode); | |
| 141 | 233 | } |
| 142 | 234 | |
| 143 | - logger.info("FFmpeg process terminated: {}", tag); | |
| 235 | + // 检查是否需要杀掉残留进程 | |
| 236 | + checkAndKillOrphanedProcesses(); | |
| 237 | + | |
| 238 | + } else { | |
| 239 | + logger.info("[{}] FFmpeg进程为空,无需关闭", tag); | |
| 144 | 240 | } |
| 145 | 241 | |
| 242 | + logger.info("[{}] ====== FFmpeg推流已关闭 ======", tag); | |
| 243 | + | |
| 146 | 244 | // 中断线程(如果还在阻塞读取) |
| 147 | 245 | this.interrupt(); |
| 148 | 246 | |
| 149 | 247 | // 等待线程结束 |
| 150 | 248 | this.join(2000); |
| 249 | + if (this.isAlive()) { | |
| 250 | + logger.warn("[{}] RTMPPublisher线程未能正常结束", tag); | |
| 251 | + } | |
| 151 | 252 | |
| 152 | 253 | } catch(Exception e) { |
| 153 | - logger.error("Error closing RTMPPublisher: " + tag, e); | |
| 254 | + logger.error("[{}] 关闭RTMPPublisher时出错: {}", tag, e); | |
| 255 | + } | |
| 256 | + } | |
| 257 | + | |
| 258 | + /** | |
| 259 | + * 检查并杀掉可能残留的FFmpeg进程 | |
| 260 | + * Java 8兼容实现,使用系统命令查找和终止进程 | |
| 261 | + */ | |
| 262 | + private void checkAndKillOrphanedProcesses() { | |
| 263 | + try { | |
| 264 | + logger.debug("[{}] 检查是否有FFmpeg残留进程...", tag); | |
| 265 | + // 判断操作系统类型 | |
| 266 | + String os = System.getProperty("os.name").toLowerCase(); | |
| 267 | + boolean isWindows = os.contains("win"); | |
| 268 | + | |
| 269 | + if (isWindows) { | |
| 270 | + // Windows: 使用tasklist和taskkill | |
| 271 | + killOrphanedProcessesWindows(); | |
| 272 | + } else { | |
| 273 | + // Linux/Unix: 使用ps和kill | |
| 274 | + killOrphanedProcessesUnix(); | |
| 275 | + } | |
| 276 | + } catch (Exception e) { | |
| 277 | + logger.error("[{}] 检查残留进程时出错: {}", tag, e.getMessage()); | |
| 278 | + } | |
| 279 | + } | |
| 280 | + | |
| 281 | + /** | |
| 282 | + * Windows环境下查找并终止残留的FFmpeg进程 | |
| 283 | + */ | |
| 284 | + private void killOrphanedProcessesWindows() { | |
| 285 | + try { | |
| 286 | + // 查找包含ffmpeg的进程 | |
| 287 | + ProcessBuilder pb = new ProcessBuilder("tasklist", "/FI", "IMAGENAME eq ffmpeg.exe", "/FO", "CSV", "/NH"); | |
| 288 | + Process process = pb.start(); | |
| 289 | + java.io.BufferedReader reader = new java.io.BufferedReader( | |
| 290 | + new java.io.InputStreamReader(process.getInputStream())); | |
| 291 | + | |
| 292 | + String line; | |
| 293 | + while ((line = reader.readLine()) != null) { | |
| 294 | + // CSV格式: "Image Name","PID","Session Name","Session#","Mem Usage" | |
| 295 | + String[] parts = line.split(","); | |
| 296 | + if (parts.length > 1) { | |
| 297 | + String pidStr = parts[1].replaceAll("\"", "").trim(); | |
| 298 | + try { | |
| 299 | + long pid = Long.parseLong(pidStr); | |
| 300 | + // 检查命令行是否包含当前tag或wvp-jt1078 | |
| 301 | + ProcessBuilder cmdPb = new ProcessBuilder("wmic", "process", "where", "ProcessId=" + pid, "get", "CommandLine"); | |
| 302 | + Process cmdProcess = cmdPb.start(); | |
| 303 | + java.io.BufferedReader cmdReader = new java.io.BufferedReader( | |
| 304 | + new java.io.InputStreamReader(cmdProcess.getInputStream())); | |
| 305 | + | |
| 306 | + String cmdLine; | |
| 307 | + boolean shouldKill = false; | |
| 308 | + while ((cmdLine = cmdReader.readLine()) != null) { | |
| 309 | + if (cmdLine.toLowerCase().contains("ffmpeg") | |
| 310 | + && (cmdLine.contains(tag) || cmdLine.contains("wvp-jt1078"))) { | |
| 311 | + shouldKill = true; | |
| 312 | + break; | |
| 313 | + } | |
| 314 | + } | |
| 315 | + | |
| 316 | + if (shouldKill) { | |
| 317 | + logger.warn("[{}] 发现残留FFmpeg进程[PID:{}],正在终止...", tag, pid); | |
| 318 | + Process killProcess = Runtime.getRuntime().exec("taskkill /PID " + pid + " /F"); | |
| 319 | + killProcess.waitFor(); | |
| 320 | + logger.info("[{}] 残留FFmpeg进程已终止[PID:{}]", tag, pid); | |
| 321 | + } | |
| 322 | + } catch (NumberFormatException e) { | |
| 323 | + // 忽略非数字PID | |
| 324 | + } | |
| 325 | + } | |
| 326 | + } | |
| 327 | + } catch (Exception e) { | |
| 328 | + logger.error("[{}] Windows检查残留进程时出错: {}", tag, e.getMessage()); | |
| 329 | + } | |
| 330 | + } | |
| 331 | + | |
| 332 | + /** | |
| 333 | + * Linux/Unix环境下查找并终止残留的FFmpeg进程 | |
| 334 | + */ | |
| 335 | + private void killOrphanedProcessesUnix() { | |
| 336 | + try { | |
| 337 | + // 查找包含ffmpeg的进程 | |
| 338 | + ProcessBuilder pb = new ProcessBuilder("ps", "-ef"); | |
| 339 | + Process process = pb.start(); | |
| 340 | + java.io.BufferedReader reader = new java.io.BufferedReader( | |
| 341 | + new java.io.InputStreamReader(process.getInputStream())); | |
| 342 | + | |
| 343 | + String line; | |
| 344 | + while ((line = reader.readLine()) != null) { | |
| 345 | + // ps -ef 输出格式: UID PID PPID C STIME TTY TIME CMD | |
| 346 | + if (line.toLowerCase().contains("ffmpeg") && line.contains(tag)) { | |
| 347 | + String[] parts = line.split("\\s+"); | |
| 348 | + if (parts.length > 1) { | |
| 349 | + String pidStr = parts[1]; | |
| 350 | + try { | |
| 351 | + long pid = Long.parseLong(pidStr); | |
| 352 | + logger.warn("[{}] 发现残留FFmpeg进程[PID:{}],正在终止...", tag, pid); | |
| 353 | + Process killProcess = Runtime.getRuntime().exec("kill -9 " + pid); | |
| 354 | + killProcess.waitFor(); | |
| 355 | + logger.info("[{}] 残留FFmpeg进程已终止[PID:{}]", tag, pid); | |
| 356 | + } catch (NumberFormatException e) { | |
| 357 | + // 忽略非数字PID | |
| 358 | + } | |
| 359 | + } | |
| 360 | + } | |
| 361 | + } | |
| 362 | + } catch (Exception e) { | |
| 363 | + logger.error("[{}] Unix检查残留进程时出错: {}", tag, e.getMessage()); | |
| 154 | 364 | } |
| 155 | 365 | } |
| 156 | 366 | |
| ... | ... | @@ -166,4 +376,27 @@ public class RTMPPublisher extends Thread |
| 166 | 376 | } |
| 167 | 377 | } |
| 168 | 378 | } |
| 379 | + | |
| 380 | + /** | |
| 381 | + * 获取推流状态 | |
| 382 | + */ | |
| 383 | + public boolean isRunning() { | |
| 384 | + return running && this.isAlive() && process != null; | |
| 385 | + } | |
| 386 | + | |
| 387 | + /** | |
| 388 | + * 获取FFmpeg进程信息(用于调试) | |
| 389 | + */ | |
| 390 | + public String getProcessInfo() { | |
| 391 | + if (process == null) { | |
| 392 | + return "process is null"; | |
| 393 | + } | |
| 394 | + try { | |
| 395 | + long runDuration = processStartTime > 0 ? System.currentTimeMillis() - processStartTime : 0; | |
| 396 | + return String.format("FFmpeg进程, 已运行%dms, alive=%s", | |
| 397 | + runDuration, process.isAlive()); | |
| 398 | + } catch (Exception e) { | |
| 399 | + return "获取进程信息失败: " + e.getMessage(); | |
| 400 | + } | |
| 401 | + } | |
| 169 | 402 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| 1 | -package com.genersoft.iot.vmp.media.zlm; | |
| 2 | - | |
| 3 | -import com.alibaba.fastjson2.JSON; | |
| 4 | -import com.alibaba.fastjson2.JSONObject; | |
| 5 | -import com.genersoft.iot.vmp.common.InviteInfo; | |
| 6 | -import com.genersoft.iot.vmp.common.InviteSessionType; | |
| 7 | -import com.genersoft.iot.vmp.common.StreamInfo; | |
| 8 | -import com.genersoft.iot.vmp.common.VideoManagerConstants; | |
| 9 | -import com.genersoft.iot.vmp.conf.UserSetting; | |
| 10 | -import com.genersoft.iot.vmp.conf.exception.ServiceException; | |
| 11 | -import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 12 | -import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 13 | -import com.genersoft.iot.vmp.gb28181.event.EventPublisher; | |
| 14 | -import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | |
| 15 | -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; | |
| 16 | -import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; | |
| 17 | -import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 18 | -import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | |
| 19 | -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | |
| 20 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | |
| 21 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | |
| 22 | -import com.genersoft.iot.vmp.media.zlm.dto.HookType; | |
| 23 | -import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 24 | -import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; | |
| 25 | -import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; | |
| 26 | -import com.genersoft.iot.vmp.media.zlm.dto.hook.*; | |
| 27 | -import com.genersoft.iot.vmp.service.*; | |
| 28 | -import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; | |
| 29 | -import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 30 | -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 31 | -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 32 | -import com.genersoft.iot.vmp.utils.DateUtil; | |
| 33 | -import com.genersoft.iot.vmp.vmanager.bean.*; | |
| 34 | -import com.genersoft.iot.vmp.vmanager.jt1078.platform.Jt1078OfCarController; | |
| 35 | -import com.genersoft.iot.vmp.vmanager.jt1078.platform.service.IntercomService; | |
| 36 | -import org.apache.commons.lang3.StringUtils; | |
| 37 | -import org.slf4j.Logger; | |
| 38 | -import org.slf4j.LoggerFactory; | |
| 39 | -import org.springframework.beans.factory.annotation.Autowired; | |
| 40 | -import org.springframework.beans.factory.annotation.Qualifier; | |
| 41 | -import org.springframework.data.redis.core.RedisTemplate; | |
| 42 | -import org.springframework.scheduling.annotation.Scheduled; | |
| 43 | -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | |
| 44 | -import org.springframework.util.ObjectUtils; | |
| 45 | -import org.springframework.web.bind.annotation.*; | |
| 46 | -import org.springframework.web.context.request.async.DeferredResult; | |
| 47 | - | |
| 48 | -import javax.annotation.Resource; | |
| 49 | -import javax.servlet.http.HttpServletRequest; | |
| 50 | -import javax.sip.InvalidArgumentException; | |
| 51 | -import javax.sip.SipException; | |
| 52 | -import java.text.ParseException; | |
| 53 | -import java.util.*; | |
| 54 | -import java.util.concurrent.TimeUnit; | |
| 55 | - | |
| 56 | -/** | |
| 57 | - * @description:针对 ZLMediaServer的hook事件监听 | |
| 58 | - * @author: swwheihei | |
| 59 | - * @date: 2020年5月8日 上午10:46:48 | |
| 60 | - */ | |
| 61 | -@RestController | |
| 62 | -@RequestMapping("/index/hook") | |
| 63 | -public class ZLMHttpHookListener { | |
| 64 | - | |
| 65 | - private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class); | |
| 66 | - | |
| 67 | - @Autowired | |
| 68 | - private SIPCommander cmder; | |
| 69 | - | |
| 70 | - @Autowired | |
| 71 | - private ISIPCommanderForPlatform commanderFroPlatform; | |
| 72 | - | |
| 73 | - @Autowired | |
| 74 | - private AudioBroadcastManager audioBroadcastManager; | |
| 75 | - | |
| 76 | - @Autowired | |
| 77 | - private IPlayService playService; | |
| 78 | - | |
| 79 | - @Autowired | |
| 80 | - private IVideoManagerStorage storager; | |
| 81 | - | |
| 82 | - @Autowired | |
| 83 | - private IRedisCatchStorage redisCatchStorage; | |
| 84 | - | |
| 85 | - @Autowired | |
| 86 | - private IInviteStreamService inviteStreamService; | |
| 87 | - | |
| 88 | - @Autowired | |
| 89 | - private IDeviceService deviceService; | |
| 90 | - | |
| 91 | - @Autowired | |
| 92 | - private IMediaServerService mediaServerService; | |
| 93 | - | |
| 94 | - @Autowired | |
| 95 | - private IStreamProxyService streamProxyService; | |
| 96 | - | |
| 97 | - @Autowired | |
| 98 | - private DeferredResultHolder resultHolder; | |
| 99 | - | |
| 100 | - @Autowired | |
| 101 | - private IMediaService mediaService; | |
| 102 | - | |
| 103 | - @Autowired | |
| 104 | - private EventPublisher eventPublisher; | |
| 105 | - | |
| 106 | - @Autowired | |
| 107 | - private ZLMMediaListManager zlmMediaListManager; | |
| 108 | - | |
| 109 | - @Autowired | |
| 110 | - private ZlmHttpHookSubscribe subscribe; | |
| 111 | - | |
| 112 | - @Autowired | |
| 113 | - private UserSetting userSetting; | |
| 114 | - | |
| 115 | - @Autowired | |
| 116 | - private IUserService userService; | |
| 117 | - | |
| 118 | - @Autowired | |
| 119 | - private ICloudRecordService cloudRecordService; | |
| 120 | - | |
| 121 | - @Autowired | |
| 122 | - private VideoStreamSessionManager sessionManager; | |
| 123 | - | |
| 124 | - @Autowired | |
| 125 | - private SSRCFactory ssrcFactory; | |
| 126 | - | |
| 127 | - @Qualifier("taskExecutor") | |
| 128 | - @Autowired | |
| 129 | - private ThreadPoolTaskExecutor taskExecutor; | |
| 130 | - | |
| 131 | - @Autowired | |
| 132 | - private RedisTemplate<Object, Object> redisTemplate; | |
| 133 | - | |
| 134 | - @Autowired | |
| 135 | - private Jt1078OfCarController jt1078OfCarController; | |
| 136 | - | |
| 137 | - @Autowired | |
| 138 | - private StremProxyService1078 stremProxyService1078; | |
| 139 | - @Resource | |
| 140 | - private IntercomService intercomService; | |
| 141 | - | |
| 142 | - | |
| 143 | - /** | |
| 144 | - * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 | |
| 145 | - */ | |
| 146 | - @ResponseBody | |
| 147 | - | |
| 148 | - @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") | |
| 149 | - public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) { | |
| 150 | - | |
| 151 | - | |
| 152 | - taskExecutor.execute(() -> { | |
| 153 | - List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive); | |
| 154 | - if (subscribes != null && !subscribes.isEmpty()) { | |
| 155 | - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 156 | - subscribe.response(null, param); | |
| 157 | - } | |
| 158 | - } | |
| 159 | - }); | |
| 160 | - mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData()); | |
| 161 | - | |
| 162 | - return HookResult.SUCCESS(); | |
| 163 | - } | |
| 164 | - | |
| 165 | - /** | |
| 166 | - * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 | |
| 167 | - */ | |
| 168 | - @ResponseBody | |
| 169 | - | |
| 170 | - @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") | |
| 171 | - public HookResult onPlay(@RequestBody OnPlayHookParam param) { | |
| 172 | - if (logger.isDebugEnabled()) { | |
| 173 | - logger.debug("[ZLM HOOK] 播放鉴权:{}->{}", param.getMediaServerId(), param); | |
| 174 | - } | |
| 175 | - String mediaServerId = param.getMediaServerId(); | |
| 176 | - | |
| 177 | - taskExecutor.execute(() -> { | |
| 178 | - JSONObject json = (JSONObject) JSON.toJSON(param); | |
| 179 | - ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json); | |
| 180 | - if (subscribe != null) { | |
| 181 | - MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); | |
| 182 | - if (mediaInfo != null) { | |
| 183 | - subscribe.response(mediaInfo, param); | |
| 184 | - } | |
| 185 | - } | |
| 186 | - }); | |
| 187 | - if (!"rtp".equals(param.getApp())) { | |
| 188 | - Map<String, String> paramMap = urlParamToMap(param.getParams()); | |
| 189 | - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); | |
| 190 | - if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) { | |
| 191 | - return new HookResult(401, "Unauthorized"); | |
| 192 | - } | |
| 193 | - } | |
| 194 | - return HookResult.SUCCESS(); | |
| 195 | - } | |
| 196 | - | |
| 197 | - /** | |
| 198 | - * rtsp/rtmp/rtp推流鉴权事件。 | |
| 199 | - */ | |
| 200 | - @ResponseBody | |
| 201 | - @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") | |
| 202 | - public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) { | |
| 203 | - | |
| 204 | - JSONObject json = (JSONObject) JSON.toJSON(param); | |
| 205 | - | |
| 206 | - logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param); | |
| 207 | - | |
| 208 | - String mediaServerId = json.getString("mediaServerId"); | |
| 209 | - MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); | |
| 210 | - if (mediaInfo == null) { | |
| 211 | - return new HookResultForOnPublish(200, "success"); | |
| 212 | - } | |
| 213 | - // 推流鉴权的处理 | |
| 214 | - if (!"rtp".equals(param.getApp()) && !"schedule".equals(param.getApp())) { | |
| 215 | - StreamProxyItem stream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | |
| 216 | - if (stream != null) { | |
| 217 | - HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); | |
| 218 | - result.setEnable_audio(stream.isEnableAudio()); | |
| 219 | - result.setEnable_mp4(stream.isEnableMp4()); | |
| 220 | - return result; | |
| 221 | - } | |
| 222 | - if (userSetting.getPushAuthority()) { | |
| 223 | - // 推流鉴权 | |
| 224 | - if (param.getParams() == null) { | |
| 225 | - logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)"); | |
| 226 | - return new HookResultForOnPublish(401, "Unauthorized"); | |
| 227 | - } | |
| 228 | - Map<String, String> paramMap = urlParamToMap(param.getParams()); | |
| 229 | - String sign = paramMap.get("sign"); | |
| 230 | - if (sign == null) { | |
| 231 | - logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)"); | |
| 232 | - return new HookResultForOnPublish(401, "Unauthorized"); | |
| 233 | - } | |
| 234 | - // 鉴权配置 | |
| 235 | - // 推流自定义播放鉴权码 | |
| 236 | - // 鉴权配置 | |
| 237 | - boolean hasAuthority = userService.checkPushAuthority(null, sign); | |
| 238 | - if (!hasAuthority) { | |
| 239 | - logger.info("推流鉴权失败: sign 无权限: sign={}", sign); | |
| 240 | - return new HookResultForOnPublish(401, "Unauthorized"); | |
| 241 | - } | |
| 242 | - StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | |
| 243 | - streamAuthorityInfo.setCallId(sign); | |
| 244 | - streamAuthorityInfo.setSign(sign); | |
| 245 | - // 鉴权通过 | |
| 246 | - redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); | |
| 247 | - } | |
| 248 | - } else { | |
| 249 | - zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId()); | |
| 250 | - } | |
| 251 | - | |
| 252 | - | |
| 253 | - HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); | |
| 254 | - result.setEnable_audio(true); | |
| 255 | - taskExecutor.execute(() -> { | |
| 256 | - ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json); | |
| 257 | - if (subscribe != null) { | |
| 258 | - subscribe.response(mediaInfo, param); | |
| 259 | - } | |
| 260 | - }); | |
| 261 | - | |
| 262 | - // 是否录像 | |
| 263 | - if ("rtp".equals(param.getApp())) { | |
| 264 | - result.setEnable_mp4(userSetting.getRecordSip()); | |
| 265 | - } else { | |
| 266 | - result.setEnable_mp4(userSetting.isRecordPushLive()); | |
| 267 | - } | |
| 268 | - // 国标流 | |
| 269 | - if ("rtp".equals(param.getApp())) { | |
| 270 | - | |
| 271 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | |
| 272 | - | |
| 273 | - // 单端口模式下修改流 ID | |
| 274 | - if (!mediaInfo.isRtpEnable() && inviteInfo == null) { | |
| 275 | - String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16)); | |
| 276 | - inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc); | |
| 277 | - if (inviteInfo != null) { | |
| 278 | - result.setStream_replace(inviteInfo.getStream()); | |
| 279 | - logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream()); | |
| 280 | - } | |
| 281 | - } | |
| 282 | - | |
| 283 | - // 设置音频信息及录制信息 | |
| 284 | - List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); | |
| 285 | - if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { | |
| 286 | - | |
| 287 | - // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用 | |
| 288 | - StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | |
| 289 | - streamAuthorityInfo.setApp(param.getApp()); | |
| 290 | - streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream()); | |
| 291 | - streamAuthorityInfo.setCallId(ssrcTransactionForAll.get(0).getSipTransactionInfo().getCallId()); | |
| 292 | - | |
| 293 | - redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), ssrcTransactionForAll.get(0).getStream(), streamAuthorityInfo); | |
| 294 | - | |
| 295 | - String deviceId = ssrcTransactionForAll.get(0).getDeviceId(); | |
| 296 | - String channelId = ssrcTransactionForAll.get(0).getChannelId(); | |
| 297 | - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); | |
| 298 | - if (deviceChannel != null) { | |
| 299 | - result.setEnable_audio(deviceChannel.isHasAudio()); | |
| 300 | - } | |
| 301 | - // 如果是录像下载就设置视频间隔十秒 | |
| 302 | - if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { | |
| 303 | - // 获取录像的总时长,然后设置为这个视频的时长 | |
| 304 | - InviteInfo inviteInfoForDownload = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, param.getStream()); | |
| 305 | - if (inviteInfoForDownload != null && inviteInfoForDownload.getStreamInfo() != null) { | |
| 306 | - String startTime = inviteInfoForDownload.getStreamInfo().getStartTime(); | |
| 307 | - String endTime = inviteInfoForDownload.getStreamInfo().getEndTime(); | |
| 308 | - long difference = DateUtil.getDifference(startTime, endTime) / 1000; | |
| 309 | - result.setMp4_max_second((int) difference); | |
| 310 | - result.setEnable_mp4(true); | |
| 311 | - // 设置为2保证得到的mp4的时长是正常的 | |
| 312 | - result.setModify_stamp(2); | |
| 313 | - } | |
| 314 | - } | |
| 315 | - // 如果是talk对讲,则默认获取声音 | |
| 316 | - if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) { | |
| 317 | - result.setEnable_audio(true); | |
| 318 | - } | |
| 319 | - } | |
| 320 | - } else if (param.getApp().equals("broadcast")) { | |
| 321 | - result.setEnable_audio(true); | |
| 322 | - } else if (param.getApp().equals("talk")) { | |
| 323 | - result.setEnable_audio(true); | |
| 324 | - } | |
| 325 | - if (param.getApp().equalsIgnoreCase("rtp")) { | |
| 326 | - String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream(); | |
| 327 | - OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey); | |
| 328 | - | |
| 329 | - String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + param.getStream(); | |
| 330 | - OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo) redisTemplate.opsForValue().get(receiveKeyForPS); | |
| 331 | - if (otherRtpSendInfo != null || otherPsSendInfo != null) { | |
| 332 | - result.setEnable_mp4(true); | |
| 333 | - } | |
| 334 | - } | |
| 335 | - logger.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, result); | |
| 336 | - return result; | |
| 337 | - } | |
| 338 | - | |
| 339 | - | |
| 340 | - /** | |
| 341 | - * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 | |
| 342 | - */ | |
| 343 | - @ResponseBody | |
| 344 | - @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") | |
| 345 | - public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) { | |
| 346 | - | |
| 347 | - if (param.isRegist()) { | |
| 348 | - logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 349 | - } else { | |
| 350 | - logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 351 | - } | |
| 352 | - | |
| 353 | - JSONObject json = (JSONObject) JSON.toJSON(param); | |
| 354 | - taskExecutor.execute(() -> { | |
| 355 | - ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); | |
| 356 | - MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | |
| 357 | - if (mediaInfo == null) { | |
| 358 | - logger.info("[ZLM HOOK] 流变化未找到ZLM, {}", param.getMediaServerId()); | |
| 359 | - return; | |
| 360 | - } | |
| 361 | - if (subscribe != null) { | |
| 362 | - subscribe.response(mediaInfo, param); | |
| 363 | - } | |
| 364 | - | |
| 365 | - List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); | |
| 366 | - // TODO 重构此处逻辑 | |
| 367 | - if (param.isRegist()) { | |
| 368 | - // 处理流注册的鉴权信息, 流注销这里不再删除鉴权信息,下次来了新的鉴权信息会对就的进行覆盖 | |
| 369 | - if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() | |
| 370 | - || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() | |
| 371 | - || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { | |
| 372 | - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); | |
| 373 | - if (streamAuthorityInfo == null) { | |
| 374 | - streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | |
| 375 | - } else { | |
| 376 | - streamAuthorityInfo.setOriginType(param.getOriginType()); | |
| 377 | - streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr()); | |
| 378 | - } | |
| 379 | - redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); | |
| 380 | - } | |
| 381 | - } | |
| 382 | - if ("rtsp".equals(param.getSchema())) { | |
| 383 | - logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream()); | |
| 384 | - if (param.isRegist()) { | |
| 385 | - mediaServerService.addCount(param.getMediaServerId()); | |
| 386 | - } else { | |
| 387 | - mediaServerService.removeCount(param.getMediaServerId()); | |
| 388 | - } | |
| 389 | - | |
| 390 | - int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream()); | |
| 391 | - if (updateStatusResult > 0) { | |
| 392 | - | |
| 393 | - } | |
| 394 | - | |
| 395 | - if ("rtp".equals(param.getApp()) && !param.isRegist()) { | |
| 396 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | |
| 397 | - if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) { | |
| 398 | - inviteStreamService.removeInviteInfo(inviteInfo); | |
| 399 | - storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); | |
| 400 | - } | |
| 401 | - } else if ("broadcast".equals(param.getApp())) { | |
| 402 | - // 语音对讲推流 stream需要满足格式deviceId_channelId | |
| 403 | - if (param.getStream().indexOf("_") > 0) { | |
| 404 | - String[] streamArray = param.getStream().split("_"); | |
| 405 | - if (streamArray.length == 2) { | |
| 406 | - String deviceId = streamArray[0]; | |
| 407 | - String channelId = streamArray[1]; | |
| 408 | - Device device = deviceService.getDevice(deviceId); | |
| 409 | - if (device != null) { | |
| 410 | - if (param.isRegist()) { | |
| 411 | - if (audioBroadcastManager.exit(deviceId, channelId)) { | |
| 412 | - playService.stopAudioBroadcast(deviceId, channelId); | |
| 413 | - } | |
| 414 | - // 开启语音对讲通道 | |
| 415 | - try { | |
| 416 | - playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg) -> { | |
| 417 | - logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); | |
| 418 | - }); | |
| 419 | - } catch (InvalidArgumentException | ParseException | SipException e) { | |
| 420 | - logger.error("[命令发送失败] 语音对讲: {}", e.getMessage()); | |
| 421 | - } | |
| 422 | - } else { | |
| 423 | - // 流注销 | |
| 424 | - playService.stopAudioBroadcast(deviceId, channelId); | |
| 425 | - } | |
| 426 | - } else { | |
| 427 | - logger.info("[语音对讲] 未找到设备:{}", deviceId); | |
| 428 | - } | |
| 429 | - } | |
| 430 | - } | |
| 431 | - } else if ("talk".equals(param.getApp())) { | |
| 432 | - // 语音对讲推流 stream需要满足格式deviceId_channelId | |
| 433 | - if (param.getStream().indexOf("_") > 0) { | |
| 434 | - String[] streamArray = param.getStream().split("_"); | |
| 435 | - if (streamArray.length == 2) { | |
| 436 | - String deviceId = streamArray[0]; | |
| 437 | - String channelId = streamArray[1]; | |
| 438 | - Device device = deviceService.getDevice(deviceId); | |
| 439 | - if (device != null) { | |
| 440 | - if (param.isRegist()) { | |
| 441 | - if (audioBroadcastManager.exit(deviceId, channelId)) { | |
| 442 | - playService.stopAudioBroadcast(deviceId, channelId); | |
| 443 | - } | |
| 444 | - // 开启语音对讲通道 | |
| 445 | - playService.talkCmd(device, channelId, mediaInfo, param.getStream(), (msg) -> { | |
| 446 | - logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); | |
| 447 | - }); | |
| 448 | - } else { | |
| 449 | - // 流注销 | |
| 450 | - playService.stopTalk(device, channelId, param.isRegist()); | |
| 451 | - } | |
| 452 | - } else { | |
| 453 | - logger.info("[语音对讲] 未找到设备:{}", deviceId); | |
| 454 | - } | |
| 455 | - } | |
| 456 | - } | |
| 457 | - | |
| 458 | - } else { | |
| 459 | - if (!"rtp".equals(param.getApp())) { | |
| 460 | - String type = OriginType.values()[param.getOriginType()].getType(); | |
| 461 | - if (param.isRegist()) { | |
| 462 | - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo( | |
| 463 | - param.getApp(), param.getStream()); | |
| 464 | - String callId = null; | |
| 465 | - if (streamAuthorityInfo != null) { | |
| 466 | - callId = streamAuthorityInfo.getCallId(); | |
| 467 | - } | |
| 468 | - StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo, | |
| 469 | - param.getApp(), param.getStream(), tracks, callId); | |
| 470 | - param.setStreamInfo(new StreamContent(streamInfoByAppAndStream)); | |
| 471 | - redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param); | |
| 472 | - if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal() | |
| 473 | - || param.getOriginType() == OriginType.RTMP_PUSH.ordinal() | |
| 474 | - || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { | |
| 475 | - param.setSeverId(userSetting.getServerId()); | |
| 476 | - zlmMediaListManager.addPush(param); | |
| 477 | - | |
| 478 | - // 冗余数据,自己系统中自用 | |
| 479 | - redisCatchStorage.addPushListItem(param.getApp(), param.getStream(), param); | |
| 480 | - } | |
| 481 | - } else { | |
| 482 | - // 兼容流注销时类型从redis记录获取 | |
| 483 | - OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo( | |
| 484 | - param.getApp(), param.getStream(), param.getMediaServerId()); | |
| 485 | - if (onStreamChangedHookParam != null) { | |
| 486 | - type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType(); | |
| 487 | - redisCatchStorage.removeStream(mediaInfo.getId(), type, param.getApp(), param.getStream()); | |
| 488 | - if ("PUSH".equalsIgnoreCase(type)) { | |
| 489 | - // 冗余数据,自己系统中自用 | |
| 490 | - redisCatchStorage.removePushListItem(param.getApp(), param.getStream(), param.getMediaServerId()); | |
| 491 | - } | |
| 492 | - } | |
| 493 | - GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); | |
| 494 | - if (gbStream != null) { | |
| 495 | -// eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF); | |
| 496 | - } | |
| 497 | - zlmMediaListManager.removeMedia(param.getApp(), param.getStream()); | |
| 498 | - } | |
| 499 | - GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); | |
| 500 | - if (gbStream != null) { | |
| 501 | - if (userSetting.isUsePushingAsStatus()) { | |
| 502 | - eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist() ? CatalogEvent.ON : CatalogEvent.OFF); | |
| 503 | - } | |
| 504 | - } | |
| 505 | - if (type != null) { | |
| 506 | - // 发送流变化redis消息 | |
| 507 | - JSONObject jsonObject = new JSONObject(); | |
| 508 | - jsonObject.put("serverId", userSetting.getServerId()); | |
| 509 | - jsonObject.put("app", param.getApp()); | |
| 510 | - jsonObject.put("stream", param.getStream()); | |
| 511 | - jsonObject.put("register", param.isRegist()); | |
| 512 | - jsonObject.put("mediaServerId", param.getMediaServerId()); | |
| 513 | - redisCatchStorage.sendStreamChangeMsg(type, jsonObject); | |
| 514 | - } | |
| 515 | - } | |
| 516 | - } | |
| 517 | - if (!param.isRegist()) { | |
| 518 | - List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); | |
| 519 | - if (!sendRtpItems.isEmpty()) { | |
| 520 | - for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 521 | - if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) { | |
| 522 | - String platformId = sendRtpItem.getPlatformId(); | |
| 523 | - ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); | |
| 524 | - Device device = deviceService.getDevice(platformId); | |
| 525 | - | |
| 526 | - try { | |
| 527 | - if (platform != null) { | |
| 528 | - commanderFroPlatform.streamByeCmd(platform, sendRtpItem); | |
| 529 | - redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), | |
| 530 | - sendRtpItem.getCallId(), sendRtpItem.getStream()); | |
| 531 | - } else { | |
| 532 | - cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId()); | |
| 533 | - if (sendRtpItem.getPlayType().equals(InviteStreamType.BROADCAST) | |
| 534 | - || sendRtpItem.getPlayType().equals(InviteStreamType.TALK)) { | |
| 535 | - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 536 | - if (audioBroadcastCatch != null) { | |
| 537 | - // 来自上级平台的停止对讲 | |
| 538 | - logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 539 | - audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 540 | - } | |
| 541 | - } | |
| 542 | - } | |
| 543 | - } catch (SipException | InvalidArgumentException | ParseException | | |
| 544 | - SsrcTransactionNotFoundException e) { | |
| 545 | - logger.error("[命令发送失败] 发送BYE: {}", e.getMessage()); | |
| 546 | - } | |
| 547 | - } | |
| 548 | - } | |
| 549 | - } | |
| 550 | - } | |
| 551 | - } | |
| 552 | - }); | |
| 553 | - return HookResult.SUCCESS(); | |
| 554 | - } | |
| 555 | - | |
| 556 | - /** | |
| 557 | - * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 | |
| 558 | - */ | |
| 559 | - @ResponseBody | |
| 560 | - @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") | |
| 561 | - public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) { | |
| 562 | - | |
| 563 | - logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), | |
| 564 | - param.getApp(), param.getStream()); | |
| 565 | - JSONObject ret = new JSONObject(); | |
| 566 | - ret.put("code", 0); | |
| 567 | - stremProxyService1078.sendIORequestStop(param.getStream()); | |
| 568 | - // 国标类型的流 | |
| 569 | - if ("rtp".equals(param.getApp())) { | |
| 570 | - ret.put("close", userSetting.getStreamOnDemand()); | |
| 571 | - String stream = param.getStream(); | |
| 572 | - Object port = redisTemplate.opsForValue().get("tag:history:port:" + stream); | |
| 573 | - Object httpPort = redisTemplate.opsForValue().get("tag:history:httpPort:" + stream); | |
| 574 | - Object time = redisTemplate.opsForValue().get("tag:history:httpPort:time:" + stream); | |
| 575 | - | |
| 576 | - if (ObjectUtils.isEmpty(port)) { | |
| 577 | - port = -1; | |
| 578 | - } | |
| 579 | - | |
| 580 | - if (ObjectUtils.isEmpty(httpPort)) { | |
| 581 | - httpPort = -1; | |
| 582 | - } | |
| 583 | - | |
| 584 | - if (Objects.isNull(time) || Objects.equals(0L, time)) { | |
| 585 | - Map<String, Object> resl = jt1078OfCarController.sendIORequestStop(StringUtils.substringBefore(stream, "-"), StringUtils.substringAfter(stream, "-"), stream, (Integer) port, (Integer) httpPort); | |
| 586 | - if (!StringUtils.equals(String.valueOf(resl.get("code")), "1")) { | |
| 587 | - logger.info("停流失败,稍后再试:{},{},{}", stream, port, httpPort); | |
| 588 | - return ret; | |
| 589 | - } | |
| 590 | - } | |
| 591 | - | |
| 592 | - // 国标流, 点播/录像回放/录像下载 | |
| 593 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | |
| 594 | - // 点播 | |
| 595 | - if (inviteInfo != null) { | |
| 596 | - // 录像下载 | |
| 597 | - if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { | |
| 598 | - ret.put("close", false); | |
| 599 | - return ret; | |
| 600 | - } | |
| 601 | - // 收到无人观看说明流也没有在往上级推送 | |
| 602 | - if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) { | |
| 603 | - List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChannelId( | |
| 604 | - inviteInfo.getChannelId()); | |
| 605 | - if (!sendRtpItems.isEmpty()) { | |
| 606 | - for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 607 | - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 608 | - try { | |
| 609 | - commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); | |
| 610 | - } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 611 | - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 612 | - } | |
| 613 | - redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), | |
| 614 | - sendRtpItem.getCallId(), sendRtpItem.getStream()); | |
| 615 | - if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) { | |
| 616 | - MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 617 | - sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), | |
| 618 | - sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); | |
| 619 | - messageForPushChannel.setPlatFormIndex(parentPlatform.getId()); | |
| 620 | - redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); | |
| 621 | - } | |
| 622 | - } | |
| 623 | - } | |
| 624 | - } | |
| 625 | - Device device = deviceService.getDevice(inviteInfo.getDeviceId()); | |
| 626 | - if (device != null) { | |
| 627 | - try { | |
| 628 | - // 多查询一次防止已经被处理了 | |
| 629 | - InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(), | |
| 630 | - inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); | |
| 631 | - if (info != null) { | |
| 632 | - cmder.streamByeCmd(device, inviteInfo.getChannelId(), | |
| 633 | - inviteInfo.getStream(), null); | |
| 634 | - } else { | |
| 635 | - logger.info("[无人观看] 未找到设备的点播信息: {}, 流:{}", inviteInfo.getDeviceId(), param.getStream()); | |
| 636 | - } | |
| 637 | - } catch (InvalidArgumentException | ParseException | SipException | | |
| 638 | - SsrcTransactionNotFoundException e) { | |
| 639 | - logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); | |
| 640 | - } | |
| 641 | - } else { | |
| 642 | - logger.info("[无人观看] 未找到设备: {},流:{}", inviteInfo.getDeviceId(), param.getStream()); | |
| 643 | - } | |
| 644 | - | |
| 645 | - inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), | |
| 646 | - inviteInfo.getChannelId(), inviteInfo.getStream()); | |
| 647 | - storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); | |
| 648 | - return ret; | |
| 649 | - } | |
| 650 | - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null); | |
| 651 | - if (sendRtpItem != null && "talk".equals(sendRtpItem.getApp())) { | |
| 652 | - ret.put("close", false); | |
| 653 | - return ret; | |
| 654 | - } | |
| 655 | - } else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) { | |
| 656 | - ret.put("close", false); | |
| 657 | - } else { | |
| 658 | - // 非国标流 推流/拉流代理 | |
| 659 | - // 拉流代理 | |
| 660 | - StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | |
| 661 | - if (streamProxyItem != null) { | |
| 662 | - if (streamProxyItem.isEnableRemoveNoneReader()) { | |
| 663 | - // 无人观看自动移除 | |
| 664 | - ret.put("close", true); | |
| 665 | - streamProxyService.del(param.getApp(), param.getStream()); | |
| 666 | - String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrcUrl(); | |
| 667 | - logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url); | |
| 668 | - } else if (streamProxyItem.isEnableDisableNoneReader()) { | |
| 669 | - // 无人观看停用 | |
| 670 | - ret.put("close", true); | |
| 671 | - // 修改数据 | |
| 672 | - streamProxyService.stop(param.getApp(), param.getStream()); | |
| 673 | - } else { | |
| 674 | - // 无人观看不做处理 | |
| 675 | - ret.put("close", false); | |
| 676 | - } | |
| 677 | - return ret; | |
| 678 | - } | |
| 679 | - | |
| 680 | - | |
| 681 | - // TODO 推流具有主动性,暂时不做处理 | |
| 682 | -// StreamPushItem streamPushItem = streamPushService.getPush(app, streamId); | |
| 683 | -// if (streamPushItem != null) { | |
| 684 | -// // TODO 发送停止 | |
| 685 | -// } | |
| 686 | - //推流需要更新缓存数据以防止端口占用 | |
| 687 | - logger.info("无人观看流 ---> {}", JSONObject.toJSONString(param)); | |
| 688 | - Object object = redisTemplate.opsForValue().get("patrol:stream:" + param.getStream()); | |
| 689 | - if (object == null) { | |
| 690 | - String[] split = param.getStream().split("_"); | |
| 691 | - if (split != null && split.length == 2) { | |
| 692 | - if (split[1].equals("voice")){ | |
| 693 | - intercomService.stopIntercom(split[0]); | |
| 694 | - logger.info("{} 历史语音断流成功", split[0]); | |
| 695 | - }else { | |
| 696 | - try { | |
| 697 | - jt1078OfCarController.clearMap(split[1],split[0]); | |
| 698 | - } catch (ServiceException e) { | |
| 699 | - throw new RuntimeException(e); | |
| 700 | - } | |
| 701 | - } | |
| 702 | - } | |
| 703 | - } | |
| 704 | - } | |
| 705 | - return ret; | |
| 706 | - } | |
| 707 | - | |
| 708 | - /** | |
| 709 | - * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 | |
| 710 | - */ | |
| 711 | - @ResponseBody | |
| 712 | - @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") | |
| 713 | - public DeferredResult<HookResult> onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) { | |
| 714 | - logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 715 | - | |
| 716 | - DeferredResult<HookResult> defaultResult = new DeferredResult<>(); | |
| 717 | - | |
| 718 | - MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | |
| 719 | - if (!userSetting.isAutoApplyPlay() || mediaInfo == null) { | |
| 720 | - defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | |
| 721 | - return defaultResult; | |
| 722 | - } | |
| 723 | - if ("rtp".equals(param.getApp())) { | |
| 724 | - String[] s = param.getStream().split("_"); | |
| 725 | - if ((s.length != 2 && s.length != 4)) { | |
| 726 | - defaultResult.setResult(HookResult.SUCCESS()); | |
| 727 | - return defaultResult; | |
| 728 | - } | |
| 729 | - String deviceId = s[0]; | |
| 730 | - String channelId = s[1]; | |
| 731 | - Device device = redisCatchStorage.getDevice(deviceId); | |
| 732 | - if (device == null || !device.isOnLine()) { | |
| 733 | - defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | |
| 734 | - return defaultResult; | |
| 735 | - } | |
| 736 | - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); | |
| 737 | - if (deviceChannel == null) { | |
| 738 | - defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | |
| 739 | - return defaultResult; | |
| 740 | - } | |
| 741 | - if (s.length == 2) { | |
| 742 | - logger.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 743 | - | |
| 744 | - RequestMessage msg = new RequestMessage(); | |
| 745 | - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; | |
| 746 | - boolean exist = resultHolder.exist(key, null); | |
| 747 | - msg.setKey(key); | |
| 748 | - String uuid = UUID.randomUUID().toString(); | |
| 749 | - msg.setId(uuid); | |
| 750 | - DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 751 | - | |
| 752 | - result.onTimeout(() -> { | |
| 753 | - logger.info("[ZLM HOOK] 预览流自动点播, 等待超时"); | |
| 754 | - msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 755 | - resultHolder.invokeAllResult(msg); | |
| 756 | - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | |
| 757 | - storager.stopPlay(deviceId, channelId); | |
| 758 | - }); | |
| 759 | - | |
| 760 | - resultHolder.put(key, uuid, result); | |
| 761 | - | |
| 762 | - if (!exist) { | |
| 763 | - playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> { | |
| 764 | - msg.setData(new HookResult(code, message)); | |
| 765 | - resultHolder.invokeResult(msg); | |
| 766 | - }); | |
| 767 | - } | |
| 768 | - return result; | |
| 769 | - } else if (s.length == 4) { | |
| 770 | - // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间 | |
| 771 | - String startTimeStr = s[2]; | |
| 772 | - String endTimeStr = s[3]; | |
| 773 | - if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) { | |
| 774 | - defaultResult.setResult(HookResult.SUCCESS()); | |
| 775 | - return defaultResult; | |
| 776 | - } | |
| 777 | - String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr); | |
| 778 | - String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr); | |
| 779 | - logger.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}", | |
| 780 | - param.getMediaServerId(), param.getSchema(), | |
| 781 | - param.getApp(), param.getStream(), | |
| 782 | - startTime, endTime | |
| 783 | - ); | |
| 784 | - RequestMessage msg = new RequestMessage(); | |
| 785 | - String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId; | |
| 786 | - boolean exist = resultHolder.exist(key, null); | |
| 787 | - msg.setKey(key); | |
| 788 | - String uuid = UUID.randomUUID().toString(); | |
| 789 | - msg.setId(uuid); | |
| 790 | - DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 791 | - | |
| 792 | - result.onTimeout(() -> { | |
| 793 | - logger.info("[ZLM HOOK] 回放流自动点播, 等待超时"); | |
| 794 | - // 释放rtpserver | |
| 795 | - msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 796 | - resultHolder.invokeResult(msg); | |
| 797 | - }); | |
| 798 | - | |
| 799 | - resultHolder.put(key, uuid, result); | |
| 800 | - | |
| 801 | - if (!exist) { | |
| 802 | - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null, | |
| 803 | - device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam()); | |
| 804 | - playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> { | |
| 805 | - msg.setData(new HookResult(code, message)); | |
| 806 | - resultHolder.invokeResult(msg); | |
| 807 | - }); | |
| 808 | - } | |
| 809 | - return result; | |
| 810 | - } else { | |
| 811 | - defaultResult.setResult(HookResult.SUCCESS()); | |
| 812 | - return defaultResult; | |
| 813 | - } | |
| 814 | - | |
| 815 | - } else { | |
| 816 | - // 拉流代理 | |
| 817 | - StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | |
| 818 | - if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) { | |
| 819 | - streamProxyService.start(param.getApp(), param.getStream()); | |
| 820 | - } | |
| 821 | - DeferredResult<HookResult> result = new DeferredResult<>(); | |
| 822 | - result.setResult(HookResult.SUCCESS()); | |
| 823 | - return result; | |
| 824 | - } | |
| 825 | - } | |
| 826 | - | |
| 827 | - /** | |
| 828 | - * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 | |
| 829 | - */ | |
| 830 | - @ResponseBody | |
| 831 | - @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") | |
| 832 | - public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) { | |
| 833 | - | |
| 834 | - jsonObject.put("ip", request.getRemoteAddr()); | |
| 835 | - ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject); | |
| 836 | - zlmServerConfig.setIp(request.getRemoteAddr()); | |
| 837 | - logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId()); | |
| 838 | - taskExecutor.execute(() -> { | |
| 839 | - List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started); | |
| 840 | - if (subscribes != null && !subscribes.isEmpty()) { | |
| 841 | - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 842 | - subscribe.response(null, zlmServerConfig); | |
| 843 | - } | |
| 844 | - } | |
| 845 | - mediaServerService.zlmServerOnline(zlmServerConfig); | |
| 846 | - }); | |
| 847 | - | |
| 848 | - return HookResult.SUCCESS(); | |
| 849 | - } | |
| 850 | - | |
| 851 | - /** | |
| 852 | - * 发送rtp(startSendRtp)被动关闭时回调 | |
| 853 | - */ | |
| 854 | - @ResponseBody | |
| 855 | - @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") | |
| 856 | - public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) { | |
| 857 | - | |
| 858 | - logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); | |
| 859 | - | |
| 860 | - // 查找对应的上级推流,发送停止 | |
| 861 | - if (!"rtp".equals(param.getApp())) { | |
| 862 | - return HookResult.SUCCESS(); | |
| 863 | - } | |
| 864 | - taskExecutor.execute(() -> { | |
| 865 | - List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); | |
| 866 | - if (sendRtpItems.size() > 0) { | |
| 867 | - for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 868 | - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 869 | - ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 870 | - try { | |
| 871 | - commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); | |
| 872 | - } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 873 | - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 874 | - } | |
| 875 | - redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), | |
| 876 | - sendRtpItem.getCallId(), sendRtpItem.getStream()); | |
| 877 | - } | |
| 878 | - } | |
| 879 | - }); | |
| 880 | - | |
| 881 | - return HookResult.SUCCESS(); | |
| 882 | - } | |
| 883 | - | |
| 884 | - /** | |
| 885 | - * rtpServer收流超时 | |
| 886 | - */ | |
| 887 | - @ResponseBody | |
| 888 | - @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") | |
| 889 | - public HookResult onRtpServerTimeout(@RequestBody OnRtpServerTimeoutHookParam | |
| 890 | - param) { | |
| 891 | - logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); | |
| 892 | - | |
| 893 | - taskExecutor.execute(() -> { | |
| 894 | - List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout); | |
| 895 | - if (subscribes != null && !subscribes.isEmpty()) { | |
| 896 | - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 897 | - subscribe.response(null, param); | |
| 898 | - } | |
| 899 | - } | |
| 900 | - }); | |
| 901 | - | |
| 902 | - return HookResult.SUCCESS(); | |
| 903 | - } | |
| 904 | - | |
| 905 | - /** | |
| 906 | - * 录像完成事件 | |
| 907 | - */ | |
| 908 | - @ResponseBody | |
| 909 | - @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") | |
| 910 | - public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4HookParam param) { | |
| 911 | - logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path()); | |
| 912 | - | |
| 913 | - taskExecutor.execute(() -> { | |
| 914 | - List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_record_mp4); | |
| 915 | - if (subscribes != null && !subscribes.isEmpty()) { | |
| 916 | - for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 917 | - subscribe.response(null, param); | |
| 918 | - } | |
| 919 | - } | |
| 920 | - cloudRecordService.addRecord(param); | |
| 921 | - | |
| 922 | - }); | |
| 923 | - | |
| 924 | - return HookResult.SUCCESS(); | |
| 925 | - } | |
| 926 | - | |
| 927 | - private Map<String, String> urlParamToMap(String params) { | |
| 928 | - HashMap<String, String> map = new HashMap<>(); | |
| 929 | - if (ObjectUtils.isEmpty(params)) { | |
| 930 | - return map; | |
| 931 | - } | |
| 932 | - String[] paramsArray = params.split("&"); | |
| 933 | - if (paramsArray.length == 0) { | |
| 934 | - return map; | |
| 935 | - } | |
| 936 | - for (String param : paramsArray) { | |
| 937 | - String[] paramArray = param.split("="); | |
| 938 | - if (paramArray.length == 2) { | |
| 939 | - map.put(paramArray[0], paramArray[1]); | |
| 940 | - } | |
| 941 | - } | |
| 942 | - return map; | |
| 943 | - } | |
| 944 | -} | |
| 1 | +package com.genersoft.iot.vmp.media.zlm; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson2.JSON; | |
| 4 | +import com.alibaba.fastjson2.JSONObject; | |
| 5 | +import com.genersoft.iot.vmp.common.InviteInfo; | |
| 6 | +import com.genersoft.iot.vmp.common.InviteSessionType; | |
| 7 | +import com.genersoft.iot.vmp.common.StreamInfo; | |
| 8 | +import com.genersoft.iot.vmp.common.VideoManagerConstants; | |
| 9 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 10 | +import com.genersoft.iot.vmp.conf.exception.ServiceException; | |
| 11 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 12 | +import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 13 | +import com.genersoft.iot.vmp.gb28181.event.EventPublisher; | |
| 14 | +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | |
| 15 | +import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; | |
| 16 | +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; | |
| 17 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 18 | +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | |
| 19 | +import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | |
| 20 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | |
| 21 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | |
| 22 | +import com.genersoft.iot.vmp.media.zlm.dto.HookType; | |
| 23 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 24 | +import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; | |
| 25 | +import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; | |
| 26 | +import com.genersoft.iot.vmp.media.zlm.dto.hook.*; | |
| 27 | +import com.genersoft.iot.vmp.service.*; | |
| 28 | +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; | |
| 29 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 30 | +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 31 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 32 | +import com.genersoft.iot.vmp.utils.DateUtil; | |
| 33 | +import com.genersoft.iot.vmp.vmanager.bean.*; | |
| 34 | +import com.genersoft.iot.vmp.vmanager.jt1078.platform.Jt1078OfCarController; | |
| 35 | +import com.genersoft.iot.vmp.vmanager.jt1078.platform.service.IntercomService; | |
| 36 | +import org.apache.commons.lang3.StringUtils; | |
| 37 | +import org.slf4j.Logger; | |
| 38 | +import org.slf4j.LoggerFactory; | |
| 39 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 40 | +import org.springframework.beans.factory.annotation.Qualifier; | |
| 41 | +import org.springframework.data.redis.core.RedisTemplate; | |
| 42 | +import org.springframework.scheduling.annotation.Scheduled; | |
| 43 | +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | |
| 44 | +import org.springframework.util.ObjectUtils; | |
| 45 | +import org.springframework.web.bind.annotation.*; | |
| 46 | +import org.springframework.web.context.request.async.DeferredResult; | |
| 47 | + | |
| 48 | +import javax.annotation.Resource; | |
| 49 | +import javax.servlet.http.HttpServletRequest; | |
| 50 | +import javax.sip.InvalidArgumentException; | |
| 51 | +import javax.sip.SipException; | |
| 52 | +import java.text.ParseException; | |
| 53 | +import java.util.*; | |
| 54 | +import java.util.concurrent.TimeUnit; | |
| 55 | + | |
| 56 | +/** | |
| 57 | + * @description:针对 ZLMediaServer的hook事件监听 | |
| 58 | + * @author: swwheihei | |
| 59 | + * @date: 2020年5月8日 上午10:46:48 | |
| 60 | + */ | |
| 61 | +@RestController | |
| 62 | +@RequestMapping("/index/hook") | |
| 63 | +public class ZLMHttpHookListener { | |
| 64 | + | |
| 65 | + private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class); | |
| 66 | + | |
| 67 | + @Autowired | |
| 68 | + private SIPCommander cmder; | |
| 69 | + | |
| 70 | + @Autowired | |
| 71 | + private ISIPCommanderForPlatform commanderFroPlatform; | |
| 72 | + | |
| 73 | + @Autowired | |
| 74 | + private AudioBroadcastManager audioBroadcastManager; | |
| 75 | + | |
| 76 | + @Autowired | |
| 77 | + private IPlayService playService; | |
| 78 | + | |
| 79 | + @Autowired | |
| 80 | + private IVideoManagerStorage storager; | |
| 81 | + | |
| 82 | + @Autowired | |
| 83 | + private IRedisCatchStorage redisCatchStorage; | |
| 84 | + | |
| 85 | + @Autowired | |
| 86 | + private IInviteStreamService inviteStreamService; | |
| 87 | + | |
| 88 | + @Autowired | |
| 89 | + private IDeviceService deviceService; | |
| 90 | + | |
| 91 | + @Autowired | |
| 92 | + private IMediaServerService mediaServerService; | |
| 93 | + | |
| 94 | + @Autowired | |
| 95 | + private IStreamProxyService streamProxyService; | |
| 96 | + | |
| 97 | + @Autowired | |
| 98 | + private DeferredResultHolder resultHolder; | |
| 99 | + | |
| 100 | + @Autowired | |
| 101 | + private IMediaService mediaService; | |
| 102 | + | |
| 103 | + @Autowired | |
| 104 | + private EventPublisher eventPublisher; | |
| 105 | + | |
| 106 | + @Autowired | |
| 107 | + private ZLMMediaListManager zlmMediaListManager; | |
| 108 | + | |
| 109 | + @Autowired | |
| 110 | + private ZlmHttpHookSubscribe subscribe; | |
| 111 | + | |
| 112 | + @Autowired | |
| 113 | + private UserSetting userSetting; | |
| 114 | + | |
| 115 | + @Autowired | |
| 116 | + private IUserService userService; | |
| 117 | + | |
| 118 | + @Autowired | |
| 119 | + private ICloudRecordService cloudRecordService; | |
| 120 | + | |
| 121 | + @Autowired | |
| 122 | + private VideoStreamSessionManager sessionManager; | |
| 123 | + | |
| 124 | + @Autowired | |
| 125 | + private SSRCFactory ssrcFactory; | |
| 126 | + | |
| 127 | + @Qualifier("taskExecutor") | |
| 128 | + @Autowired | |
| 129 | + private ThreadPoolTaskExecutor taskExecutor; | |
| 130 | + | |
| 131 | + @Autowired | |
| 132 | + private RedisTemplate<Object, Object> redisTemplate; | |
| 133 | + | |
| 134 | + @Autowired | |
| 135 | + private Jt1078OfCarController jt1078OfCarController; | |
| 136 | + | |
| 137 | + @Autowired | |
| 138 | + private StremProxyService1078 stremProxyService1078; | |
| 139 | + @Resource | |
| 140 | + private IntercomService intercomService; | |
| 141 | + | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 | |
| 145 | + */ | |
| 146 | + @ResponseBody | |
| 147 | + | |
| 148 | + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") | |
| 149 | + public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) { | |
| 150 | + | |
| 151 | + | |
| 152 | + taskExecutor.execute(() -> { | |
| 153 | + List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive); | |
| 154 | + if (subscribes != null && !subscribes.isEmpty()) { | |
| 155 | + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 156 | + subscribe.response(null, param); | |
| 157 | + } | |
| 158 | + } | |
| 159 | + }); | |
| 160 | + mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData()); | |
| 161 | + | |
| 162 | + return HookResult.SUCCESS(); | |
| 163 | + } | |
| 164 | + | |
| 165 | + /** | |
| 166 | + * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 | |
| 167 | + */ | |
| 168 | + @ResponseBody | |
| 169 | + | |
| 170 | + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") | |
| 171 | + public HookResult onPlay(@RequestBody OnPlayHookParam param) { | |
| 172 | + if (logger.isDebugEnabled()) { | |
| 173 | + logger.debug("[ZLM HOOK] 播放鉴权:{}->{}", param.getMediaServerId(), param); | |
| 174 | + } | |
| 175 | + String mediaServerId = param.getMediaServerId(); | |
| 176 | + | |
| 177 | + taskExecutor.execute(() -> { | |
| 178 | + JSONObject json = (JSONObject) JSON.toJSON(param); | |
| 179 | + ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json); | |
| 180 | + if (subscribe != null) { | |
| 181 | + MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); | |
| 182 | + if (mediaInfo != null) { | |
| 183 | + subscribe.response(mediaInfo, param); | |
| 184 | + } | |
| 185 | + } | |
| 186 | + }); | |
| 187 | + if (!"rtp".equals(param.getApp())) { | |
| 188 | + Map<String, String> paramMap = urlParamToMap(param.getParams()); | |
| 189 | + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); | |
| 190 | + if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) { | |
| 191 | + return new HookResult(401, "Unauthorized"); | |
| 192 | + } | |
| 193 | + } | |
| 194 | + return HookResult.SUCCESS(); | |
| 195 | + } | |
| 196 | + | |
| 197 | + /** | |
| 198 | + * rtsp/rtmp/rtp推流鉴权事件。 | |
| 199 | + */ | |
| 200 | + @ResponseBody | |
| 201 | + @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8") | |
| 202 | + public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) { | |
| 203 | + | |
| 204 | + JSONObject json = (JSONObject) JSON.toJSON(param); | |
| 205 | + | |
| 206 | + logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param); | |
| 207 | + | |
| 208 | + String mediaServerId = json.getString("mediaServerId"); | |
| 209 | + MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); | |
| 210 | + if (mediaInfo == null) { | |
| 211 | + return new HookResultForOnPublish(200, "success"); | |
| 212 | + } | |
| 213 | + // 推流鉴权的处理 | |
| 214 | + if (!"rtp".equals(param.getApp()) && !"schedule".equals(param.getApp())) { | |
| 215 | + StreamProxyItem stream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | |
| 216 | + if (stream != null) { | |
| 217 | + HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); | |
| 218 | + result.setEnable_audio(stream.isEnableAudio()); | |
| 219 | + result.setEnable_mp4(stream.isEnableMp4()); | |
| 220 | + return result; | |
| 221 | + } | |
| 222 | + if (userSetting.getPushAuthority()) { | |
| 223 | + // 推流鉴权 | |
| 224 | + if (param.getParams() == null) { | |
| 225 | + logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)"); | |
| 226 | + return new HookResultForOnPublish(401, "Unauthorized"); | |
| 227 | + } | |
| 228 | + Map<String, String> paramMap = urlParamToMap(param.getParams()); | |
| 229 | + String sign = paramMap.get("sign"); | |
| 230 | + if (sign == null) { | |
| 231 | + logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)"); | |
| 232 | + return new HookResultForOnPublish(401, "Unauthorized"); | |
| 233 | + } | |
| 234 | + // 鉴权配置 | |
| 235 | + // 推流自定义播放鉴权码 | |
| 236 | + // 鉴权配置 | |
| 237 | + boolean hasAuthority = userService.checkPushAuthority(null, sign); | |
| 238 | + if (!hasAuthority) { | |
| 239 | + logger.info("推流鉴权失败: sign 无权限: sign={}", sign); | |
| 240 | + return new HookResultForOnPublish(401, "Unauthorized"); | |
| 241 | + } | |
| 242 | + StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | |
| 243 | + streamAuthorityInfo.setCallId(sign); | |
| 244 | + streamAuthorityInfo.setSign(sign); | |
| 245 | + // 鉴权通过 | |
| 246 | + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); | |
| 247 | + } | |
| 248 | + } else { | |
| 249 | + zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId()); | |
| 250 | + } | |
| 251 | + | |
| 252 | + | |
| 253 | + HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); | |
| 254 | + result.setEnable_audio(true); | |
| 255 | + taskExecutor.execute(() -> { | |
| 256 | + ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json); | |
| 257 | + if (subscribe != null) { | |
| 258 | + subscribe.response(mediaInfo, param); | |
| 259 | + } | |
| 260 | + }); | |
| 261 | + | |
| 262 | + // 是否录像 | |
| 263 | + if ("rtp".equals(param.getApp())) { | |
| 264 | + result.setEnable_mp4(userSetting.getRecordSip()); | |
| 265 | + } else { | |
| 266 | + result.setEnable_mp4(userSetting.isRecordPushLive()); | |
| 267 | + } | |
| 268 | + // 国标流 | |
| 269 | + if ("rtp".equals(param.getApp())) { | |
| 270 | + | |
| 271 | + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | |
| 272 | + | |
| 273 | + // 单端口模式下修改流 ID | |
| 274 | + if (!mediaInfo.isRtpEnable() && inviteInfo == null) { | |
| 275 | + String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16)); | |
| 276 | + inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc); | |
| 277 | + if (inviteInfo != null) { | |
| 278 | + result.setStream_replace(inviteInfo.getStream()); | |
| 279 | + logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream()); | |
| 280 | + } | |
| 281 | + } | |
| 282 | + | |
| 283 | + // 设置音频信息及录制信息 | |
| 284 | + List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); | |
| 285 | + if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { | |
| 286 | + | |
| 287 | + // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用 | |
| 288 | + StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | |
| 289 | + streamAuthorityInfo.setApp(param.getApp()); | |
| 290 | + streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream()); | |
| 291 | + streamAuthorityInfo.setCallId(ssrcTransactionForAll.get(0).getSipTransactionInfo().getCallId()); | |
| 292 | + | |
| 293 | + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), ssrcTransactionForAll.get(0).getStream(), streamAuthorityInfo); | |
| 294 | + | |
| 295 | + String deviceId = ssrcTransactionForAll.get(0).getDeviceId(); | |
| 296 | + String channelId = ssrcTransactionForAll.get(0).getChannelId(); | |
| 297 | + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); | |
| 298 | + if (deviceChannel != null) { | |
| 299 | + result.setEnable_audio(deviceChannel.isHasAudio()); | |
| 300 | + } | |
| 301 | + // 如果是录像下载就设置视频间隔十秒 | |
| 302 | + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { | |
| 303 | + // 获取录像的总时长,然后设置为这个视频的时长 | |
| 304 | + InviteInfo inviteInfoForDownload = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, param.getStream()); | |
| 305 | + if (inviteInfoForDownload != null && inviteInfoForDownload.getStreamInfo() != null) { | |
| 306 | + String startTime = inviteInfoForDownload.getStreamInfo().getStartTime(); | |
| 307 | + String endTime = inviteInfoForDownload.getStreamInfo().getEndTime(); | |
| 308 | + long difference = DateUtil.getDifference(startTime, endTime) / 1000; | |
| 309 | + result.setMp4_max_second((int) difference); | |
| 310 | + result.setEnable_mp4(true); | |
| 311 | + // 设置为2保证得到的mp4的时长是正常的 | |
| 312 | + result.setModify_stamp(2); | |
| 313 | + } | |
| 314 | + } | |
| 315 | + // 如果是talk对讲,则默认获取声音 | |
| 316 | + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) { | |
| 317 | + result.setEnable_audio(true); | |
| 318 | + } | |
| 319 | + } | |
| 320 | + } else if (param.getApp().equals("broadcast")) { | |
| 321 | + result.setEnable_audio(true); | |
| 322 | + } else if (param.getApp().equals("talk")) { | |
| 323 | + result.setEnable_audio(true); | |
| 324 | + } | |
| 325 | + if (param.getApp().equalsIgnoreCase("rtp")) { | |
| 326 | + String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream(); | |
| 327 | + OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey); | |
| 328 | + | |
| 329 | + String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + param.getStream(); | |
| 330 | + OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo) redisTemplate.opsForValue().get(receiveKeyForPS); | |
| 331 | + if (otherRtpSendInfo != null || otherPsSendInfo != null) { | |
| 332 | + result.setEnable_mp4(true); | |
| 333 | + } | |
| 334 | + } | |
| 335 | + logger.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, result); | |
| 336 | + return result; | |
| 337 | + } | |
| 338 | + | |
| 339 | + | |
| 340 | + /** | |
| 341 | + * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 | |
| 342 | + */ | |
| 343 | + @ResponseBody | |
| 344 | + @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") | |
| 345 | + public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) { | |
| 346 | + | |
| 347 | + if (param.isRegist()) { | |
| 348 | + logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 349 | + } else { | |
| 350 | + logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 351 | + } | |
| 352 | + | |
| 353 | + JSONObject json = (JSONObject) JSON.toJSON(param); | |
| 354 | + taskExecutor.execute(() -> { | |
| 355 | + ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); | |
| 356 | + MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | |
| 357 | + if (mediaInfo == null) { | |
| 358 | + logger.info("[ZLM HOOK] 流变化未找到ZLM, {}", param.getMediaServerId()); | |
| 359 | + return; | |
| 360 | + } | |
| 361 | + if (subscribe != null) { | |
| 362 | + subscribe.response(mediaInfo, param); | |
| 363 | + } | |
| 364 | + | |
| 365 | + List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); | |
| 366 | + // TODO 重构此处逻辑 | |
| 367 | + if (param.isRegist()) { | |
| 368 | + // 处理流注册的鉴权信息, 流注销这里不再删除鉴权信息,下次来了新的鉴权信息会对就的进行覆盖 | |
| 369 | + if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() | |
| 370 | + || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() | |
| 371 | + || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { | |
| 372 | + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); | |
| 373 | + if (streamAuthorityInfo == null) { | |
| 374 | + streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | |
| 375 | + } else { | |
| 376 | + streamAuthorityInfo.setOriginType(param.getOriginType()); | |
| 377 | + streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr()); | |
| 378 | + } | |
| 379 | + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); | |
| 380 | + } | |
| 381 | + } | |
| 382 | + if ("rtsp".equals(param.getSchema())) { | |
| 383 | + logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream()); | |
| 384 | + if (param.isRegist()) { | |
| 385 | + mediaServerService.addCount(param.getMediaServerId()); | |
| 386 | + } else { | |
| 387 | + mediaServerService.removeCount(param.getMediaServerId()); | |
| 388 | + } | |
| 389 | + | |
| 390 | + int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream()); | |
| 391 | + if (updateStatusResult > 0) { | |
| 392 | + | |
| 393 | + } | |
| 394 | + | |
| 395 | + if ("rtp".equals(param.getApp()) && !param.isRegist()) { | |
| 396 | + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | |
| 397 | + if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) { | |
| 398 | + inviteStreamService.removeInviteInfo(inviteInfo); | |
| 399 | + storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); | |
| 400 | + } | |
| 401 | + } else if ("broadcast".equals(param.getApp())) { | |
| 402 | + // 语音对讲推流 stream需要满足格式deviceId_channelId | |
| 403 | + if (param.getStream().indexOf("_") > 0) { | |
| 404 | + String[] streamArray = param.getStream().split("_"); | |
| 405 | + if (streamArray.length == 2) { | |
| 406 | + String deviceId = streamArray[0]; | |
| 407 | + String channelId = streamArray[1]; | |
| 408 | + Device device = deviceService.getDevice(deviceId); | |
| 409 | + if (device != null) { | |
| 410 | + if (param.isRegist()) { | |
| 411 | + if (audioBroadcastManager.exit(deviceId, channelId)) { | |
| 412 | + playService.stopAudioBroadcast(deviceId, channelId); | |
| 413 | + } | |
| 414 | + // 开启语音对讲通道 | |
| 415 | + try { | |
| 416 | + playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg) -> { | |
| 417 | + logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); | |
| 418 | + }); | |
| 419 | + } catch (InvalidArgumentException | ParseException | SipException e) { | |
| 420 | + logger.error("[命令发送失败] 语音对讲: {}", e.getMessage()); | |
| 421 | + } | |
| 422 | + } else { | |
| 423 | + // 流注销 | |
| 424 | + playService.stopAudioBroadcast(deviceId, channelId); | |
| 425 | + } | |
| 426 | + } else { | |
| 427 | + logger.info("[语音对讲] 未找到设备:{}", deviceId); | |
| 428 | + } | |
| 429 | + } | |
| 430 | + } | |
| 431 | + } else if ("talk".equals(param.getApp())) { | |
| 432 | + // 语音对讲推流 stream需要满足格式deviceId_channelId | |
| 433 | + if (param.getStream().indexOf("_") > 0) { | |
| 434 | + String[] streamArray = param.getStream().split("_"); | |
| 435 | + if (streamArray.length == 2) { | |
| 436 | + String deviceId = streamArray[0]; | |
| 437 | + String channelId = streamArray[1]; | |
| 438 | + Device device = deviceService.getDevice(deviceId); | |
| 439 | + if (device != null) { | |
| 440 | + if (param.isRegist()) { | |
| 441 | + if (audioBroadcastManager.exit(deviceId, channelId)) { | |
| 442 | + playService.stopAudioBroadcast(deviceId, channelId); | |
| 443 | + } | |
| 444 | + // 开启语音对讲通道 | |
| 445 | + playService.talkCmd(device, channelId, mediaInfo, param.getStream(), (msg) -> { | |
| 446 | + logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); | |
| 447 | + }); | |
| 448 | + } else { | |
| 449 | + // 流注销 | |
| 450 | + playService.stopTalk(device, channelId, param.isRegist()); | |
| 451 | + } | |
| 452 | + } else { | |
| 453 | + logger.info("[语音对讲] 未找到设备:{}", deviceId); | |
| 454 | + } | |
| 455 | + } | |
| 456 | + } | |
| 457 | + | |
| 458 | + } else { | |
| 459 | + if (!"rtp".equals(param.getApp())) { | |
| 460 | + String type = OriginType.values()[param.getOriginType()].getType(); | |
| 461 | + if (param.isRegist()) { | |
| 462 | + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo( | |
| 463 | + param.getApp(), param.getStream()); | |
| 464 | + String callId = null; | |
| 465 | + if (streamAuthorityInfo != null) { | |
| 466 | + callId = streamAuthorityInfo.getCallId(); | |
| 467 | + } | |
| 468 | + StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo, | |
| 469 | + param.getApp(), param.getStream(), tracks, callId); | |
| 470 | + param.setStreamInfo(new StreamContent(streamInfoByAppAndStream)); | |
| 471 | + redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param); | |
| 472 | + if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal() | |
| 473 | + || param.getOriginType() == OriginType.RTMP_PUSH.ordinal() | |
| 474 | + || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { | |
| 475 | + param.setSeverId(userSetting.getServerId()); | |
| 476 | + zlmMediaListManager.addPush(param); | |
| 477 | + | |
| 478 | + // 冗余数据,自己系统中自用 | |
| 479 | + redisCatchStorage.addPushListItem(param.getApp(), param.getStream(), param); | |
| 480 | + } | |
| 481 | + } else { | |
| 482 | + // 兼容流注销时类型从redis记录获取 | |
| 483 | + OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo( | |
| 484 | + param.getApp(), param.getStream(), param.getMediaServerId()); | |
| 485 | + if (onStreamChangedHookParam != null) { | |
| 486 | + type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType(); | |
| 487 | + redisCatchStorage.removeStream(mediaInfo.getId(), type, param.getApp(), param.getStream()); | |
| 488 | + if ("PUSH".equalsIgnoreCase(type)) { | |
| 489 | + // 冗余数据,自己系统中自用 | |
| 490 | + redisCatchStorage.removePushListItem(param.getApp(), param.getStream(), param.getMediaServerId()); | |
| 491 | + } | |
| 492 | + } | |
| 493 | + GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); | |
| 494 | + if (gbStream != null) { | |
| 495 | +// eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF); | |
| 496 | + } | |
| 497 | + zlmMediaListManager.removeMedia(param.getApp(), param.getStream()); | |
| 498 | + } | |
| 499 | + GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); | |
| 500 | + if (gbStream != null) { | |
| 501 | + if (userSetting.isUsePushingAsStatus()) { | |
| 502 | + eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist() ? CatalogEvent.ON : CatalogEvent.OFF); | |
| 503 | + } | |
| 504 | + } | |
| 505 | + if (type != null) { | |
| 506 | + // 发送流变化redis消息 | |
| 507 | + JSONObject jsonObject = new JSONObject(); | |
| 508 | + jsonObject.put("serverId", userSetting.getServerId()); | |
| 509 | + jsonObject.put("app", param.getApp()); | |
| 510 | + jsonObject.put("stream", param.getStream()); | |
| 511 | + jsonObject.put("register", param.isRegist()); | |
| 512 | + jsonObject.put("mediaServerId", param.getMediaServerId()); | |
| 513 | + redisCatchStorage.sendStreamChangeMsg(type, jsonObject); | |
| 514 | + } | |
| 515 | + } | |
| 516 | + } | |
| 517 | + if (!param.isRegist()) { | |
| 518 | + List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); | |
| 519 | + if (!sendRtpItems.isEmpty()) { | |
| 520 | + for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 521 | + if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) { | |
| 522 | + String platformId = sendRtpItem.getPlatformId(); | |
| 523 | + ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); | |
| 524 | + Device device = deviceService.getDevice(platformId); | |
| 525 | + | |
| 526 | + try { | |
| 527 | + if (platform != null) { | |
| 528 | + commanderFroPlatform.streamByeCmd(platform, sendRtpItem); | |
| 529 | + redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), | |
| 530 | + sendRtpItem.getCallId(), sendRtpItem.getStream()); | |
| 531 | + } else { | |
| 532 | + cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId()); | |
| 533 | + if (sendRtpItem.getPlayType().equals(InviteStreamType.BROADCAST) | |
| 534 | + || sendRtpItem.getPlayType().equals(InviteStreamType.TALK)) { | |
| 535 | + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 536 | + if (audioBroadcastCatch != null) { | |
| 537 | + // 来自上级平台的停止对讲 | |
| 538 | + logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 539 | + audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 540 | + } | |
| 541 | + } | |
| 542 | + } | |
| 543 | + } catch (SipException | InvalidArgumentException | ParseException | | |
| 544 | + SsrcTransactionNotFoundException e) { | |
| 545 | + logger.error("[命令发送失败] 发送BYE: {}", e.getMessage()); | |
| 546 | + } | |
| 547 | + } | |
| 548 | + } | |
| 549 | + } | |
| 550 | + } | |
| 551 | + } | |
| 552 | + }); | |
| 553 | + return HookResult.SUCCESS(); | |
| 554 | + } | |
| 555 | + | |
| 556 | + /** | |
| 557 | + * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 | |
| 558 | + */ | |
| 559 | + @ResponseBody | |
| 560 | + @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") | |
| 561 | + public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) { | |
| 562 | + | |
| 563 | + logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), | |
| 564 | + param.getApp(), param.getStream()); | |
| 565 | + JSONObject ret = new JSONObject(); | |
| 566 | + ret.put("code", 0); | |
| 567 | + stremProxyService1078.sendIORequestStop(param.getStream()); | |
| 568 | + // 国标类型的流 | |
| 569 | + if ("rtp".equals(param.getApp())) { | |
| 570 | + ret.put("close", userSetting.getStreamOnDemand()); | |
| 571 | + String stream = param.getStream(); | |
| 572 | + Object port = redisTemplate.opsForValue().get("tag:history:port:" + stream); | |
| 573 | + Object httpPort = redisTemplate.opsForValue().get("tag:history:httpPort:" + stream); | |
| 574 | + Object time = redisTemplate.opsForValue().get("tag:history:httpPort:time:" + stream); | |
| 575 | + | |
| 576 | + if (ObjectUtils.isEmpty(port)) { | |
| 577 | + port = -1; | |
| 578 | + } | |
| 579 | + | |
| 580 | + if (ObjectUtils.isEmpty(httpPort)) { | |
| 581 | + httpPort = -1; | |
| 582 | + } | |
| 583 | + | |
| 584 | + if (Objects.isNull(time) || Objects.equals(0L, time)) { | |
| 585 | + Map<String, Object> resl = jt1078OfCarController.sendIORequestStop(StringUtils.substringBefore(stream, "-"), StringUtils.substringAfter(stream, "-"), stream, (Integer) port, (Integer) httpPort); | |
| 586 | + if (!StringUtils.equals(String.valueOf(resl.get("code")), "1")) { | |
| 587 | + logger.info("停流失败,稍后再试:{},{},{}", stream, port, httpPort); | |
| 588 | + return ret; | |
| 589 | + } | |
| 590 | + } | |
| 591 | + | |
| 592 | + // 国标流, 点播/录像回放/录像下载 | |
| 593 | + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | |
| 594 | + // 点播 | |
| 595 | + if (inviteInfo != null) { | |
| 596 | + // 录像下载 | |
| 597 | + if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { | |
| 598 | + ret.put("close", false); | |
| 599 | + return ret; | |
| 600 | + } | |
| 601 | + // 收到无人观看说明流也没有在往上级推送 | |
| 602 | + if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) { | |
| 603 | + List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChannelId( | |
| 604 | + inviteInfo.getChannelId()); | |
| 605 | + if (!sendRtpItems.isEmpty()) { | |
| 606 | + for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 607 | + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 608 | + try { | |
| 609 | + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); | |
| 610 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 611 | + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 612 | + } | |
| 613 | + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), | |
| 614 | + sendRtpItem.getCallId(), sendRtpItem.getStream()); | |
| 615 | + if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) { | |
| 616 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 617 | + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), | |
| 618 | + sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); | |
| 619 | + messageForPushChannel.setPlatFormIndex(parentPlatform.getId()); | |
| 620 | + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); | |
| 621 | + } | |
| 622 | + } | |
| 623 | + } | |
| 624 | + } | |
| 625 | + Device device = deviceService.getDevice(inviteInfo.getDeviceId()); | |
| 626 | + if (device != null) { | |
| 627 | + try { | |
| 628 | + // 多查询一次防止已经被处理了 | |
| 629 | + InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(), | |
| 630 | + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); | |
| 631 | + if (info != null) { | |
| 632 | + cmder.streamByeCmd(device, inviteInfo.getChannelId(), | |
| 633 | + inviteInfo.getStream(), null); | |
| 634 | + } else { | |
| 635 | + logger.info("[无人观看] 未找到设备的点播信息: {}, 流:{}", inviteInfo.getDeviceId(), param.getStream()); | |
| 636 | + } | |
| 637 | + } catch (InvalidArgumentException | ParseException | SipException | | |
| 638 | + SsrcTransactionNotFoundException e) { | |
| 639 | + logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); | |
| 640 | + } | |
| 641 | + } else { | |
| 642 | + logger.info("[无人观看] 未找到设备: {},流:{}", inviteInfo.getDeviceId(), param.getStream()); | |
| 643 | + } | |
| 644 | + | |
| 645 | + inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), | |
| 646 | + inviteInfo.getChannelId(), inviteInfo.getStream()); | |
| 647 | + storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); | |
| 648 | + return ret; | |
| 649 | + } | |
| 650 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null); | |
| 651 | + if (sendRtpItem != null && "talk".equals(sendRtpItem.getApp())) { | |
| 652 | + ret.put("close", false); | |
| 653 | + return ret; | |
| 654 | + } | |
| 655 | + } else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) { | |
| 656 | + ret.put("close", false); | |
| 657 | + } else { | |
| 658 | + // 非国标流 推流/拉流代理 | |
| 659 | + // 拉流代理 | |
| 660 | + StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | |
| 661 | + if (streamProxyItem != null) { | |
| 662 | + if (streamProxyItem.isEnableRemoveNoneReader()) { | |
| 663 | + // 无人观看自动移除 | |
| 664 | + ret.put("close", true); | |
| 665 | + streamProxyService.del(param.getApp(), param.getStream()); | |
| 666 | + String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrcUrl(); | |
| 667 | + logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url); | |
| 668 | + } else if (streamProxyItem.isEnableDisableNoneReader()) { | |
| 669 | + // 无人观看停用 | |
| 670 | + ret.put("close", true); | |
| 671 | + // 修改数据 | |
| 672 | + streamProxyService.stop(param.getApp(), param.getStream()); | |
| 673 | + } else { | |
| 674 | + // 无人观看不做处理 | |
| 675 | + ret.put("close", false); | |
| 676 | + } | |
| 677 | + return ret; | |
| 678 | + } | |
| 679 | + | |
| 680 | + | |
| 681 | + // TODO 推流具有主动性,暂时不做处理 | |
| 682 | +// StreamPushItem streamPushItem = streamPushService.getPush(app, streamId); | |
| 683 | +// if (streamPushItem != null) { | |
| 684 | +// // TODO 发送停止 | |
| 685 | +// } | |
| 686 | + //推流需要更新缓存数据以防止端口占用 | |
| 687 | + logger.info("无人观看流 ---> {}", JSONObject.toJSONString(param)); | |
| 688 | + Object object = redisTemplate.opsForValue().get("patrol:stream:" + param.getStream()); | |
| 689 | + if (object == null) { | |
| 690 | + String[] split = param.getStream().split("_"); | |
| 691 | + if (split != null && split.length == 2) { | |
| 692 | + if (split[1].equals("voice")){ | |
| 693 | + intercomService.stopIntercom(split[0]); | |
| 694 | + logger.info("{} 历史语音断流成功", split[0]); | |
| 695 | + }else { | |
| 696 | + try { | |
| 697 | + jt1078OfCarController.clearMap(split[1],split[0]); | |
| 698 | + } catch (ServiceException e) { | |
| 699 | + throw new RuntimeException(e); | |
| 700 | + } | |
| 701 | + } | |
| 702 | + } | |
| 703 | + } | |
| 704 | + } | |
| 705 | + return ret; | |
| 706 | + } | |
| 707 | + | |
| 708 | + /** | |
| 709 | + * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 | |
| 710 | + */ | |
| 711 | + @ResponseBody | |
| 712 | + @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") | |
| 713 | + public DeferredResult<HookResult> onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) { | |
| 714 | + logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 715 | + | |
| 716 | + DeferredResult<HookResult> defaultResult = new DeferredResult<>(); | |
| 717 | + | |
| 718 | + MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | |
| 719 | + if (!userSetting.isAutoApplyPlay() || mediaInfo == null) { | |
| 720 | + defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | |
| 721 | + return defaultResult; | |
| 722 | + } | |
| 723 | + if ("rtp".equals(param.getApp())) { | |
| 724 | + String[] s = param.getStream().split("_"); | |
| 725 | + if ((s.length != 2 && s.length != 4)) { | |
| 726 | + defaultResult.setResult(HookResult.SUCCESS()); | |
| 727 | + return defaultResult; | |
| 728 | + } | |
| 729 | + String deviceId = s[0]; | |
| 730 | + String channelId = s[1]; | |
| 731 | + Device device = redisCatchStorage.getDevice(deviceId); | |
| 732 | + if (device == null || !device.isOnLine()) { | |
| 733 | + defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | |
| 734 | + return defaultResult; | |
| 735 | + } | |
| 736 | + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); | |
| 737 | + if (deviceChannel == null) { | |
| 738 | + defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | |
| 739 | + return defaultResult; | |
| 740 | + } | |
| 741 | + if (s.length == 2) { | |
| 742 | + logger.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 743 | + | |
| 744 | + RequestMessage msg = new RequestMessage(); | |
| 745 | + String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; | |
| 746 | + boolean exist = resultHolder.exist(key, null); | |
| 747 | + msg.setKey(key); | |
| 748 | + String uuid = UUID.randomUUID().toString(); | |
| 749 | + msg.setId(uuid); | |
| 750 | + DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 751 | + | |
| 752 | + result.onTimeout(() -> { | |
| 753 | + logger.info("[ZLM HOOK] 预览流自动点播, 等待超时"); | |
| 754 | + msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 755 | + resultHolder.invokeAllResult(msg); | |
| 756 | + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | |
| 757 | + storager.stopPlay(deviceId, channelId); | |
| 758 | + }); | |
| 759 | + | |
| 760 | + resultHolder.put(key, uuid, result); | |
| 761 | + | |
| 762 | + if (!exist) { | |
| 763 | + playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> { | |
| 764 | + msg.setData(new HookResult(code, message)); | |
| 765 | + resultHolder.invokeResult(msg); | |
| 766 | + }); | |
| 767 | + } | |
| 768 | + return result; | |
| 769 | + } else if (s.length == 4) { | |
| 770 | + // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间 | |
| 771 | + String startTimeStr = s[2]; | |
| 772 | + String endTimeStr = s[3]; | |
| 773 | + if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) { | |
| 774 | + defaultResult.setResult(HookResult.SUCCESS()); | |
| 775 | + return defaultResult; | |
| 776 | + } | |
| 777 | + String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr); | |
| 778 | + String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr); | |
| 779 | + logger.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}", | |
| 780 | + param.getMediaServerId(), param.getSchema(), | |
| 781 | + param.getApp(), param.getStream(), | |
| 782 | + startTime, endTime | |
| 783 | + ); | |
| 784 | + RequestMessage msg = new RequestMessage(); | |
| 785 | + String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId; | |
| 786 | + boolean exist = resultHolder.exist(key, null); | |
| 787 | + msg.setKey(key); | |
| 788 | + String uuid = UUID.randomUUID().toString(); | |
| 789 | + msg.setId(uuid); | |
| 790 | + DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 791 | + | |
| 792 | + result.onTimeout(() -> { | |
| 793 | + logger.info("[ZLM HOOK] 回放流自动点播, 等待超时"); | |
| 794 | + // 释放rtpserver | |
| 795 | + msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 796 | + resultHolder.invokeResult(msg); | |
| 797 | + }); | |
| 798 | + | |
| 799 | + resultHolder.put(key, uuid, result); | |
| 800 | + | |
| 801 | + if (!exist) { | |
| 802 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null, | |
| 803 | + device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam()); | |
| 804 | + playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> { | |
| 805 | + msg.setData(new HookResult(code, message)); | |
| 806 | + resultHolder.invokeResult(msg); | |
| 807 | + }); | |
| 808 | + } | |
| 809 | + return result; | |
| 810 | + } else { | |
| 811 | + defaultResult.setResult(HookResult.SUCCESS()); | |
| 812 | + return defaultResult; | |
| 813 | + } | |
| 814 | + | |
| 815 | + } else { | |
| 816 | + // 拉流代理 | |
| 817 | + StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | |
| 818 | + if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) { | |
| 819 | + streamProxyService.start(param.getApp(), param.getStream()); | |
| 820 | + } | |
| 821 | + DeferredResult<HookResult> result = new DeferredResult<>(); | |
| 822 | + result.setResult(HookResult.SUCCESS()); | |
| 823 | + return result; | |
| 824 | + } | |
| 825 | + } | |
| 826 | + | |
| 827 | + /** | |
| 828 | + * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 | |
| 829 | + */ | |
| 830 | + @ResponseBody | |
| 831 | + @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") | |
| 832 | + public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) { | |
| 833 | + | |
| 834 | + jsonObject.put("ip", request.getRemoteAddr()); | |
| 835 | + ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject); | |
| 836 | + zlmServerConfig.setIp(request.getRemoteAddr()); | |
| 837 | + logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId()); | |
| 838 | + taskExecutor.execute(() -> { | |
| 839 | + List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started); | |
| 840 | + if (subscribes != null && !subscribes.isEmpty()) { | |
| 841 | + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 842 | + subscribe.response(null, zlmServerConfig); | |
| 843 | + } | |
| 844 | + } | |
| 845 | + mediaServerService.zlmServerOnline(zlmServerConfig); | |
| 846 | + }); | |
| 847 | + | |
| 848 | + return HookResult.SUCCESS(); | |
| 849 | + } | |
| 850 | + | |
| 851 | + /** | |
| 852 | + * 发送rtp(startSendRtp)被动关闭时回调 | |
| 853 | + */ | |
| 854 | + @ResponseBody | |
| 855 | + @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") | |
| 856 | + public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) { | |
| 857 | + | |
| 858 | + logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream()); | |
| 859 | + | |
| 860 | + // 查找对应的上级推流,发送停止 | |
| 861 | + if (!"rtp".equals(param.getApp())) { | |
| 862 | + return HookResult.SUCCESS(); | |
| 863 | + } | |
| 864 | + taskExecutor.execute(() -> { | |
| 865 | + List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); | |
| 866 | + if (sendRtpItems.size() > 0) { | |
| 867 | + for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 868 | + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 869 | + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 870 | + try { | |
| 871 | + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); | |
| 872 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 873 | + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 874 | + } | |
| 875 | + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), | |
| 876 | + sendRtpItem.getCallId(), sendRtpItem.getStream()); | |
| 877 | + } | |
| 878 | + } | |
| 879 | + }); | |
| 880 | + | |
| 881 | + return HookResult.SUCCESS(); | |
| 882 | + } | |
| 883 | + | |
| 884 | + /** | |
| 885 | + * rtpServer收流超时 | |
| 886 | + */ | |
| 887 | + @ResponseBody | |
| 888 | + @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") | |
| 889 | + public HookResult onRtpServerTimeout(@RequestBody OnRtpServerTimeoutHookParam | |
| 890 | + param) { | |
| 891 | + logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); | |
| 892 | + | |
| 893 | + taskExecutor.execute(() -> { | |
| 894 | + List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout); | |
| 895 | + if (subscribes != null && !subscribes.isEmpty()) { | |
| 896 | + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 897 | + subscribe.response(null, param); | |
| 898 | + } | |
| 899 | + } | |
| 900 | + }); | |
| 901 | + | |
| 902 | + return HookResult.SUCCESS(); | |
| 903 | + } | |
| 904 | + | |
| 905 | + /** | |
| 906 | + * 录像完成事件 | |
| 907 | + */ | |
| 908 | + @ResponseBody | |
| 909 | + @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") | |
| 910 | + public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4HookParam param) { | |
| 911 | + logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path()); | |
| 912 | + | |
| 913 | + taskExecutor.execute(() -> { | |
| 914 | + List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_record_mp4); | |
| 915 | + if (subscribes != null && !subscribes.isEmpty()) { | |
| 916 | + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { | |
| 917 | + subscribe.response(null, param); | |
| 918 | + } | |
| 919 | + } | |
| 920 | + cloudRecordService.addRecord(param); | |
| 921 | + | |
| 922 | + }); | |
| 923 | + | |
| 924 | + return HookResult.SUCCESS(); | |
| 925 | + } | |
| 926 | + | |
| 927 | + private Map<String, String> urlParamToMap(String params) { | |
| 928 | + HashMap<String, String> map = new HashMap<>(); | |
| 929 | + if (ObjectUtils.isEmpty(params)) { | |
| 930 | + return map; | |
| 931 | + } | |
| 932 | + String[] paramsArray = params.split("&"); | |
| 933 | + if (paramsArray.length == 0) { | |
| 934 | + return map; | |
| 935 | + } | |
| 936 | + for (String param : paramsArray) { | |
| 937 | + String[] paramArray = param.split("="); | |
| 938 | + if (paramArray.length == 2) { | |
| 939 | + map.put(paramArray[0], paramArray[1]); | |
| 940 | + } | |
| 941 | + } | |
| 942 | + return map; | |
| 943 | + } | |
| 944 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
| ... | ... | @@ -107,7 +107,6 @@ public class ZLMRESTfulUtils { |
| 107 | 107 | responseJSON = JSON.parseObject(responseStr); |
| 108 | 108 | } |
| 109 | 109 | }else { |
| 110 | - System.out.println( 2222); | |
| 111 | 110 | System.out.println( response.code()); |
| 112 | 111 | response.close(); |
| 113 | 112 | Objects.requireNonNull(response.body()).close(); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
| ... | ... | @@ -10,6 +10,7 @@ import com.genersoft.iot.vmp.conf.UserSetting; |
| 10 | 10 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 11 | 11 | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; |
| 12 | 12 | import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; |
| 13 | +import com.genersoft.iot.vmp.jtt1078.util.Configs; | |
| 13 | 14 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 14 | 15 | import com.genersoft.iot.vmp.media.zlm.dto.*; |
| 15 | 16 | import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; |
| ... | ... | @@ -23,8 +24,10 @@ import com.genersoft.iot.vmp.storager.mapper.*; |
| 23 | 24 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 24 | 25 | import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; |
| 25 | 26 | import com.genersoft.iot.vmp.vmanager.jt1078.platform.config.Jt1078ConfigBean; |
| 27 | +import com.genersoft.iot.vmp.vmanager.jt1078.platform.config.RtspConfigBean; | |
| 26 | 28 | import com.genersoft.iot.vmp.vmanager.jt1078.platform.config.ThirdPartyHttpService; |
| 27 | 29 | import com.genersoft.iot.vmp.vmanager.jt1078.platform.domain.StreamSwitch; |
| 30 | +import com.genersoft.iot.vmp.vmanager.jt1078.platform.domain.T9101; | |
| 28 | 31 | import com.genersoft.iot.vmp.vmanager.jt1078.platform.domain.T9102; |
| 29 | 32 | import com.genersoft.iot.vmp.vmanager.util.RedisCache; |
| 30 | 33 | import com.github.pagehelper.PageHelper; |
| ... | ... | @@ -55,6 +58,9 @@ public class StreamPushServiceImpl implements IStreamPushService { |
| 55 | 58 | private GbStreamMapper gbStreamMapper; |
| 56 | 59 | |
| 57 | 60 | @Autowired |
| 61 | + private RtspConfigBean rtspConfigBean; | |
| 62 | + | |
| 63 | + @Autowired | |
| 58 | 64 | private StreamPushMapper streamPushMapper; |
| 59 | 65 | |
| 60 | 66 | @Autowired | ... | ... |
src/main/resources/app.properties
| 1 | 1 | server.port = 40000 |
| 2 | -server.http.port = 3333 | |
| 2 | +server.http.port = 3335 | |
| 3 | 3 | server.history.port = 40001 |
| 4 | 4 | server.backlog = 1024 |
| 5 | 5 | |
| ... | ... | @@ -14,7 +14,7 @@ rtmp.url = rtsp://127.0.0.1:554/schedule/{TAG}?sign={sign} |
| 14 | 14 | |
| 15 | 15 | #rtmp.url = rtsp://192.168.169.100:19555/schedule/{TAG}?sign={sign} |
| 16 | 16 | # 设置为onæ¶ï¼æ§å¶å°å°è¾åºffmpegçè¾åº |
| 17 | -debug.mode = off | |
| 17 | +debug.mode = on | |
| 18 | 18 | |
| 19 | 19 | zlm.host = 127.0.0.1 |
| 20 | 20 | zlm.http.port = 80 | ... | ... |
web_src/config/index.js
| ... | ... | @@ -29,7 +29,7 @@ module.exports = { |
| 29 | 29 | // Various Dev Server settings |
| 30 | 30 | host: "127.0.0.1", |
| 31 | 31 | useLocalIp: false, // can be overwritten by process.env.HOST |
| 32 | - port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined | |
| 32 | + port: 8085, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined | |
| 33 | 33 | autoOpenBrowser: false, |
| 34 | 34 | errorOverlay: true, |
| 35 | 35 | notifyOnErrors: true, | ... | ... |
web_src/src/components/DeviceList1078.vue
| ... | ... | @@ -6,6 +6,7 @@ |
| 6 | 6 | ref="vehicleList" |
| 7 | 7 | @tree-loaded="handleTreeLoaded" |
| 8 | 8 | @node-click="nodeClick" |
| 9 | + @node-dblclick="onVehicleNodeDblclick" | |
| 9 | 10 | @node-contextmenu="nodeContextmenu" |
| 10 | 11 | /> |
| 11 | 12 | </div> |
| ... | ... | @@ -85,6 +86,7 @@ |
| 85 | 86 | @playerClick="handleClick" |
| 86 | 87 | :video-url="videoUrl" |
| 87 | 88 | :videoDataList="videoDataList" |
| 89 | + :has-audio="true" | |
| 88 | 90 | v-model="windowNum" |
| 89 | 91 | style="width: 100%; height: 100%;" |
| 90 | 92 | ></player-list-component> |
| ... | ... | @@ -712,7 +714,7 @@ export default { |
| 712 | 714 | this.intercomTarget = null; |
| 713 | 715 | this.intercomTargetName = ''; |
| 714 | 716 | this.currentIntercomSim = null; |
| 715 | - this.$message.info("对讲已挂断"); | |
| 717 | + // this.$message.info("对讲已挂断"); | |
| 716 | 718 | }, |
| 717 | 719 | |
| 718 | 720 | // 工具:降采样 (Float32 -> Int16) |
| ... | ... | @@ -795,6 +797,12 @@ export default { |
| 795 | 797 | } |
| 796 | 798 | }, |
| 797 | 799 | |
| 800 | + /** 双击车辆(含子通道的节点):与右键「一键播放该设备」相同 */ | |
| 801 | + onVehicleNodeDblclick(data) { | |
| 802 | + if (!data.children || data.children.length === 0) return; | |
| 803 | + this.batchPlayback(data); | |
| 804 | + }, | |
| 805 | + | |
| 798 | 806 | playSingleChannel(data) { |
| 799 | 807 | let stream = data.code.replace('-', '_'); |
| 800 | 808 | let arr = stream.split("_"); |
| ... | ... | @@ -905,7 +913,7 @@ export default { |
| 905 | 913 | clearTimeout(this.carouselTimer); |
| 906 | 914 | this.carouselTimer = null; |
| 907 | 915 | } |
| 908 | - this.$message.info("轮播已停止"); | |
| 916 | + // this.$message.info("轮播已停止"); | |
| 909 | 917 | }, |
| 910 | 918 | |
| 911 | 919 | async startCarousel(config) { | ... | ... |
web_src/src/components/HistoricalRecord.vue
| ... | ... | @@ -29,22 +29,44 @@ |
| 29 | 29 | :query-params="queryParams" |
| 30 | 30 | @handleQuery="handleQuery" |
| 31 | 31 | /> |
| 32 | - <el-row v-if="deviceData || channelData" :gutter="10" class="mb8" style="margin-bottom: 10px;"> | |
| 32 | + <el-row v-if="showSelectionTag" :gutter="10" class="mb8" style="margin-bottom: 10px;"> | |
| 33 | 33 | <el-col :span="24"> |
| 34 | 34 | <el-tag effect="dark" type="success"> |
| 35 | 35 | <i class="el-icon-video-camera"></i> {{ deviceTitle }} |
| 36 | 36 | </el-tag> |
| 37 | 37 | </el-col> |
| 38 | 38 | </el-row> |
| 39 | + | |
| 40 | + <div v-if="allVehicleChannels.length" class="channel-pick"> | |
| 41 | + <div class="channel-pick-main"> | |
| 42 | + <span class="channel-pick-label">查询通道(可多选):</span> | |
| 43 | + <el-checkbox-group v-model="checkedChannelKeys" class="channel-pick-group"> | |
| 44 | + <el-checkbox | |
| 45 | + v-for="c in allVehicleChannels" | |
| 46 | + :key="c.key" | |
| 47 | + :label="c.key" | |
| 48 | + > | |
| 49 | + {{ c.channelName }} | |
| 50 | + </el-checkbox> | |
| 51 | + </el-checkbox-group> | |
| 52 | + </div> | |
| 53 | + <div class="channel-pick-actions"> | |
| 54 | + <el-button type="text" size="mini" @click="selectAllChannels">全选</el-button> | |
| 55 | + <el-button type="text" size="mini" @click="clearChannelSelection">清空</el-button> | |
| 56 | + </div> | |
| 57 | + </div> | |
| 39 | 58 | </div> |
| 40 | 59 | |
| 41 | 60 | <div class="table-section"> |
| 42 | 61 | <history-search-table |
| 43 | 62 | ref="historySearchTable" |
| 44 | - style="width: 100%; height: 100%;" | |
| 63 | + class="history-search-table-host" | |
| 45 | 64 | :table-data="historyData" |
| 65 | + :table-height="historyTableHeight" | |
| 66 | + :busy-channel-keys="busyChannelKeys" | |
| 46 | 67 | @playHistoryVideo="clickHistoricalPlay" |
| 47 | - @uploadHistoryVideo="uploadHistoryVideo" | |
| 68 | + @uploadHistoryVideo="onUploadHistoryVideo" | |
| 69 | + @channel-operation="onChannelOperation" | |
| 48 | 70 | /> |
| 49 | 71 | </div> |
| 50 | 72 | |
| ... | ... | @@ -57,19 +79,18 @@ |
| 57 | 79 | </template> |
| 58 | 80 | |
| 59 | 81 | <script> |
| 60 | -// 1. 引入 VehicleList (替换 Device1078Tree) | |
| 61 | 82 | import VehicleList from "./JT1078Components/deviceList/VehicleList.vue"; |
| 62 | 83 | import HistoryPlayDialog from "./JT1078Components/HistoryPlayDialog.vue"; |
| 63 | 84 | import HistoricalRecordForm from "./JT1078Components/HistoryRecordFrom.vue"; |
| 64 | 85 | import HistorySearchTable from "./JT1078Components/HistorySearchTable.vue"; |
| 65 | 86 | import { Splitpanes, Pane } from 'splitpanes' |
| 66 | 87 | import 'splitpanes/dist/splitpanes.css' |
| 67 | -import {parseTime} from "../../utils/ruoyi"; | |
| 88 | +import { parseTime } from "../../utils/ruoyi"; | |
| 68 | 89 | |
| 69 | 90 | export default { |
| 70 | 91 | name: "HistoricalRecord", |
| 71 | 92 | components: { |
| 72 | - VehicleList, // 注册组件 | |
| 93 | + VehicleList, | |
| 73 | 94 | HistoryPlayDialog, |
| 74 | 95 | HistorySearchTable, |
| 75 | 96 | HistoricalRecordForm, |
| ... | ... | @@ -78,18 +99,18 @@ export default { |
| 78 | 99 | }, |
| 79 | 100 | data() { |
| 80 | 101 | return { |
| 81 | - //列表定时器 | |
| 82 | 102 | timer: null, |
| 83 | - //历史视频列表定时器 | |
| 84 | 103 | historyTimer: null, |
| 85 | 104 | historyData: [], |
| 86 | - //源列表数据 | |
| 87 | 105 | sourceValue: [], |
| 88 | - //遮罩层 | |
| 89 | 106 | loading: false, |
| 90 | - //sim号和通道号 | |
| 91 | 107 | sim_channel: null, |
| 92 | - channelData: null, | |
| 108 | + selectedChannels: [], | |
| 109 | + /** 当前车辆下可选通道(用于复选框) */ | |
| 110 | + allVehicleChannels: [], | |
| 111 | + /** 选中的通道 key:sim_channel */ | |
| 112 | + checkedChannelKeys: [], | |
| 113 | + channelData: null, | |
| 93 | 114 | nodeChannelData: null, |
| 94 | 115 | deviceData: null, |
| 95 | 116 | deviceTitle: '', |
| ... | ... | @@ -101,8 +122,28 @@ export default { |
| 101 | 122 | }, |
| 102 | 123 | videoUrl: [], |
| 103 | 124 | deviceNode: null, |
| 125 | + busyChannelKeys: [], | |
| 126 | + /** 用于计算表格固定高度,避免 el-table height=100% 无像素高度导致无法滚动 */ | |
| 127 | + viewportHeight: typeof window !== 'undefined' ? window.innerHeight : 600 | |
| 104 | 128 | }; |
| 105 | 129 | }, |
| 130 | + computed: { | |
| 131 | + showSelectionTag() { | |
| 132 | + return this.allVehicleChannels.length > 0 || this.deviceData || this.channelData | |
| 133 | + }, | |
| 134 | + historyTableHeight() { | |
| 135 | + const v = this.viewportHeight || 600 | |
| 136 | + /* 预留:顶栏/标签/表单/通道区/内边距(略多留一点以免裁切) */ | |
| 137 | + const reserved = 340 | |
| 138 | + return Math.max(260, Math.floor(v - reserved)) | |
| 139 | + } | |
| 140 | + }, | |
| 141 | + mounted() { | |
| 142 | + this._onViewportResize = () => { | |
| 143 | + this.viewportHeight = window.innerHeight | |
| 144 | + } | |
| 145 | + window.addEventListener('resize', this._onViewportResize) | |
| 146 | + }, | |
| 106 | 147 | watch: { |
| 107 | 148 | deviceNode(val) { |
| 108 | 149 | this.deviceNode = val |
| ... | ... | @@ -111,12 +152,12 @@ export default { |
| 111 | 152 | } |
| 112 | 153 | } |
| 113 | 154 | }, |
| 114 | - created() { | |
| 115 | - // 移除 getCarInfoBuffer() 调用,VehicleList 会自动加载 | |
| 116 | - }, | |
| 117 | 155 | destroyed() { |
| 118 | 156 | clearInterval(this.timer) |
| 119 | 157 | clearInterval(this.historyTimer) |
| 158 | + if (this._onViewportResize) { | |
| 159 | + window.removeEventListener('resize', this._onViewportResize) | |
| 160 | + } | |
| 120 | 161 | }, |
| 121 | 162 | methods: { |
| 122 | 163 | getTodayRange() { |
| ... | ... | @@ -127,148 +168,304 @@ export default { |
| 127 | 168 | return [startOfToday, endOfToday] |
| 128 | 169 | }, |
| 129 | 170 | |
| 130 | - /** | |
| 131 | - * 接收 VehicleList 加载完成的数据 | |
| 132 | - */ | |
| 133 | 171 | handleTreeLoaded(data) { |
| 134 | 172 | this.sourceValue = data; |
| 135 | 173 | }, |
| 136 | 174 | |
| 175 | + channelKey(sim, channel) { | |
| 176 | + return String(sim) + '_' + String(channel) | |
| 177 | + }, | |
| 178 | + | |
| 179 | + parseChannelFromId(nodeData) { | |
| 180 | + const parts = String(nodeData.id).split('_') | |
| 181 | + if (parts.length !== 3) return null | |
| 182 | + const sim = parts[1] | |
| 183 | + const channel = parts[2] | |
| 184 | + return { | |
| 185 | + sim, | |
| 186 | + channel, | |
| 187 | + channelName: nodeData.name, | |
| 188 | + vehicleName: nodeData.pid, | |
| 189 | + key: this.channelKey(sim, channel) | |
| 190 | + } | |
| 191 | + }, | |
| 192 | + | |
| 193 | + buildNodeChannelMap(children) { | |
| 194 | + const map = {} | |
| 195 | + if (!children) return map | |
| 196 | + for (let i = 0; i < children.length; i++) { | |
| 197 | + const c = children[i] | |
| 198 | + const ids = c.id.split('_') | |
| 199 | + if (ids.length === 3) { | |
| 200 | + c.deviceId = ids[0] | |
| 201 | + c.channelId = ids[2] | |
| 202 | + map[ids[2]] = c | |
| 203 | + } | |
| 204 | + } | |
| 205 | + return map | |
| 206 | + }, | |
| 207 | + | |
| 137 | 208 | /** |
| 138 | - * 树点击事件 (逻辑保持不变,适配 VehicleList 的数据结构) | |
| 209 | + * 点击通道:单通道并默认勾选;点击车辆:列出通道,需手动勾选再搜索 | |
| 139 | 210 | */ |
| 140 | 211 | nodeClick(data, node) { |
| 141 | - if (data) { | |
| 142 | - // VehicleList 生成的 ID 格式通常为 deviceId_sim_channel | |
| 143 | - let split = data.id.split("_"); | |
| 144 | - this.deviceNode = node | |
| 145 | - this.nodeChannelData = {}; | |
| 146 | - let nodeChannelDataList = []; | |
| 147 | - | |
| 148 | - // 判断是否为通道节点 (根据你的逻辑,长度为3代表是通道) | |
| 149 | - if (split.length === 3) { | |
| 150 | - this.sim_channel = split[1] + '_' + split[2] | |
| 151 | - this.channelData = data | |
| 152 | - this.deviceTitle = `车辆:${data.pid} 通道:${data.name}` // data.pid 是 VehicleList 处理好的父级ID | |
| 153 | - | |
| 154 | - // 获取同级所有通道用于显示 | |
| 155 | - let children = node.parent.data.children; | |
| 156 | - if (children) { | |
| 157 | - for (let i in children) { | |
| 158 | - const nodeChannelData = children[i]; | |
| 159 | - let ids = nodeChannelData.id.split("_"); | |
| 160 | - if (ids.length === 3) { | |
| 161 | - nodeChannelData.deviceId = ids[0]; | |
| 162 | - nodeChannelData.channelId = ids[2]; | |
| 163 | - nodeChannelDataList.push(nodeChannelData) | |
| 164 | - } | |
| 212 | + if (!data) return | |
| 213 | + const split = data.id.split('_') | |
| 214 | + this.deviceNode = node | |
| 215 | + | |
| 216 | + if (split.length === 3) { | |
| 217 | + const ch = this.parseChannelFromId(data) | |
| 218 | + this.channelData = data | |
| 219 | + this.deviceData = node.parent && node.parent.data ? node.parent.data : null | |
| 220 | + this.deviceTitle = `车辆:${data.pid} 通道:${data.name}` | |
| 221 | + | |
| 222 | + const children = node.parent && node.parent.data && node.parent.data.children | |
| 223 | + this.nodeChannelData = this.buildNodeChannelMap(children) | |
| 224 | + | |
| 225 | + this.allVehicleChannels = [] | |
| 226 | + if (children && children.length) { | |
| 227 | + for (let i = 0; i < children.length; i++) { | |
| 228 | + const p = this.parseChannelFromId(children[i]) | |
| 229 | + if (p) { | |
| 230 | + p.vehicleName = data.pid | |
| 231 | + this.allVehicleChannels.push(p) | |
| 165 | 232 | } |
| 166 | - this.nodeChannelData = nodeChannelDataList.reduce((map, item) => { | |
| 167 | - map[item.channelId] = item; | |
| 168 | - return map; | |
| 169 | - }, {}); | |
| 170 | 233 | } |
| 171 | - } else { | |
| 172 | - // 点击了父设备节点 | |
| 173 | - this.deviceData = data; | |
| 174 | - this.deviceTitle = `车辆:${data.name}`; | |
| 175 | - // 清空通道选择,或者默认选择第一个通道,视业务需求而定 | |
| 176 | - this.sim_channel = null; | |
| 234 | + } else if (ch) { | |
| 235 | + this.allVehicleChannels = [ch] | |
| 236 | + } | |
| 237 | + this.checkedChannelKeys = ch ? [ch.key] : [] | |
| 238 | + this.selectedChannels = ch ? [ch] : [] | |
| 239 | + this.sim_channel = ch ? ch.sim + '_' + ch.channel : null | |
| 240 | + return | |
| 241 | + } | |
| 242 | + | |
| 243 | + this.deviceData = data | |
| 244 | + this.channelData = null | |
| 245 | + const kids = data.children | |
| 246 | + if (kids && kids.length > 0) { | |
| 247 | + const fp = kids[0] && kids[0].id ? String(kids[0].id).split('_') : [] | |
| 248 | + if (fp.length === 3) { | |
| 249 | + this.allVehicleChannels = [] | |
| 250 | + for (let i = 0; i < kids.length; i++) { | |
| 251 | + const p = this.parseChannelFromId(kids[i]) | |
| 252 | + if (p) { | |
| 253 | + p.vehicleName = data.name | |
| 254 | + this.allVehicleChannels.push(p) | |
| 255 | + } | |
| 256 | + } | |
| 257 | + this.nodeChannelData = this.buildNodeChannelMap(kids) | |
| 258 | + this.checkedChannelKeys = [] | |
| 259 | + this.selectedChannels = [] | |
| 260 | + this.sim_channel = null | |
| 261 | + this.deviceTitle = `车辆:${data.name} · 请勾选要查询的通道后点击搜索` | |
| 262 | + return | |
| 177 | 263 | } |
| 178 | 264 | } |
| 265 | + this.allVehicleChannels = [] | |
| 266 | + this.checkedChannelKeys = [] | |
| 267 | + this.selectedChannels = [] | |
| 268 | + this.sim_channel = null | |
| 269 | + this.nodeChannelData = {} | |
| 270 | + this.deviceTitle = `车辆/分组:${data.name}(请展开后选择车辆或通道)` | |
| 271 | + }, | |
| 272 | + | |
| 273 | + selectAllChannels() { | |
| 274 | + this.checkedChannelKeys = this.allVehicleChannels.map(c => c.key) | |
| 275 | + }, | |
| 276 | + | |
| 277 | + clearChannelSelection() { | |
| 278 | + this.checkedChannelKeys = [] | |
| 179 | 279 | }, |
| 180 | 280 | |
| 181 | 281 | handleQuery(queryParams) { |
| 182 | - this.queryParams = {...this.queryParams, ...queryParams}; | |
| 282 | + this.queryParams = { ...this.queryParams, ...queryParams }; | |
| 183 | 283 | this.searchHistoryList() |
| 184 | 284 | }, |
| 185 | 285 | |
| 186 | - // --- 【删除】所有手动获取和处理树数据的方法 --- | |
| 187 | - // 删除 getCarInfoBuffer, getCarInfo, statisticsOnline, processingTreeData | |
| 188 | - // 删除 addChannels, disableItemsByName, processingSimList | |
| 189 | - // 理由:VehicleList 组件内部已经完成了这些工作 | |
| 190 | - | |
| 191 | 286 | clickHistoricalPlay(data) { |
| 192 | 287 | this.playHistoryItem(data) |
| 193 | 288 | }, |
| 194 | 289 | |
| 195 | - uploadHistoryVideo(data) { | |
| 290 | + channelBusyKey(row) { | |
| 291 | + return String(row.sim) + '_' + String(row.channel) | |
| 292 | + }, | |
| 293 | + | |
| 294 | + onChannelOperation({ key, busy }) { | |
| 295 | + if (busy) { | |
| 296 | + if (this.busyChannelKeys.indexOf(key) < 0) { | |
| 297 | + this.busyChannelKeys.push(key) | |
| 298 | + } | |
| 299 | + } else { | |
| 300 | + this.busyChannelKeys = this.busyChannelKeys.filter(k => k !== key) | |
| 301 | + } | |
| 302 | + }, | |
| 303 | + | |
| 304 | + onUploadHistoryVideo(row) { | |
| 305 | + const key = this.channelBusyKey(row) | |
| 306 | + if (this.busyChannelKeys.indexOf(key) >= 0) { | |
| 307 | + this.$message.warning('该通道正在上传或下载,请稍候') | |
| 308 | + return | |
| 309 | + } | |
| 310 | + this.busyChannelKeys.push(key) | |
| 196 | 311 | this.loading = true |
| 197 | 312 | this.$axios({ |
| 198 | 313 | method: 'get', |
| 199 | - url: '/api/jt1078/query/history/uploading/' + data.name | |
| 200 | - }).then(res => { | |
| 314 | + url: '/api/jt1078/query/history/uploading/' + row.name | |
| 315 | + }).then(() => { | |
| 201 | 316 | this.$message.success("视频开始上传,请等待") |
| 202 | 317 | this.searchHistoryList() |
| 203 | - this.loading = false | |
| 204 | - }).catch(err => { | |
| 318 | + }).catch(() => { | |
| 205 | 319 | this.$message.error("视频上传失败") |
| 320 | + }).finally(() => { | |
| 321 | + this.busyChannelKeys = this.busyChannelKeys.filter(k => k !== key) | |
| 206 | 322 | this.loading = false |
| 207 | 323 | }) |
| 208 | 324 | }, |
| 209 | 325 | |
| 210 | - searchHistoryList() { | |
| 211 | - let simChannel = this.sim_channel; | |
| 212 | - if (this.isEmpty(simChannel)) { | |
| 213 | - return this.$message.error('请先点击左侧选择车辆通道'); | |
| 326 | + extractHistoryItems(res) { | |
| 327 | + try { | |
| 328 | + const items = res.data && res.data.data && res.data.data.obj && res.data.data.obj.data && res.data.data.obj.data.items | |
| 329 | + return Array.isArray(items) ? items : null | |
| 330 | + } catch (e) { | |
| 331 | + return null | |
| 332 | + } | |
| 333 | + }, | |
| 334 | + | |
| 335 | + listResponseErrorMsg(res) { | |
| 336 | + const top = res && res.data | |
| 337 | + const body = top && top.data | |
| 338 | + if (body && (body.msg || body.message)) return body.msg || body.message | |
| 339 | + if (top && (top.msg || top.message)) return top.msg || top.message | |
| 340 | + return '' | |
| 341 | + }, | |
| 342 | + | |
| 343 | + channelDisplayName(sim, channelId) { | |
| 344 | + const k = String(channelId) | |
| 345 | + if (this.nodeChannelData && this.nodeChannelData[k]) { | |
| 346 | + return this.nodeChannelData[k].name | |
| 214 | 347 | } |
| 348 | + const found = this.allVehicleChannels.find( | |
| 349 | + c => String(c.sim) === String(sim) && String(c.channel) === k | |
| 350 | + ) | |
| 351 | + if (found && found.channelName) return found.channelName | |
| 352 | + return `通道${Number(channelId)}` | |
| 353 | + }, | |
| 215 | 354 | |
| 216 | - let split = simChannel.split('_'); | |
| 217 | - let sim = split[0]; | |
| 218 | - let channel = split[1]; | |
| 355 | + defaultDeviceIdForRow(sim) { | |
| 356 | + if (this.deviceData && this.deviceData.name) return this.deviceData.name | |
| 357 | + if (this.channelData && this.channelData.pid) return this.channelData.pid | |
| 358 | + const hit = this.allVehicleChannels.find(c => String(c.sim) === String(sim)) | |
| 359 | + return (hit && hit.vehicleName) ? hit.vehicleName : sim | |
| 360 | + }, | |
| 219 | 361 | |
| 362 | + /** 串行请求历史列表,避免多路并发压垮 JT1078 网关 9205 */ | |
| 363 | + async searchHistoryList() { | |
| 364 | + if (!this.allVehicleChannels.length) { | |
| 365 | + return this.$message.error('请先点击左侧选择车辆或通道') | |
| 366 | + } | |
| 367 | + if (!this.checkedChannelKeys.length) { | |
| 368 | + return this.$message.warning('请至少勾选一个要查询的通道') | |
| 369 | + } | |
| 220 | 370 | if (!this.queryParams.time) { |
| 221 | 371 | return this.$message.error('请选择开始和结束时间'); |
| 222 | 372 | } |
| 223 | 373 | |
| 224 | - this.loading = true; | |
| 225 | - this.$axios({ | |
| 226 | - method: 'get', | |
| 227 | - url: '/api/jt1078/query/history/list/' + sim + '/' + channel + "/" + parseTime(this.queryParams.time[0], '{y}-{m}-{d} {h}:{i}:{s}') + "/" + parseTime(this.queryParams.time[1], '{y}-{m}-{d} {h}:{i}:{s}') | |
| 228 | - }).then(res => { | |
| 229 | - let items = res.data.data.obj.data.items; | |
| 230 | - if (items) { | |
| 231 | - for (let i in items) { | |
| 232 | - items[i].disabled = false; | |
| 233 | - items[i].countdown = 10; | |
| 234 | - items[i].channelName = this.nodeChannelData[items[i].channel] ? this.nodeChannelData[items[i].channel].name : `通道${Number(items[i].channel)}` | |
| 235 | - items[i].deviceId = this.deviceData ? this.deviceData.name : this.channelData.pid | |
| 374 | + const toQuery = this.allVehicleChannels.filter(c => this.checkedChannelKeys.indexOf(c.key) >= 0) | |
| 375 | + if (!toQuery.length) { | |
| 376 | + return this.$message.warning('所选通道无效,请重新勾选') | |
| 377 | + } | |
| 378 | + | |
| 379 | + const t0 = parseTime(this.queryParams.time[0], '{y}-{m}-{d} {h}:{i}:{s}') | |
| 380 | + const t1 = parseTime(this.queryParams.time[1], '{y}-{m}-{d} {h}:{i}:{s}') | |
| 381 | + this.loading = true | |
| 382 | + const merged = [] | |
| 383 | + let failCount = 0 | |
| 384 | + | |
| 385 | + try { | |
| 386 | + for (let i = 0; i < toQuery.length; i++) { | |
| 387 | + const ch = toQuery[i] | |
| 388 | + try { | |
| 389 | + const res = await this.$axios({ | |
| 390 | + method: 'get', | |
| 391 | + url: '/api/jt1078/query/history/list/' + ch.sim + '/' + ch.channel + '/' + t0 + '/' + t1 | |
| 392 | + }) | |
| 393 | + const inner = res.data && res.data.data | |
| 394 | + const code = inner && inner.code | |
| 395 | + if (code === '-1' || code === -1) { | |
| 396 | + const msg = this.listResponseErrorMsg(res) || '该通道查询失败' | |
| 397 | + this.$message.warning(`通道 ${ch.channelName}: ${msg}`) | |
| 398 | + failCount++ | |
| 399 | + continue | |
| 400 | + } | |
| 401 | + const items = this.extractHistoryItems(res) | |
| 402 | + if (!items || !items.length) continue | |
| 403 | + for (let j = 0; j < items.length; j++) { | |
| 404 | + const it = { ...items[j] } | |
| 405 | + it.disabled = false | |
| 406 | + it.countdown = 10 | |
| 407 | + it.channelName = this.channelDisplayName(it.sim || ch.sim, it.channel) | |
| 408 | + it.deviceId = this.defaultDeviceIdForRow(it.sim || ch.sim) | |
| 409 | + merged.push(it) | |
| 410 | + } | |
| 411 | + } catch (e) { | |
| 412 | + failCount++ | |
| 413 | + console.warn(e) | |
| 414 | + this.$message.warning(`通道 ${ch.channelName} 请求异常`) | |
| 236 | 415 | } |
| 237 | - this.historyData = items; | |
| 238 | - } else { | |
| 239 | - this.historyData = []; | |
| 240 | - this.$message.warning("搜索历史列表为空"); | |
| 241 | 416 | } |
| 417 | + merged.sort((a, b) => { | |
| 418 | + const ta = new Date(a.startTime || 0).getTime() | |
| 419 | + const tb = new Date(b.startTime || 0).getTime() | |
| 420 | + return ta - tb | |
| 421 | + }) | |
| 422 | + this.historyData = merged | |
| 423 | + this.selectedChannels = toQuery | |
| 424 | + if (merged.length === 0) { | |
| 425 | + this.$message.warning(failCount > 0 ? '全部通道未返回列表,请查看网关或超时配置' : '搜索历史列表为空') | |
| 426 | + } | |
| 427 | + } finally { | |
| 242 | 428 | this.loading = false |
| 243 | - }).catch(error => { | |
| 244 | - this.loading = false | |
| 245 | - this.$message.error("发送历史视频列表指令异常"); | |
| 246 | - }) | |
| 429 | + } | |
| 247 | 430 | }, |
| 248 | 431 | |
| 249 | 432 | playHistoryItem(e) { |
| 250 | - this.videoUrl = []; | |
| 251 | 433 | this.loading = true |
| 252 | 434 | this.$axios({ |
| 253 | 435 | method: 'get', |
| 254 | 436 | url: '/api/jt1078/query/send/request/io/history/' + e.sim + '/' + e.channel + "/" + e.startTime + "/" + e.endTime + "/" + e.channelMapping |
| 255 | 437 | }).then(res => { |
| 256 | 438 | if (res.data && res.data.data && res.data.data.data) { |
| 257 | - let videoUrl1 = (location.protocol === "https:") ? res.data.data.data.wss_flv : res.data.data.data.ws_flv; | |
| 258 | - | |
| 259 | - this.$refs.historyPlayDialog.updateOpen(true) | |
| 260 | - this.$refs.historyPlayDialog.data = { | |
| 261 | - videoUrl: videoUrl1, | |
| 262 | - startTime: e.startTime, | |
| 263 | - endTime: e.endTime, | |
| 264 | - deviceId: e.deviceId, | |
| 265 | - channelName: e.channelName, | |
| 266 | - channel: e.channel, | |
| 267 | - sim: e.sim | |
| 439 | + const d = res.data.data.data | |
| 440 | + const videoUrl1 = location.protocol === 'https:' | |
| 441 | + ? (d.wss_flv || d.ws_flv || d.flv) | |
| 442 | + : (d.ws_flv || d.wss_flv || d.flv) | |
| 443 | + if (!videoUrl1) { | |
| 444 | + this.$message.error('未获取到播放地址') | |
| 445 | + this.loading = false | |
| 446 | + return | |
| 268 | 447 | } |
| 448 | + const dlg = this.$refs.historyPlayDialog | |
| 449 | + dlg.updateOpen(true) | |
| 450 | + this.$nextTick(() => { | |
| 451 | + dlg.data = { | |
| 452 | + videoUrl: videoUrl1, | |
| 453 | + startTime: e.startTime, | |
| 454 | + endTime: e.endTime, | |
| 455 | + deviceId: e.deviceId, | |
| 456 | + channelName: e.channelName, | |
| 457 | + channel: e.channel, | |
| 458 | + sim: e.sim, | |
| 459 | + channelMapping: e.channelMapping | |
| 460 | + } | |
| 461 | + this.loading = false | |
| 462 | + }) | |
| 269 | 463 | } else { |
| 270 | 464 | this.$message.error(res.data.msg || "获取播放地址失败"); |
| 465 | + this.loading = false | |
| 271 | 466 | } |
| 467 | + }).catch(() => { | |
| 468 | + this.$message.error("请求播放地址异常") | |
| 272 | 469 | this.loading = false |
| 273 | 470 | }) |
| 274 | 471 | }, |
| ... | ... | @@ -285,7 +482,6 @@ export default { |
| 285 | 482 | </script> |
| 286 | 483 | |
| 287 | 484 | <style scoped> |
| 288 | -/* 1. 全局容器 */ | |
| 289 | 485 | .history-container { |
| 290 | 486 | height: 100%; |
| 291 | 487 | width: 100%; |
| ... | ... | @@ -295,16 +491,21 @@ export default { |
| 295 | 491 | .layout-main { |
| 296 | 492 | padding: 0; |
| 297 | 493 | height: 100%; |
| 494 | + min-height: 0; | |
| 298 | 495 | overflow: hidden; |
| 299 | 496 | } |
| 300 | 497 | |
| 301 | -/* 2. SplitPanes 容器背景统一 */ | |
| 302 | 498 | .splitpanes-container { |
| 303 | 499 | height: 100%; |
| 304 | - background-color: #ffffff; /* 【修改】统一为白色 */ | |
| 500 | + min-height: 0; | |
| 501 | + background-color: #ffffff; | |
| 502 | +} | |
| 503 | + | |
| 504 | +.splitpanes-container ::v-deep .splitpanes__pane { | |
| 505 | + min-height: 0; | |
| 506 | + overflow: hidden; | |
| 305 | 507 | } |
| 306 | 508 | |
| 307 | -/* 3. 左侧侧边栏 (保持之前的优化) */ | |
| 308 | 509 | .aside-pane { |
| 309 | 510 | background-color: #ffffff; |
| 310 | 511 | border-right: 1px solid #dcdfe6; |
| ... | ... | @@ -324,58 +525,103 @@ export default { |
| 324 | 525 | box-sizing: border-box; |
| 325 | 526 | } |
| 326 | 527 | |
| 327 | -/* 样式穿透修复 DeviceList (保持之前的优化) */ | |
| 328 | 528 | .tree-wrapper ::v-deep .head-container { background-color: transparent !important; padding: 0 !important; margin: 0 !important; } |
| 329 | 529 | .tree-wrapper ::v-deep .head-container .el-row { margin: 0 !important; display: block; } |
| 330 | 530 | .tree-wrapper ::v-deep .head-container .el-col { width: 100% !important; padding: 0 !important; float: none !important; } |
| 331 | 531 | .tree-wrapper ::v-deep .el-input__inner { border-radius: 4px; } |
| 332 | 532 | .tree-wrapper ::v-deep .vue-easy-tree, .tree-wrapper ::v-deep .filter-tree { margin-top: 10px !important; padding-left: 5px !important; background-color: #ffffff !important; } |
| 333 | 533 | |
| 334 | -/* ============================================================ | |
| 335 | - 【核心修改区】右侧样式统一 | |
| 336 | - ============================================================ */ | |
| 337 | - | |
| 338 | -/* 4. 右侧主面板 */ | |
| 339 | 534 | .main-pane { |
| 340 | - background-color: #ffffff; /* 【修改】背景纯白 */ | |
| 535 | + background-color: #ffffff; | |
| 341 | 536 | padding: 0; |
| 342 | 537 | overflow: hidden; |
| 538 | + height: 100%; | |
| 539 | + display: flex; | |
| 540 | + flex-direction: column; | |
| 541 | + min-height: 0; | |
| 343 | 542 | } |
| 344 | 543 | |
| 345 | -/* 5. 内容包裹层 */ | |
| 346 | 544 | .content-main { |
| 347 | - height: 100%; | |
| 545 | + flex: 1; | |
| 546 | + min-height: 0; | |
| 348 | 547 | width: 100%; |
| 349 | 548 | display: flex; |
| 350 | 549 | flex-direction: column; |
| 351 | - background-color: #ffffff; /* 【修改】背景纯白 */ | |
| 352 | - | |
| 353 | - /* 【关键修改】移除外边距和圆角,填满整个区域 */ | |
| 550 | + background-color: #ffffff; | |
| 354 | 551 | margin: 0; |
| 355 | 552 | border-radius: 0; |
| 356 | - | |
| 357 | - /* 内部保留间距 */ | |
| 358 | 553 | padding: 20px; |
| 359 | 554 | box-sizing: border-box; |
| 360 | 555 | } |
| 361 | 556 | |
| 362 | -/* 6. 顶部搜索区 */ | |
| 363 | 557 | .header-section { |
| 364 | 558 | flex-shrink: 0; |
| 365 | 559 | margin-bottom: 15px; |
| 366 | - border-bottom: 1px solid #EBEEF5; /* 保留底部分割线 */ | |
| 560 | + border-bottom: 1px solid #EBEEF5; | |
| 367 | 561 | padding-bottom: 10px; |
| 368 | 562 | } |
| 369 | 563 | |
| 370 | -/* 7. 表格区域 */ | |
| 564 | +.channel-pick { | |
| 565 | + margin-top: 10px; | |
| 566 | + padding: 10px 12px; | |
| 567 | + background: #f5f7fa; | |
| 568 | + border-radius: 4px; | |
| 569 | + border: 1px solid #ebeef5; | |
| 570 | + display: flex; | |
| 571 | + flex-direction: row; | |
| 572 | + align-items: flex-start; | |
| 573 | + justify-content: space-between; | |
| 574 | + gap: 16px; | |
| 575 | +} | |
| 576 | + | |
| 577 | +.channel-pick-main { | |
| 578 | + flex: 1; | |
| 579 | + min-width: 0; | |
| 580 | + display: flex; | |
| 581 | + flex-wrap: wrap; | |
| 582 | + align-items: flex-start; | |
| 583 | + gap: 8px 12px; | |
| 584 | +} | |
| 585 | + | |
| 586 | +.channel-pick-label { | |
| 587 | + flex-shrink: 0; | |
| 588 | + font-size: 13px; | |
| 589 | + color: #606266; | |
| 590 | + line-height: 28px; | |
| 591 | +} | |
| 592 | + | |
| 593 | +.channel-pick-group { | |
| 594 | + display: flex; | |
| 595 | + flex-wrap: wrap; | |
| 596 | + align-items: center; | |
| 597 | + gap: 4px 12px; | |
| 598 | + flex: 1; | |
| 599 | + min-width: 0; | |
| 600 | +} | |
| 601 | + | |
| 602 | +.channel-pick-actions { | |
| 603 | + flex-shrink: 0; | |
| 604 | + margin-left: auto; | |
| 605 | + padding-left: 12px; | |
| 606 | + white-space: nowrap; | |
| 607 | + line-height: 28px; | |
| 608 | +} | |
| 609 | + | |
| 371 | 610 | .table-section { |
| 372 | - flex: 1; /* 撑满剩余高度 */ | |
| 611 | + flex: 1; | |
| 612 | + min-height: 0; | |
| 373 | 613 | overflow: hidden; |
| 374 | 614 | background: #ffffff; |
| 375 | 615 | position: relative; |
| 376 | 616 | } |
| 377 | 617 | |
| 378 | -/* 修复搜索表单 Label 颜色 */ | |
| 618 | +.history-search-table-host { | |
| 619 | + width: 100%; | |
| 620 | + height: 100%; | |
| 621 | + min-height: 0; | |
| 622 | + display: block; | |
| 623 | +} | |
| 624 | + | |
| 379 | 625 | ::v-deep .el-form-item__label { |
| 380 | 626 | color: #606266; |
| 381 | 627 | font-weight: 500; | ... | ... |
web_src/src/components/JT1078Components/HistoryPlayDialog.vue
| ... | ... | @@ -5,60 +5,59 @@ |
| 5 | 5 | :visible.sync="open" |
| 6 | 6 | width="90%" |
| 7 | 7 | center |
| 8 | - :before-close="handleClose" | |
| 9 | - class="history-dialog-center"> | |
| 8 | + append-to-body | |
| 9 | + @opened="onDialogOpened" | |
| 10 | + > | |
| 10 | 11 | <el-container> |
| 11 | - <el-main> | |
| 12 | - <el-card class="box-card" shadow="always" :body-style="{ height: '95%' }"> | |
| 13 | - <div class='main-play'> | |
| 14 | - <video-player :class="`video`" ref="player" | |
| 15 | - :initial-play-url="videoUrl" style="width: 100%;height: 100%;" | |
| 16 | - @getTime="getTime" | |
| 17 | - ></video-player> | |
| 12 | + <el-main style="display: flex;justify-content: center;align-items: center;background-color: #e9eef3;"> | |
| 13 | + <el-card class="box-card" shadow="always" :body-style="{ height: '100%', padding: '10px' }"> | |
| 14 | + <div class="main-play"> | |
| 15 | + <jess-video-player | |
| 16 | + v-if="videoUrl" | |
| 17 | + ref="histPlayer" | |
| 18 | + :key="'hist-' + playSessionKey" | |
| 19 | + class="hist-jess-player" | |
| 20 | + :initial-play-url="videoUrl" | |
| 21 | + :vod="true" | |
| 22 | + :jessibuca-timeout="60" | |
| 23 | + :ui-play-timeout-sec="45" | |
| 24 | + :player-auto-resize="true" | |
| 25 | + /> | |
| 18 | 26 | </div> |
| 19 | 27 | </el-card> |
| 20 | 28 | </el-main> |
| 21 | 29 | </el-container> |
| 22 | - <el-footer> | |
| 23 | - <div> | |
| 24 | - <TimeLine | |
| 25 | - ref="time_line" | |
| 26 | - @change="changeDate" | |
| 27 | - :width="width" | |
| 28 | - :mark-time="markTime" | |
| 29 | - :time-range="time_range" | |
| 30 | - :isAutoPlay="isAutoPlay" | |
| 31 | - :startMeddleTime="startMeddleTime" | |
| 32 | - @click="clickCanvas" | |
| 33 | - /> | |
| 34 | - </div> | |
| 30 | + <el-footer style="padding: 0;background-color: #2b2f33;"> | |
| 31 | + <TimeLine | |
| 32 | + ref="time_line" | |
| 33 | + @change="changeDate" | |
| 34 | + :width="width" | |
| 35 | + :mark-time="markTime" | |
| 36 | + :time-range="time_range" | |
| 37 | + :isAutoPlay="isAutoPlay" | |
| 38 | + :startMeddleTime="startMeddleTime" | |
| 39 | + @click="clickCanvas" | |
| 40 | + /> | |
| 35 | 41 | </el-footer> |
| 36 | 42 | <span slot="footer" class="dialog-footer"> |
| 37 | - <el-button @click="handleClose">取 消</el-button> | |
| 38 | - </span> | |
| 43 | + <el-button @click="handleClose">取 消</el-button> | |
| 44 | + </span> | |
| 39 | 45 | </el-dialog> |
| 40 | 46 | </template> |
| 41 | 47 | |
| 42 | 48 | <script> |
| 43 | -import videoPlayer from "../common/JessVideoPlayer.vue"; | |
| 49 | +import JessVideoPlayer from '../common/JessVideoPlayer.vue' | |
| 44 | 50 | import TimeLine from './TimeLineCanvas.vue' |
| 45 | -import dayjs from 'dayjs' | |
| 46 | -import {formattedTime} from "../../../utils/dateFormate"; | |
| 47 | -//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等), | |
| 48 | -//例如:import 《组件名称》 from '《组件路径》, | |
| 51 | + | |
| 49 | 52 | export default { |
| 50 | - name: "HistoryPlayDialog", | |
| 51 | - //import引入的组件需要注入到对象中才能使用" | |
| 52 | - components: {TimeLine, videoPlayer}, | |
| 53 | - props: { | |
| 54 | - }, | |
| 53 | + name: 'HistoryPlayDialog', | |
| 54 | + components: { TimeLine, JessVideoPlayer }, | |
| 55 | 55 | data() { |
| 56 | - //这里存放数据" | |
| 57 | 56 | return { |
| 58 | - data:{}, | |
| 57 | + data: {}, | |
| 59 | 58 | videoUrl: null, |
| 60 | 59 | isAutoPlay: false, |
| 61 | - width: "100%", | |
| 60 | + width: '100%', | |
| 62 | 61 | startMeddleTime: null, |
| 63 | 62 | startTime: null, |
| 64 | 63 | endTime: null, |
| ... | ... | @@ -67,19 +66,16 @@ export default { |
| 67 | 66 | form: { |
| 68 | 67 | code: '', |
| 69 | 68 | startTime: '', |
| 70 | - endTime: '', | |
| 69 | + endTime: '' | |
| 71 | 70 | }, |
| 72 | - channelList: [], | |
| 73 | - pickerOptions: {}, | |
| 74 | 71 | open: false, |
| 75 | - }; | |
| 72 | + playSessionKey: 0 | |
| 73 | + } | |
| 76 | 74 | }, |
| 77 | - //计算属性 类似于data概念", | |
| 78 | - computed: {}, | |
| 79 | - //监控data中的数据变化", | |
| 80 | 75 | watch: { |
| 81 | 76 | data(val) { |
| 82 | - console.log('播放数据', val) | |
| 77 | + if (!val || !val.videoUrl) return | |
| 78 | + this.playSessionKey += 1 | |
| 83 | 79 | this.videoUrl = val.videoUrl |
| 84 | 80 | this.startMeddleTime = val.startTime |
| 85 | 81 | this.startTime = val.startTime |
| ... | ... | @@ -89,149 +85,80 @@ export default { |
| 89 | 85 | { |
| 90 | 86 | beginTime: val.startTime, |
| 91 | 87 | endTime: val.endTime, |
| 92 | - bgColor: "green", | |
| 93 | - text: "有视频", | |
| 94 | - }, | |
| 88 | + bgColor: 'green', | |
| 89 | + text: '有视频' | |
| 90 | + } | |
| 95 | 91 | ] |
| 96 | 92 | this.form.startTime = this.startTime |
| 97 | 93 | this.form.endTime = this.endTime |
| 94 | + this.$nextTick(() => { | |
| 95 | + const p = this.$refs.histPlayer | |
| 96 | + if (p && typeof p.tryResize === 'function') p.tryResize() | |
| 97 | + }) | |
| 98 | 98 | }, |
| 99 | + videoUrl() { | |
| 100 | + this.$nextTick(() => { | |
| 101 | + const p = this.$refs.histPlayer | |
| 102 | + if (p && typeof p.tryResize === 'function') p.tryResize() | |
| 103 | + }) | |
| 104 | + } | |
| 99 | 105 | }, |
| 100 | - //方法集合", | |
| 101 | 106 | methods: { |
| 102 | - getTime(time) { | |
| 103 | - // console.log('当前视频帧',time) | |
| 107 | + onDialogOpened() { | |
| 108 | + this.$nextTick(() => { | |
| 109 | + const p = this.$refs.histPlayer | |
| 110 | + if (p && typeof p.tryResize === 'function') p.tryResize() | |
| 111 | + }) | |
| 104 | 112 | }, |
| 105 | - updateOpen(flag){ | |
| 113 | + updateOpen(flag) { | |
| 106 | 114 | this.open = flag |
| 107 | 115 | }, |
| 108 | 116 | clickCanvas(date) { |
| 117 | + const map = this.data.channelMapping != null ? String(this.data.channelMapping) : '' | |
| 109 | 118 | this.$axios({ |
| 110 | 119 | method: 'get', |
| 111 | - url: '/api/jt1078/query/send/request/io/history/' + this.data.sim + '/' + this.data.channel + "/" + this.data.startTime + "/" + date + "/" + undefined | |
| 120 | + url: '/api/jt1078/query/send/request/io/history/' + this.data.sim + '/' + this.data.channel + '/' + this.data.startTime + '/' + date + '/' + encodeURIComponent(map) | |
| 112 | 121 | }).then(res => { |
| 113 | 122 | if (res.data && res.data.data && res.data.data.data) { |
| 114 | - let videoUrl1; | |
| 115 | - if (location.protocol === "https:") { | |
| 116 | - videoUrl1 = res.data.data.data.wss_flv; | |
| 117 | - } else { | |
| 118 | - videoUrl1 = res.data.data.data.ws_flv; | |
| 123 | + const d = res.data.data.data | |
| 124 | + const videoUrl1 = location.protocol === 'https:' | |
| 125 | + ? (d.wss_flv || d.ws_flv || d.flv) | |
| 126 | + : (d.ws_flv || d.wss_flv || d.flv) | |
| 127 | + if (videoUrl1) { | |
| 128 | + this.playSessionKey += 1 | |
| 129 | + this.videoUrl = videoUrl1 | |
| 119 | 130 | } |
| 120 | - this.videoUrl = videoUrl1; | |
| 121 | 131 | } else if (res.data.data && res.data.data.msg) { |
| 122 | - this.$message.error(res.data.data.msg); | |
| 132 | + this.$message.error(res.data.data.msg) | |
| 123 | 133 | } else if (res.data.msg) { |
| 124 | - this.$message.error(res.data.msg); | |
| 125 | - } else if (res.msg) { | |
| 126 | - this.$message.error(res.msg); | |
| 134 | + this.$message.error(res.data.msg) | |
| 127 | 135 | } |
| 128 | 136 | }) |
| 129 | 137 | }, |
| 130 | 138 | changeDate(date, status) { |
| 131 | - console.log("选择时间:" + date + " 播放状态:" + status); | |
| 139 | + console.log('选择时间:' + date + ' 播放状态:' + status) | |
| 132 | 140 | }, |
| 133 | - handleClose(){ | |
| 141 | + handleClose() { | |
| 134 | 142 | this.open = false |
| 135 | 143 | } |
| 136 | - }, | |
| 137 | - //生命周期 - 创建完成(可以访问当前this实例)", | |
| 138 | - created() { | |
| 139 | - }, | |
| 140 | - //生命周期 - 挂载完成(可以访问DOM元素)", | |
| 141 | - mounted() { | |
| 142 | - }, | |
| 143 | - beforeCreate() { | |
| 144 | - }, //生命周期 - 创建之前", | |
| 145 | - beforeMount() { | |
| 146 | - }, //生命周期 - 挂载之前", | |
| 147 | - beforeUpdate() { | |
| 148 | - }, //生命周期 - 更新之前", | |
| 149 | - updated() { | |
| 150 | - }, //生命周期 - 更新之后", | |
| 151 | - beforeDestroy() { | |
| 152 | - }, //生命周期 - 销毁之前", | |
| 153 | - destroyed() { | |
| 154 | - }, //生命周期 - 销毁完成", | |
| 155 | - activated() { | |
| 156 | - } //如果页面有keep-alive缓存功能,这个函数会触发", | |
| 157 | -}; | |
| 158 | -</script> | |
| 159 | -<style scoped> | |
| 160 | -.el-header { | |
| 161 | - background-color: #B3C0D1; | |
| 162 | - color: #333; | |
| 163 | - text-align: center; | |
| 164 | - line-height: 60px; | |
| 165 | -} | |
| 166 | - | |
| 167 | -.el-footer { | |
| 168 | - background-color: #B3C0D1; | |
| 169 | - color: #333; | |
| 170 | - text-align: center; | |
| 171 | - line-height: 60px; | |
| 172 | -} | |
| 173 | - | |
| 174 | -.el-aside { | |
| 175 | - background-color: #D3DCE6; | |
| 176 | - color: #333; | |
| 177 | - text-align: center; | |
| 178 | - line-height: 200px; | |
| 179 | - height: 75vh; | |
| 180 | -} | |
| 181 | - | |
| 182 | -.el-main { | |
| 183 | - display: flex; | |
| 184 | - justify-content: center; /* 水平居中 */ | |
| 185 | - align-items: center; /* 垂直居中 */ | |
| 186 | - background-color: #E9EEF3; | |
| 187 | - color: #333; | |
| 188 | - text-align: center; | |
| 189 | - line-height: 160px; | |
| 190 | - height: 80vh; | |
| 191 | -} | |
| 192 | - | |
| 193 | -body > .el-container { | |
| 194 | - margin-bottom: 40px; | |
| 195 | -} | |
| 196 | - | |
| 197 | -.el-container:nth-child(5) .el-aside, | |
| 198 | -.el-container:nth-child(6) .el-aside { | |
| 199 | - line-height: 260px; | |
| 144 | + } | |
| 200 | 145 | } |
| 146 | +</script> | |
| 201 | 147 | |
| 148 | +<style scoped> | |
| 202 | 149 | .main-play { |
| 203 | 150 | width: 100%; |
| 204 | - height: 100%; | |
| 205 | - background-color: black; | |
| 151 | + height: 500px; | |
| 152 | + background-color: #000000; | |
| 206 | 153 | } |
| 207 | 154 | |
| 208 | -.box-card { | |
| 209 | - width: 80%; | |
| 210 | - height: 100%; | |
| 211 | -} | |
| 212 | -/* 在现有样式基础上添加 */ | |
| 213 | -.history-dialog-center { | |
| 214 | - display: flex; | |
| 215 | - justify-content: center; | |
| 216 | - align-items: center; | |
| 217 | - position: fixed; | |
| 218 | - top: 0; | |
| 219 | - left: 0; | |
| 155 | +.hist-jess-player { | |
| 220 | 156 | width: 100%; |
| 221 | 157 | height: 100%; |
| 222 | - margin: 0 !important; | |
| 223 | -} | |
| 224 | - | |
| 225 | -.history-dialog-center .el-dialog { | |
| 226 | - margin: 0 auto !important; | |
| 227 | - max-height: 90vh; | |
| 228 | - display: flex; | |
| 229 | - flex-direction: column; | |
| 230 | 158 | } |
| 231 | 159 | |
| 232 | -.history-dialog-center .el-dialog__body { | |
| 233 | - flex: 1; | |
| 234 | - overflow-y: auto; | |
| 160 | +.box-card { | |
| 161 | + width: 100%; | |
| 162 | + max-width: 1280px; | |
| 235 | 163 | } |
| 236 | - | |
| 237 | 164 | </style> | ... | ... |
web_src/src/components/JT1078Components/HistorySearchTable.vue
| 1 | 1 | <template> |
| 2 | - <div style="width: 100%;height: 100%;"> | |
| 2 | + <div class="history-table-wrap"> | |
| 3 | 3 | <el-table |
| 4 | 4 | v-if="tableData.length > 0" |
| 5 | 5 | ref="singleTable" |
| 6 | 6 | :data="tableData" |
| 7 | 7 | :header-cell-style="{ textAlign: 'center' ,height:'100%',lineHeight:'100%' }" |
| 8 | 8 | border |
| 9 | - height="calc(100% - 100px)" | |
| 9 | + :height="tableHeightNum" | |
| 10 | 10 | highlight-current-row |
| 11 | 11 | @current-change="handleCurrentChange" |
| 12 | 12 | style="width: 100%;text-align: center"> |
| ... | ... | @@ -55,14 +55,31 @@ |
| 55 | 55 | label="操作"> |
| 56 | 56 | <template slot-scope="scope"> |
| 57 | 57 | <el-button icon="el-icon-video-play" size="small" style="border: none" @click="playHistoryVideo(scope.row)"></el-button> |
| 58 | - <el-button v-if="scope.row.status === '0'" icon="el-icon-upload" size="small" style="border: none" @click="uploadHistoryVideo(scope.row)"></el-button> | |
| 59 | - <el-button v-if="scope.row.status === '3'" | |
| 60 | - icon="el-icon-download" | |
| 61 | - :disabled="scope.row.disabled" | |
| 62 | - size="small" style="border: none" | |
| 63 | - @click="downloadHistoryVideo(scope.row)"> | |
| 64 | - {{ scope.row.disabled ? `${scope.row.countdown}s` : '' }} | |
| 65 | - </el-button> | |
| 58 | + <el-tooltip :disabled="!isChannelBusy(scope.row)" content="该通道正在上传或下载" placement="top"> | |
| 59 | + <span> | |
| 60 | + <el-button | |
| 61 | + v-if="scope.row.status === '0'" | |
| 62 | + icon="el-icon-upload" | |
| 63 | + size="small" | |
| 64 | + style="border: none" | |
| 65 | + :disabled="isChannelBusy(scope.row)" | |
| 66 | + @click="uploadHistoryVideo(scope.row)" | |
| 67 | + /> | |
| 68 | + </span> | |
| 69 | + </el-tooltip> | |
| 70 | + <el-tooltip :disabled="!isChannelBusy(scope.row)" content="该通道正在上传或下载" placement="top"> | |
| 71 | + <span> | |
| 72 | + <el-button | |
| 73 | + v-if="scope.row.status === '3'" | |
| 74 | + icon="el-icon-download" | |
| 75 | + :disabled="scope.row.disabled || isChannelBusy(scope.row)" | |
| 76 | + size="small" | |
| 77 | + style="border: none" | |
| 78 | + @click="downloadHistoryVideo(scope.row)"> | |
| 79 | + {{ scope.row.disabled ? `${scope.row.countdown}s` : '' }} | |
| 80 | + </el-button> | |
| 81 | + </span> | |
| 82 | + </el-tooltip> | |
| 66 | 83 | </template> |
| 67 | 84 | </el-table-column> |
| 68 | 85 | </el-table> |
| ... | ... | @@ -71,69 +88,78 @@ |
| 71 | 88 | </template> |
| 72 | 89 | |
| 73 | 90 | <script> |
| 74 | -//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等), | |
| 75 | -//例如:import 《组件名称》 from '《组件路径》, | |
| 76 | 91 | export default { |
| 77 | - //import引入的组件需要注入到对象中才能使用" | |
| 78 | 92 | name: "historySearchTable", |
| 79 | 93 | components: {}, |
| 80 | 94 | props: { |
| 81 | 95 | tableData: { |
| 82 | 96 | type: Array, |
| 83 | - default: [] | |
| 97 | + default: () => [] | |
| 98 | + }, | |
| 99 | + /** 像素高度;el-table 需要数值高度才能出现纵向滚动条 */ | |
| 100 | + tableHeight: { | |
| 101 | + type: Number, | |
| 102 | + default: 400 | |
| 103 | + }, | |
| 104 | + busyChannelKeys: { | |
| 105 | + type: Array, | |
| 106 | + default: () => [] | |
| 107 | + } | |
| 108 | + }, | |
| 109 | + computed: { | |
| 110 | + tableHeightNum() { | |
| 111 | + const h = Number(this.tableHeight) | |
| 112 | + return h > 120 ? h : 400 | |
| 84 | 113 | } |
| 85 | 114 | }, |
| 86 | 115 | data() { |
| 87 | - //这里存放数据" | |
| 88 | 116 | return { |
| 89 | 117 | currentRow: null, |
| 90 | 118 | historyTimer: null, |
| 91 | - countdown: 10, // 假设倒计时为10秒 | |
| 119 | + countdown: 10, | |
| 92 | 120 | disabled: false |
| 93 | 121 | }; |
| 94 | 122 | }, |
| 95 | - //计算属性 类似于data概念", | |
| 96 | - computed: {}, | |
| 97 | - //监控data中的数据变化", | |
| 98 | - watch: {}, | |
| 99 | - //方法集合", | |
| 100 | 123 | methods: { |
| 101 | - /** | |
| 102 | - * 选中行 | |
| 103 | - * @param val | |
| 104 | - */ | |
| 124 | + channelKey(row) { | |
| 125 | + if (!row || row.sim == null || row.channel == null) return '' | |
| 126 | + return String(row.sim) + '_' + String(row.channel) | |
| 127 | + }, | |
| 128 | + isChannelBusy(row) { | |
| 129 | + const k = this.channelKey(row) | |
| 130 | + if (!k) return false | |
| 131 | + return this.busyChannelKeys.indexOf(k) >= 0 | |
| 132 | + }, | |
| 105 | 133 | handleCurrentChange(val) { |
| 106 | 134 | this.currentRow = val; |
| 107 | 135 | }, |
| 108 | 136 | setCurrent(row) { |
| 109 | 137 | this.$refs.singleTable.setCurrentRow(row); |
| 110 | 138 | }, |
| 111 | - /** | |
| 112 | - * 播放历史视频 | |
| 113 | - * @param row | |
| 114 | - */ | |
| 115 | 139 | playHistoryVideo(row){ |
| 116 | 140 | this.$emit("playHistoryVideo", row); |
| 117 | 141 | }, |
| 118 | - /** | |
| 119 | - * 上传历史视频 | |
| 120 | - * @param row | |
| 121 | - */ | |
| 122 | 142 | uploadHistoryVideo(row){ |
| 143 | + if (this.isChannelBusy(row)) { | |
| 144 | + this.$message.warning('该通道正在上传或下载,请稍候') | |
| 145 | + return | |
| 146 | + } | |
| 123 | 147 | this.$emit('uploadHistoryVideo', row); |
| 124 | 148 | }, |
| 125 | - /** | |
| 126 | - * 下载历史视频 | |
| 127 | - * @param row | |
| 128 | - */ | |
| 129 | 149 | downloadHistoryVideo(data){ |
| 130 | 150 | if (data.disabled) return; |
| 151 | + if (this.isChannelBusy(data)) { | |
| 152 | + this.$message.warning('该通道正在上传或下载,请稍候') | |
| 153 | + return | |
| 154 | + } | |
| 131 | 155 | if (data.historyTimer){ |
| 132 | 156 | this.$message.warning("点击太频繁,请稍后重试") |
| 133 | 157 | return; |
| 134 | 158 | } |
| 159 | + const opKey = this.channelKey(data) | |
| 160 | + this.$emit('channel-operation', { key: opKey, busy: true }) | |
| 135 | 161 | data.disabled = true; |
| 136 | - data.countdown = 10; // 重置倒计时 | |
| 162 | + data.countdown = 10; | |
| 137 | 163 | data.isCountingDown = true; |
| 138 | 164 | const timer = setInterval(() => { |
| 139 | 165 | if (data.countdown > 0) { |
| ... | ... | @@ -141,66 +167,51 @@ |
| 141 | 167 | } else { |
| 142 | 168 | clearInterval(timer); |
| 143 | 169 | data.disabled = false; |
| 144 | - data.countdown = 10; // 重置倒计时 | |
| 170 | + data.countdown = 10; | |
| 145 | 171 | data.isCountingDown = false; |
| 146 | 172 | } |
| 147 | 173 | }, 1000); |
| 148 | - data.historyTimer = timer; // 保存计时器引用 | |
| 149 | - /** | |
| 150 | - * 下载历史视频 | |
| 151 | - */ | |
| 174 | + data.historyTimer = timer; | |
| 152 | 175 | this.$axios({ |
| 153 | - url: `/api/jt1078/query/history/download/${data.name}`, // 请求URL | |
| 154 | - method: 'GET', // 将FTP文件路径作为参数传递 | |
| 155 | - responseType: 'blob', // 告知axios以二进制数据流的形式处理响应 | |
| 176 | + url: `/api/jt1078/query/history/download/${data.name}`, | |
| 177 | + method: 'GET', | |
| 178 | + responseType: 'blob', | |
| 156 | 179 | }).then((response) => { |
| 157 | 180 | const blob = new Blob([response.data], { type: 'application/octet-stream' }); |
| 158 | 181 | const link = document.createElement('a'); |
| 159 | 182 | link.href = window.URL.createObjectURL(blob); |
| 160 | - link.download = data.name + ".mp4"; // 设置下载文件名 | |
| 183 | + link.download = data.name + ".mp4"; | |
| 161 | 184 | document.body.appendChild(link); |
| 162 | 185 | link.click(); |
| 163 | 186 | document.body.removeChild(link); |
| 164 | 187 | clearInterval(timer); |
| 165 | 188 | data.disabled = false; |
| 166 | - data.countdown = 10; // 重置倒计时 | |
| 189 | + data.countdown = 10; | |
| 167 | 190 | data.isCountingDown = false; |
| 168 | 191 | data.historyTimer = null; |
| 169 | 192 | this.$message.success(`${link.download} 下载成功`); |
| 170 | 193 | }).catch(error => { |
| 171 | 194 | clearInterval(timer); |
| 172 | 195 | data.disabled = false; |
| 173 | - data.countdown = 10; // 重置倒计时 | |
| 196 | + data.countdown = 10; | |
| 174 | 197 | data.isCountingDown = false; |
| 175 | 198 | data.historyTimer = null; |
| 176 | 199 | this.$message.error(`${data.name} 下载失败`); |
| 177 | 200 | console.error("下载失败", error); |
| 201 | + }).finally(() => { | |
| 202 | + this.$emit('channel-operation', { key: opKey, busy: false }) | |
| 178 | 203 | }); |
| 179 | 204 | } |
| 180 | 205 | }, |
| 181 | - //生命周期 - 创建完成(可以访问当前this实例)", | |
| 182 | - created() { | |
| 183 | - }, | |
| 184 | - //生命周期 - 挂载完成(可以访问DOM元素)", | |
| 185 | - mounted() { | |
| 186 | - }, | |
| 187 | - beforeCreate() { | |
| 188 | - }, //生命周期 - 创建之前", | |
| 189 | - beforeMount() { | |
| 190 | - }, //生命周期 - 挂载之前", | |
| 191 | - beforeUpdate() { | |
| 192 | - }, //生命周期 - 更新之前", | |
| 193 | - updated() { | |
| 194 | - }, //生命周期 - 更新之后", | |
| 195 | 206 | beforeDestroy() { |
| 196 | 207 | clearInterval(this.historyTimer) |
| 197 | - }, //生命周期 - 销毁之前", | |
| 198 | - destroyed() { | |
| 199 | - }, //生命周期 - 销毁完成", | |
| 200 | - activated() { | |
| 201 | - } //如果页面有keep-alive缓存功能,这个函数会触发", | |
| 208 | + }, | |
| 202 | 209 | }; |
| 203 | 210 | </script> |
| 204 | 211 | <style scoped> |
| 205 | - | |
| 212 | +.history-table-wrap { | |
| 213 | + width: 100%; | |
| 214 | + height: 100%; | |
| 215 | + min-height: 0; | |
| 216 | +} | |
| 206 | 217 | </style> | ... | ... |
web_src/src/components/JT1078Components/deviceList/VehicleList.vue
| ... | ... | @@ -35,7 +35,7 @@ |
| 35 | 35 | @node-collapse="handleNodeCollapse" |
| 36 | 36 | style="margin-top: 10px" |
| 37 | 37 | > |
| 38 | - <span class="custom-tree-node" slot-scope="{ node, data }"> | |
| 38 | + <span class="custom-tree-node" slot-scope="{ node, data }" @dblclick.stop="onTreeNodeDblclick(data, node)"> | |
| 39 | 39 | <el-tooltip :disabled="data.abnormalStatus === undefined" |
| 40 | 40 | :visible="tooltipVisible && hoveredNode === data" placement="right"> |
| 41 | 41 | <div slot="content" style="line-height: 23px"> |
| ... | ... | @@ -145,6 +145,10 @@ export default { |
| 145 | 145 | this.$emit('node-click', data, node) |
| 146 | 146 | }, |
| 147 | 147 | |
| 148 | + onTreeNodeDblclick(data, node) { | |
| 149 | + this.$emit('node-dblclick', data, node) | |
| 150 | + }, | |
| 151 | + | |
| 148 | 152 | nodeContextmenu(event, data, node, fun) { |
| 149 | 153 | this.$emit('node-contextmenu', event, data, node); |
| 150 | 154 | }, |
| ... | ... | @@ -168,7 +172,7 @@ export default { |
| 168 | 172 | // 只有在启用测试SIM模式时才应用测试逻辑 |
| 169 | 173 | if (this.enableTestSim) { |
| 170 | 174 | //, '39045172840',39045172800 |
| 171 | - const fixedSims = ['40028816490']; | |
| 175 | + const fixedSims = ['40028816490','103071510']; | |
| 172 | 176 | const toggleSim = ''; |
| 173 | 177 | |
| 174 | 178 | // 计算当前时间处于哪个10分钟区间,用于实现 toggleSim 的状态切换 | ... | ... |
web_src/src/components/common/EasyPlayer.vue
| ... | ... | @@ -5,6 +5,7 @@ |
| 5 | 5 | @click="onPlayerClick" |
| 6 | 6 | @mousemove="onMouseMove" |
| 7 | 7 | @mouseleave="onMouseLeave" |
| 8 | + @contextmenu="onContextMenuWrapper" | |
| 8 | 9 | > |
| 9 | 10 | <div class="custom-top-bar" :class="{ 'hide-bar': !showControls }"> |
| 10 | 11 | <div class="top-bar-left"> |
| ... | ... | @@ -18,6 +19,21 @@ |
| 18 | 19 | </div> |
| 19 | 20 | |
| 20 | 21 | <div :id="uniqueId" ref="container" class="player-box"></div> |
| 22 | + | |
| 23 | + <!-- 自定义右键菜单 - 全屏时附加到 body --> | |
| 24 | + <div | |
| 25 | + v-if="showContextMenu" | |
| 26 | + ref="contextMenu" | |
| 27 | + class="custom-contextmenu" | |
| 28 | + :class="{ 'menu-fixed': isFullscreen }" | |
| 29 | + :style="{ top: contextMenuY + 'px', left: contextMenuX + 'px' }" | |
| 30 | + @click.stop | |
| 31 | + > | |
| 32 | + <div class="contextmenu-item" @click.stop="onShowVideoInfo">视频信息</div> | |
| 33 | + <div class="contextmenu-divider"></div> | |
| 34 | + <div class="contextmenu-item" @click.stop="onSwitchMainStream">主码流</div> | |
| 35 | + <div class="contextmenu-item" @click.stop="onSwitchSubStream">子码流</div> | |
| 36 | + </div> | |
| 21 | 37 | </div> |
| 22 | 38 | </template> |
| 23 | 39 | |
| ... | ... | @@ -28,7 +44,10 @@ export default { |
| 28 | 44 | initialPlayUrl: { type: String, default: '' }, |
| 29 | 45 | isResize: { type: Boolean, default: true }, |
| 30 | 46 | videoTitle: { type: String, default: '' }, |
| 31 | - hasAudio: { type: Boolean, default: false } | |
| 47 | + hasAudio: { type: Boolean, default: false }, | |
| 48 | + // 码流切换所需参数 | |
| 49 | + sim: { type: String, default: '' }, | |
| 50 | + channel: { type: [String, Number], default: '' } | |
| 32 | 51 | }, |
| 33 | 52 | data() { |
| 34 | 53 | return { |
| ... | ... | @@ -38,6 +57,15 @@ export default { |
| 38 | 57 | hasStarted: false, |
| 39 | 58 | showControls: false, |
| 40 | 59 | controlTimer: null, |
| 60 | + isFullscreen: false, // 全屏状态 | |
| 61 | + | |
| 62 | + // 自定义右键菜单 | |
| 63 | + showContextMenu: false, | |
| 64 | + contextMenuX: 0, | |
| 65 | + contextMenuY: 0, | |
| 66 | + contextMenuHandler: null, // 保存右键菜单监听器函数 | |
| 67 | + docContextMenuHandler: null, // 保存文档级右键菜单监听器函数 | |
| 68 | + containerContextMenuHandler: null, // 保存容器级右键菜单监听器函数 | |
| 41 | 69 | |
| 42 | 70 | // 【配置控制表】 |
| 43 | 71 | controlsConfig: { |
| ... | ... | @@ -94,10 +122,77 @@ export default { |
| 94 | 122 | } |
| 95 | 123 | }, 500); |
| 96 | 124 | }); |
| 125 | + // 添加全局点击事件监听,用于隐藏右键菜单 | |
| 126 | + document.addEventListener('click', this.hideContextMenu); | |
| 127 | + // 添加全屏变化监听 | |
| 128 | + document.addEventListener('fullscreenchange', this.onFullscreenChange); | |
| 129 | + document.addEventListener('webkitfullscreenchange', this.onFullscreenChange); | |
| 130 | + // 在 document 级别使用 capture 模式拦截 contextmenu 事件 | |
| 131 | + this.docContextMenuHandler = (e) => { | |
| 132 | + // 检查播放器是否在播放 | |
| 133 | + if (!this.hasStarted) { | |
| 134 | + return; | |
| 135 | + } | |
| 136 | + | |
| 137 | + // 优先使用 this.isFullscreen(通过 onFullscreenChange 更新的),因为 EasyPlayerPro 可能不会更新 document.fullscreenElement | |
| 138 | + const isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || this.isFullscreen); | |
| 139 | + console.log('Contextmenu event - isFullscreen:', isFullscreen, 'docFullscreen:', !!(document.fullscreenElement || document.webkitFullscreenElement), 'this.isFullscreen:', this.isFullscreen, 'target:', e.target); | |
| 140 | + | |
| 141 | + // 全屏模式下,使用 DOM 遍历方式检测 | |
| 142 | + if (isFullscreen) { | |
| 143 | + console.log('Entering fullscreen branch'); | |
| 144 | + let target = e.target; | |
| 145 | + | |
| 146 | + // 简化检测:直接检查点击目标是否在当前播放器元素内 | |
| 147 | + const isInPlayer = this.$el.contains(target); | |
| 148 | + console.log('isInPlayer (this.$el.contains):', isInPlayer, 'target:', target, 'this.$el:', this.$el); | |
| 149 | + | |
| 150 | + if (isInPlayer) { | |
| 151 | + e.preventDefault(); | |
| 152 | + setTimeout(() => { | |
| 153 | + this.hideNativeContextMenu(); | |
| 154 | + this.onContextMenu(e); | |
| 155 | + }, 10); | |
| 156 | + } | |
| 157 | + return; | |
| 158 | + } | |
| 159 | + | |
| 160 | + // 非全屏模式下,使用坐标检测 | |
| 161 | + const rect = this.$el.getBoundingClientRect(); | |
| 162 | + const isClickInPlayer = ( | |
| 163 | + e.clientX >= rect.left && | |
| 164 | + e.clientX <= rect.right && | |
| 165 | + e.clientY >= rect.top && | |
| 166 | + e.clientY <= rect.bottom | |
| 167 | + ); | |
| 168 | + | |
| 169 | + // 如果点击不在当前播放器内,不处理 | |
| 170 | + if (!isClickInPlayer) { | |
| 171 | + return; | |
| 172 | + } | |
| 173 | + | |
| 174 | + // 点击在当前播放器内,阻止原生菜单并显示自定义菜单 | |
| 175 | + e.preventDefault(); | |
| 176 | + // 延迟一点执行,确保我们的菜单能显示 | |
| 177 | + setTimeout(() => { | |
| 178 | + this.hideNativeContextMenu(); | |
| 179 | + this.onContextMenu(e); | |
| 180 | + }, 10); | |
| 181 | + }; | |
| 182 | + document.addEventListener('contextmenu', this.docContextMenuHandler, true); | |
| 97 | 183 | }, |
| 98 | 184 | beforeDestroy() { |
| 99 | 185 | this.destroy(); |
| 100 | 186 | if (this.controlTimer) clearTimeout(this.controlTimer); |
| 187 | + // 移除全局点击事件监听 | |
| 188 | + document.removeEventListener('click', this.hideContextMenu); | |
| 189 | + // 移除全屏变化监听 | |
| 190 | + document.removeEventListener('fullscreenchange', this.onFullscreenChange); | |
| 191 | + document.removeEventListener('webkitfullscreenchange', this.onFullscreenChange); | |
| 192 | + // 移除 document 级别的 contextmenu 监听 | |
| 193 | + if (this.docContextMenuHandler) { | |
| 194 | + document.removeEventListener('contextmenu', this.docContextMenuHandler, true); | |
| 195 | + } | |
| 101 | 196 | }, |
| 102 | 197 | methods: { |
| 103 | 198 | onMouseMove() { |
| ... | ... | @@ -158,6 +253,32 @@ export default { |
| 158 | 253 | console.warn('Player Error:', err); |
| 159 | 254 | }); |
| 160 | 255 | |
| 256 | + // 监听播放器的 contextmenu 事件,防止显示原生菜单 | |
| 257 | + this.playerInstance.on('contextmenu', (e) => { | |
| 258 | + e.preventDefault(); | |
| 259 | + }); | |
| 260 | + | |
| 261 | + // 添加原生 contextmenu 监听器到容器元素和 wrapper 元素,作为 document 级监听器的备份 | |
| 262 | + // 在 fullscreen 模式下,事件可能不会冒泡到 document | |
| 263 | + this.containerContextMenuHandler = (e) => { | |
| 264 | + console.log('Container contextmenu fired (native listener), hasStarted:', this.hasStarted); | |
| 265 | + if (!this.hasStarted) { | |
| 266 | + console.log('hasStarted check failed'); | |
| 267 | + return; | |
| 268 | + } | |
| 269 | + e.preventDefault(); | |
| 270 | + console.log('About to call onContextMenu'); | |
| 271 | + this.hideNativeContextMenu(); | |
| 272 | + this.onContextMenu(e); | |
| 273 | + console.log('onContextMenu called, showContextMenu should be:', this.showContextMenu); | |
| 274 | + }; | |
| 275 | + container.addEventListener('contextmenu', this.containerContextMenuHandler, true); | |
| 276 | + // 也在 wrapper (this.$el) 上添加监听,fullscreen 时 this.$el 可能是全屏元素 | |
| 277 | + this.$el.addEventListener('contextmenu', this.containerContextMenuHandler, true); | |
| 278 | + | |
| 279 | + // 注意:document 级别的 contextmenu 监听器已在 mounted 中添加,这里不需要重复添加 | |
| 280 | + | |
| 281 | + | |
| 161 | 282 | } catch (e) { |
| 162 | 283 | console.error("Create Error:", e); |
| 163 | 284 | } |
| ... | ... | @@ -179,6 +300,17 @@ export default { |
| 179 | 300 | destroy() { |
| 180 | 301 | this.hasStarted = false; |
| 181 | 302 | this.showControls = false; |
| 303 | + // 移除 document 级别的 contextmenu 监听 | |
| 304 | + if (this.docContextMenuHandler) { | |
| 305 | + document.removeEventListener('contextmenu', this.docContextMenuHandler, true); | |
| 306 | + this.docContextMenuHandler = null; | |
| 307 | + } | |
| 308 | + // 移除容器级别的 contextmenu 监听 | |
| 309 | + if (this.containerContextMenuHandler && this.$refs.container) { | |
| 310 | + this.$refs.container.removeEventListener('contextmenu', this.containerContextMenuHandler, true); | |
| 311 | + this.$el.removeEventListener('contextmenu', this.containerContextMenuHandler, true); | |
| 312 | + this.containerContextMenuHandler = null; | |
| 313 | + } | |
| 182 | 314 | if (this.playerInstance) { |
| 183 | 315 | this.playerInstance.destroy(); |
| 184 | 316 | this.playerInstance = null; |
| ... | ... | @@ -197,8 +329,328 @@ export default { |
| 197 | 329 | this.$emit('click'); |
| 198 | 330 | }, |
| 199 | 331 | |
| 332 | + // 右键菜单处理 - 直接在 wrapper 上拦截 | |
| 333 | + onContextMenuWrapper(e) { | |
| 334 | + // 阻止事件冒泡到 EasyPlayerPro | |
| 335 | + e.stopPropagation(); | |
| 336 | + // document 级别的监听器会处理阻止默认行为 | |
| 337 | + }, | |
| 338 | + | |
| 339 | + // 右键菜单 - 用于 capture 模式拦截 | |
| 340 | + onContextMenuCapture(e) { | |
| 341 | + // 阻止原生右键菜单 | |
| 342 | + e.preventDefault(); | |
| 343 | + e.stopPropagation(); | |
| 344 | + | |
| 345 | + // 调用正常的右键菜单处理 | |
| 346 | + this.onContextMenu(e); | |
| 347 | + }, | |
| 348 | + | |
| 349 | + // 右键菜单 | |
| 350 | + onContextMenu(e) { | |
| 351 | + console.log('onContextMenu called, hasStarted:', this.hasStarted); | |
| 352 | + // 只有在播放器已启动后才显示菜单 | |
| 353 | + if (!this.hasStarted) return; | |
| 354 | + | |
| 355 | + // 计算菜单位置,确保不超出播放器边界 | |
| 356 | + const menuWidth = 150; | |
| 357 | + const menuHeight = 140; | |
| 358 | + | |
| 359 | + // 检查是否是全屏模式 | |
| 360 | + const isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || this.isFullscreen); | |
| 361 | + | |
| 362 | + // 全屏模式:使用 viewport 坐标 | |
| 363 | + // 非全屏模式:使用播放器容器坐标 | |
| 364 | + let x, y; | |
| 365 | + if (isFs) { | |
| 366 | + // 全屏时使用绝对坐标 | |
| 367 | + x = e.clientX; | |
| 368 | + y = e.clientY; | |
| 369 | + console.log('Fullscreen mode - using viewport coords:', x, y); | |
| 370 | + } else { | |
| 371 | + // 非全屏时使用相对于 player-wrapper 的坐标 | |
| 372 | + const containerRect = this.$el.getBoundingClientRect(); | |
| 373 | + x = e.clientX - containerRect.left; | |
| 374 | + y = e.clientY - containerRect.top; | |
| 375 | + console.log('Normal mode - using relative coords:', x, y, 'containerRect:', containerRect); | |
| 376 | + } | |
| 377 | + | |
| 378 | + // 边界检测 | |
| 379 | + const maxX = isFs ? window.innerWidth : (isFs ? window.innerWidth : this.$el.getBoundingClientRect().width); | |
| 380 | + const maxY = isFs ? window.innerHeight : (isFs ? window.innerHeight : this.$el.getBoundingClientRect().height); | |
| 381 | + | |
| 382 | + if (x + menuWidth > maxX) { | |
| 383 | + x = maxX - menuWidth; | |
| 384 | + } | |
| 385 | + if (y + menuHeight > maxY) { | |
| 386 | + y = maxY - menuHeight; | |
| 387 | + } | |
| 388 | + | |
| 389 | + // 确保最小值 | |
| 390 | + x = Math.max(0, x); | |
| 391 | + y = Math.max(0, y); | |
| 392 | + | |
| 393 | + this.contextMenuX = x; | |
| 394 | + this.contextMenuY = y; | |
| 395 | + console.log('Setting showContextMenu=true, x:', x, 'y:', y); | |
| 396 | + | |
| 397 | + // 阻止事件冒泡 | |
| 398 | + e.stopPropagation(); | |
| 399 | + | |
| 400 | + // 先隐藏原生菜单(多次调用确保隐藏) | |
| 401 | + this.hideNativeContextMenu(); | |
| 402 | + | |
| 403 | + this.showContextMenu = true; | |
| 404 | + this.$forceUpdate(); | |
| 405 | + console.log('showContextMenu is now:', this.showContextMenu); | |
| 406 | + | |
| 407 | + // 全屏时将菜单附加到 body | |
| 408 | + if (isFs && this.$refs.contextMenu) { | |
| 409 | + console.log('Appending menu to body for fullscreen'); | |
| 410 | + document.body.appendChild(this.$refs.contextMenu); | |
| 411 | + // 强制设置 position: fixed 和 top/left | |
| 412 | + this.$refs.contextMenu.style.position = 'fixed'; | |
| 413 | + this.$refs.contextMenu.style.top = y + 'px'; | |
| 414 | + this.$refs.contextMenu.style.left = x + 'px'; | |
| 415 | + } | |
| 416 | + | |
| 417 | + // 检查 DOM | |
| 418 | + this.$nextTick(() => { | |
| 419 | + const menu = this.$refs.contextMenu || this.$el.querySelector('.custom-contextmenu'); | |
| 420 | + console.log('DOM check - menu exists:', !!menu, 'parent:', menu ? menu.parentElement : 'N/A'); | |
| 421 | + console.log('isFullscreen data property:', this.isFullscreen); | |
| 422 | + console.log('menu-fixed class applied:', menu ? menu.classList.contains('menu-fixed') : 'N/A'); | |
| 423 | + if (menu) { | |
| 424 | + console.log('Menu computed position:', getComputedStyle(menu).position); | |
| 425 | + console.log('Menu rect:', menu.getBoundingClientRect(), 'viewport:', window.innerWidth, window.innerHeight); | |
| 426 | + // 检查原生菜单状态 | |
| 427 | + const nativeMenus = document.querySelectorAll('[class*="easyplayer-contextmenu"]'); | |
| 428 | + console.log('Native menus count:', nativeMenus.length); | |
| 429 | + | |
| 430 | + // 检查是否有其他元素覆盖在菜单上面 | |
| 431 | + const elemBelow = document.elementFromPoint(x, y); | |
| 432 | + console.log('Element at menu position (x,y):', x, y, 'is:', elemBelow); | |
| 433 | + | |
| 434 | + // 使用 requestAnimationFrame 确保菜单在原生菜单之后渲染 | |
| 435 | + requestAnimationFrame(() => { | |
| 436 | + requestAnimationFrame(() => { | |
| 437 | + this.hideNativeContextMenu(); | |
| 438 | + // 强制确保菜单可见 | |
| 439 | + menu.style.setProperty('display', 'block', 'important'); | |
| 440 | + menu.style.setProperty('visibility', 'visible', 'important'); | |
| 441 | + menu.style.setProperty('opacity', '1', 'important'); | |
| 442 | + console.log('After rAF forcing styles - display:', getComputedStyle(menu).display, 'visibility:', getComputedStyle(menu).visibility, 'opacity:', getComputedStyle(menu).opacity, 'position:', getComputedStyle(menu).position); | |
| 443 | + // 再次检查覆盖元素 | |
| 444 | + const elemBelowAfter = document.elementFromPoint(x, y); | |
| 445 | + console.log('Element at menu position AFTER forcing:', x, y, 'is:', elemBelowAfter); | |
| 446 | + }); | |
| 447 | + }); | |
| 448 | + } | |
| 449 | + }); | |
| 450 | + }, | |
| 451 | + | |
| 452 | + // 隐藏右键菜单 | |
| 453 | + hideContextMenu() { | |
| 454 | + // 如果菜单被附加到 body,将其移回 player-wrapper | |
| 455 | + if (this.$refs.contextMenu && this.$refs.contextMenu.parentElement !== this.$el) { | |
| 456 | + this.$el.appendChild(this.$refs.contextMenu); | |
| 457 | + } | |
| 458 | + this.showContextMenu = false; | |
| 459 | + // 同时隐藏原生菜单 | |
| 460 | + this.hideNativeContextMenu(); | |
| 461 | + }, | |
| 462 | + | |
| 463 | + // 隐藏原生右键菜单 | |
| 464 | + hideNativeContextMenu() { | |
| 465 | + const menus = document.querySelectorAll('[class*="easyplayer-contextmenu"]'); | |
| 466 | + menus.forEach(menu => { | |
| 467 | + if (!menu.classList.contains('custom-contextmenu')) { | |
| 468 | + menu.style.display = 'none'; | |
| 469 | + } | |
| 470 | + }); | |
| 471 | + }, | |
| 472 | + | |
| 473 | + // 全屏状态变化处理 | |
| 474 | + onFullscreenChange() { | |
| 475 | + this.isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement); | |
| 476 | + // 全屏时隐藏右键菜单 | |
| 477 | + if (!this.isFullscreen) { | |
| 478 | + this.hideContextMenu(); | |
| 479 | + } | |
| 480 | + }, | |
| 481 | + | |
| 482 | + // 视频信息 | |
| 483 | + onShowVideoInfo() { | |
| 484 | + const instance = this.playerInstance; | |
| 485 | + | |
| 486 | + // 方法1: 直接调用 | |
| 487 | + if (instance && typeof instance.showVideoInfo === 'function') { | |
| 488 | + instance.showVideoInfo(); | |
| 489 | + this.hideContextMenu(); | |
| 490 | + return; | |
| 491 | + } | |
| 492 | + | |
| 493 | + // 方法2: 通过 player 调用 | |
| 494 | + if (instance && instance.player && typeof instance.player.showVideoInfo === 'function') { | |
| 495 | + instance.player.showVideoInfo(); | |
| 496 | + this.hideContextMenu(); | |
| 497 | + return; | |
| 498 | + } | |
| 499 | + | |
| 500 | + // 方法3: 调用 _videoinfo 方法 | |
| 501 | + if (instance && typeof instance._videoinfo === 'function') { | |
| 502 | + instance._videoinfo(); | |
| 503 | + this.hideContextMenu(); | |
| 504 | + return; | |
| 505 | + } | |
| 506 | + | |
| 507 | + // 方法4: 尝试通过 events 触发 | |
| 508 | + if (instance && instance.events && instance.events.emit) { | |
| 509 | + instance.events.emit('showVideoInfo'); | |
| 510 | + this.hideContextMenu(); | |
| 511 | + return; | |
| 512 | + } | |
| 513 | + | |
| 514 | + // 方法5: 触发原生菜单中的视频信息按钮 | |
| 515 | + // 先隐藏我们的自定义菜单,然后让原生菜单显示,再点击其视频信息按钮 | |
| 516 | + const customMenu = this.$el.querySelector('.custom-contextmenu'); | |
| 517 | + if (customMenu) { | |
| 518 | + customMenu.style.display = 'none'; | |
| 519 | + } | |
| 520 | + | |
| 521 | + // 强制显示原生菜单(移除我们的隐藏样式) | |
| 522 | + const allMenus = document.querySelectorAll('.easyplayer-contextmenu-btn'); | |
| 523 | + allMenus.forEach(menu => { | |
| 524 | + menu.style.display = ''; | |
| 525 | + menu.style.opacity = ''; | |
| 526 | + menu.style.visibility = ''; | |
| 527 | + }); | |
| 528 | + | |
| 529 | + // 延迟一点让原生菜单显示 | |
| 530 | + setTimeout(() => { | |
| 531 | + // 在当前播放器容器内查找原生菜单 | |
| 532 | + const container = this.$refs.container; | |
| 533 | + let infoBtn = null; | |
| 534 | + | |
| 535 | + if (container) { | |
| 536 | + const nativeMenu = container.querySelector('.easyplayer-contextmenu-btn'); | |
| 537 | + if (nativeMenu) { | |
| 538 | + infoBtn = nativeMenu.querySelector('.easyplayer-contextmenu-info'); | |
| 539 | + } | |
| 540 | + } | |
| 541 | + | |
| 542 | + // 如果没找到,尝试基于位置匹配 | |
| 543 | + if (!infoBtn) { | |
| 544 | + const playerRect = this.$el.getBoundingClientRect(); | |
| 545 | + for (let i = 0; i < allMenus.length; i++) { | |
| 546 | + const menu = allMenus[i]; | |
| 547 | + const menuRect = menu.getBoundingClientRect(); | |
| 548 | + const isOverlapping = | |
| 549 | + Math.abs(menuRect.left - playerRect.left) < 100 && | |
| 550 | + Math.abs(menuRect.top - playerRect.top) < 100; | |
| 551 | + if (isOverlapping) { | |
| 552 | + infoBtn = menu.querySelector('.easyplayer-contextmenu-info'); | |
| 553 | + break; | |
| 554 | + } | |
| 555 | + } | |
| 556 | + } | |
| 557 | + | |
| 558 | + if (infoBtn) { | |
| 559 | + infoBtn.click(); | |
| 560 | + } | |
| 561 | + | |
| 562 | + // 隐藏原生菜单 | |
| 563 | + allMenus.forEach(menu => { | |
| 564 | + menu.style.display = 'none'; | |
| 565 | + }); | |
| 566 | + }, 50); | |
| 567 | + }, | |
| 568 | + | |
| 569 | + // 切换主码流 | |
| 570 | + onSwitchMainStream() { | |
| 571 | + this.hideContextMenu(); | |
| 572 | + this.switchStream(0); | |
| 573 | + }, | |
| 574 | + | |
| 575 | + // 切换子码流 | |
| 576 | + onSwitchSubStream() { | |
| 577 | + this.hideContextMenu(); | |
| 578 | + this.switchStream(1); | |
| 579 | + }, | |
| 580 | + | |
| 200 | 581 | setControls(config) { |
| 201 | 582 | this.controlsConfig = { ...this.controlsConfig, ...config }; |
| 583 | + }, | |
| 584 | + | |
| 585 | + // 切换码流 | |
| 586 | + switchStream(type) { | |
| 587 | + // 如果没有sim和channel参数,则不发送请求 | |
| 588 | + if (!this.sim || !this.channel) { | |
| 589 | + console.log('码流切换跳过: 缺少sim或channel参数'); | |
| 590 | + return; | |
| 591 | + } | |
| 592 | + | |
| 593 | + const params = { | |
| 594 | + sim: this.sim, | |
| 595 | + channel: parseInt(this.channel), | |
| 596 | + type: type | |
| 597 | + }; | |
| 598 | + | |
| 599 | + console.log(`码流切换: sim=${params.sim}, channel=${params.channel}, type=${type} (0=主码流, 1=子码流)`); | |
| 600 | + | |
| 601 | + // 使用箭头函数保存this引用 | |
| 602 | + const doSwitch = (axiosInstance) => { | |
| 603 | + axiosInstance.post('/api/jt1078/query/switch/stream', params) | |
| 604 | + .then(res => { | |
| 605 | + if (res.data.code === 200 || res.data.code === 0) { | |
| 606 | + console.log('码流切换成功,等待设备切换...'); | |
| 607 | + } else { | |
| 608 | + console.warn('码流切换失败:', res.data.msg || res.data); | |
| 609 | + } | |
| 610 | + }) | |
| 611 | + .catch(err => { | |
| 612 | + console.error('码流切换请求失败:', err); | |
| 613 | + }); | |
| 614 | + }; | |
| 615 | + | |
| 616 | + if (this.$axios) { | |
| 617 | + doSwitch(this.$axios); | |
| 618 | + } else if (window.axios) { | |
| 619 | + doSwitch(window.axios); | |
| 620 | + } | |
| 621 | + }, | |
| 622 | + | |
| 623 | + // 刷新播放地址并重新播放 | |
| 624 | + refreshPlayUrl() { | |
| 625 | + if (!this.sim || !this.channel) return; | |
| 626 | + | |
| 627 | + const axiosInstance = this.$axios || window.axios; | |
| 628 | + if (!axiosInstance) return; | |
| 629 | + | |
| 630 | + axiosInstance.get(`/api/jt1078/query/send/request/io/${this.sim}/${this.channel}`) | |
| 631 | + .then(res => { | |
| 632 | + if (res.data.code === 200 || res.data.code === 0) { | |
| 633 | + const newUrl = res.data.data.ws_flv || res.data.data.wss_flv; | |
| 634 | + console.log('获取新播放地址:', newUrl); | |
| 635 | + | |
| 636 | + if (newUrl) { | |
| 637 | + // 重新创建播放器并播放新地址 | |
| 638 | + // 分步执行:先销毁,等待,再重建,确保播放器完全重置 | |
| 639 | + this.destroy(); | |
| 640 | + | |
| 641 | + // 等待一段时间后重新创建播放器 | |
| 642 | + setTimeout(() => { | |
| 643 | + this.create(); | |
| 644 | + this.play(newUrl); | |
| 645 | + }, 500); // 等待500ms确保完全销毁后再重建 | |
| 646 | + } | |
| 647 | + } else { | |
| 648 | + console.warn('获取播放地址失败:', res.data.msg || res.data); | |
| 649 | + } | |
| 650 | + }) | |
| 651 | + .catch(err => { | |
| 652 | + console.error('获取播放地址失败:', err); | |
| 653 | + }); | |
| 202 | 654 | } |
| 203 | 655 | }, |
| 204 | 656 | }; |
| ... | ... | @@ -272,7 +724,7 @@ export default { |
| 272 | 724 | } |
| 273 | 725 | |
| 274 | 726 | /* 音量按钮 */ |
| 275 | -.player-wrapper.hide-btn-audio .easyplayer-audio-box { | |
| 727 | +.player-wrapper.hide-btn-audio .easyplayer-volume { | |
| 276 | 728 | display: none !important; |
| 277 | 729 | } |
| 278 | 730 | |
| ... | ... | @@ -451,4 +903,52 @@ export default { |
| 451 | 903 | .player-wrapper .easyplayer-loading-text { |
| 452 | 904 | display: none !important; |
| 453 | 905 | } |
| 906 | + | |
| 907 | +/* ========================================= | |
| 908 | + 3. 自定义右键菜单 | |
| 909 | + ========================================= */ | |
| 910 | + | |
| 911 | +/* 隐藏原生右键菜单 - 使用更高优先级 */ | |
| 912 | +[class*="easyplayer-contextmenu"] { | |
| 913 | + display: none !important; | |
| 914 | + visibility: hidden !important; | |
| 915 | +} | |
| 916 | + | |
| 917 | +/* 自定义右键菜单样式 */ | |
| 918 | +.custom-contextmenu { | |
| 919 | + position: absolute; | |
| 920 | + z-index: 2147483647 !important; | |
| 921 | + background: rgba(30, 30, 30, 0.95); | |
| 922 | + border: 1px solid rgba(80, 80, 80, 0.8); | |
| 923 | + border-radius: 6px; | |
| 924 | + padding: 6px 0; | |
| 925 | + min-width: 140px; | |
| 926 | + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); | |
| 927 | +} | |
| 928 | + | |
| 929 | +/* 全屏模式下使用 fixed 定位,并强制使用视口作为定位上下文 */ | |
| 930 | +.custom-contextmenu.menu-fixed { | |
| 931 | + position: fixed !important; | |
| 932 | + top: var(--menu-top, 0) !important; | |
| 933 | + left: var(--menu-left, 0) !important; | |
| 934 | + transform: none !important; | |
| 935 | +} | |
| 936 | + | |
| 937 | +.custom-contextmenu .contextmenu-divider { | |
| 938 | + height: 1px; | |
| 939 | + background: rgba(80, 80, 80, 0.6); | |
| 940 | + margin: 4px 0; | |
| 941 | +} | |
| 942 | + | |
| 943 | +.custom-contextmenu .contextmenu-item { | |
| 944 | + padding: 8px 16px; | |
| 945 | + color: #fff; | |
| 946 | + font-size: 13px; | |
| 947 | + cursor: pointer; | |
| 948 | + transition: background 0.2s; | |
| 949 | +} | |
| 950 | + | |
| 951 | +.custom-contextmenu .contextmenu-item:hover { | |
| 952 | + background: rgba(30, 144, 255, 0.6); | |
| 953 | +} | |
| 454 | 954 | </style> | ... | ... |
web_src/src/components/common/PlayerListComponent.vue
| ... | ... | @@ -15,6 +15,8 @@ |
| 15 | 15 | :initial-buffer-time="0.1" |
| 16 | 16 | :show-custom-mask="false" |
| 17 | 17 | :has-audio="true" |
| 18 | + :sim="playerDataList[i] ? playerDataList[i].sim : ''" | |
| 19 | + :channel="playerDataList[i] ? playerDataList[i].channel : ''" | |
| 18 | 20 | style="width: 100%;height: 100%;" |
| 19 | 21 | @click="playerClick(item, i, items.length)" |
| 20 | 22 | ></easyPlayer> |
| ... | ... | @@ -38,6 +40,10 @@ export default { |
| 38 | 40 | type: String, |
| 39 | 41 | default: '9' |
| 40 | 42 | }, |
| 43 | + hasAudio: { | |
| 44 | + type: Boolean, | |
| 45 | + default: true | |
| 46 | + }, | |
| 41 | 47 | videoUrl: { |
| 42 | 48 | type: Array, |
| 43 | 49 | default: [] |
| ... | ... | @@ -57,6 +63,16 @@ export default { |
| 57 | 63 | }, |
| 58 | 64 | //计算属性 类似于data概念", |
| 59 | 65 | computed: { |
| 66 | + // 生成播放器数据列表,包含 sim 和 channel | |
| 67 | + playerDataList() { | |
| 68 | + return this.items.map((item, i) => { | |
| 69 | + const data = this.videoDataList[i] || {}; | |
| 70 | + return { | |
| 71 | + sim: this.getSim(data), | |
| 72 | + channel: this.getChannel(data) | |
| 73 | + }; | |
| 74 | + }); | |
| 75 | + }, | |
| 60 | 76 | }, |
| 61 | 77 | //监控data中的数据变化", |
| 62 | 78 | watch: { |
| ... | ... | @@ -272,6 +288,22 @@ export default { |
| 272 | 288 | } |
| 273 | 289 | }); |
| 274 | 290 | }, |
| 291 | + | |
| 292 | + // 获取SIM卡号 (从parent对象中获取) | |
| 293 | + getSim(data) { | |
| 294 | + if (!data) return ''; | |
| 295 | + // 优先从自身获取,否则从parent对象获取 | |
| 296 | + return data.sim || (data.parent && data.parent.sim) || ''; | |
| 297 | + }, | |
| 298 | + | |
| 299 | + // 获取通道号 (从code中提取,格式: id_sim_channelNumber) | |
| 300 | + getChannel(data) { | |
| 301 | + console.log(data) | |
| 302 | + if (!data || !data.code) return ''; | |
| 303 | + const parts = data.code.split('_'); | |
| 304 | + // code格式: id_sim_channelNumber, channel是最后一部分 | |
| 305 | + return parts.length >= 3 ? parts[parts.length - 1] : ''; | |
| 306 | + } | |
| 275 | 307 | }, |
| 276 | 308 | //生命周期 - 创建完成(可以访问当前this实例)", |
| 277 | 309 | created() { | ... | ... |
web_src/static/js/jessibuca/decoder.js
0 → 100644
| 1 | +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(require("path"),require("fs"),require("crypto")):"function"==typeof define&&define.amd?define(["path","fs","crypto"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).path,e.fs,e.crypto$1)}(this,(function(e,r,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=n(e),a=n(r),i=n(t);var s=function(e,r){return e(r={exports:{}},r.exports),r.exports}((function(e){var r=void 0!==r?r:{},t=(r={print:function(e){console.log("Jessibuca: [worker]:",e)},printErr:function(e){console.warn("Jessibuca: [worker]:",e),postMessage({cmd:"wasmError",message:e})}},Object.assign({},r)),n="./this.program",s="object"==typeof window,l="function"==typeof importScripts,u="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,c=!s&&!u&&!l;if(r.ENVIRONMENT)throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)");var d,f,p,m,h,g,v="";if(u){if("object"!=typeof process)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");v=l?o.default.dirname(v)+"/":__dirname+"/",g=()=>{h||(m=a.default,h=o.default)},d=function(e,r){return g(),e=h.normalize(e),m.readFileSync(e,r?void 0:"utf8")},p=e=>{var r=d(e,!0);return r.buffer||(r=new Uint8Array(r)),D(r.buffer),r},f=(e,r,t)=>{g(),e=h.normalize(e),m.readFile(e,(function(e,n){e?t(e):r(n.buffer)}))},process.argv.length>1&&(n=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),e.exports=r,process.on("uncaughtException",(function(e){if(!(e instanceof St))throw e})),process.on("unhandledRejection",(function(e){throw e})),r.inspect=function(){return"[Emscripten Module object]"}}else if(c){if("object"==typeof process||"object"==typeof window||"function"==typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");"undefined"!=typeof read&&(d=function(e){return read(e)}),p=function(e){let r;return"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(r=read(e,"binary"),D("object"==typeof r),r)},f=function(e,r,t){setTimeout((()=>r(p(e))),0)},"undefined"!=typeof scriptArgs&&scriptArgs,"undefined"!=typeof print&&("undefined"==typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!=typeof printErr?printErr:print)}else{if(!s&&!l)throw new Error("environment detection error");if(l?v=self.location.href:"undefined"!=typeof document&&document.currentScript&&(v=document.currentScript.src),v=0!==v.indexOf("blob:")?v.substr(0,v.replace(/[?#].*/,"").lastIndexOf("/")+1):"","object"!=typeof window&&"function"!=typeof importScripts)throw new Error("not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)");d=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},l&&(p=e=>{var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),f=(e,r,t)=>{var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="arraybuffer",n.onload=()=>{200==n.status||0==n.status&&n.response?r(n.response):t()},n.onerror=t,n.send(null)}}var y,E,w,b=r.print||console.log.bind(console),_=r.printErr||console.warn.bind(console);function T(e){T.shown||(T.shown={}),T.shown[e]||(T.shown[e]=1,_(e))}function k(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ge("Module."+e+" has been replaced with plain "+t+" (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}})}function S(e,r){var t="'"+e+"' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)";return r&&(t+=". Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you"),t}function C(e,t){Object.getOwnPropertyDescriptor(r,e)||Object.defineProperty(r,e,{configurable:!0,get:function(){ge(S(e,t))}})}function P(e,t){Object.getOwnPropertyDescriptor(r,e)||(r[e]=()=>ge(S(e,t)))}Object.assign(r,t),t=null,y="fetchSettings",Object.getOwnPropertyDescriptor(r,y)&&ge("`Module."+y+"` was supplied but `"+y+"` not included in INCOMING_MODULE_JS_API"),r.arguments,k("arguments","arguments_"),r.thisProgram&&(n=r.thisProgram),k("thisProgram","thisProgram"),r.quit,k("quit","quit_"),D(void 0===r.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead"),D(void 0===r.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead"),D(void 0===r.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead"),D(void 0===r.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead"),D(void 0===r.read,"Module.read option was removed (modify read_ in JS)"),D(void 0===r.readAsync,"Module.readAsync option was removed (modify readAsync in JS)"),D(void 0===r.readBinary,"Module.readBinary option was removed (modify readBinary in JS)"),D(void 0===r.setWindowTitle,"Module.setWindowTitle option was removed (modify setWindowTitle in JS)"),D(void 0===r.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY"),k("read","read_"),k("readAsync","readAsync"),k("readBinary","readBinary"),k("setWindowTitle","setWindowTitle"),D(!c,"shell environment detected but not enabled at build time. Add 'shell' to `-sENVIRONMENT` to enable."),r.wasmBinary&&(E=r.wasmBinary),k("wasmBinary","wasmBinary"),r.noExitRuntime,k("noExitRuntime","noExitRuntime"),"object"!=typeof WebAssembly&&ge("no native wasm support detected");var A=!1;function D(e,r){e||ge("Assertion failed"+(r?": "+r:""))}var O="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function F(e,r,t){for(var n=r+t,o=r;e[o]&&!(o>=n);)++o;if(o-r>16&&e.buffer&&O)return O.decode(e.subarray(r,o));for(var a="";r<o;){var i=e[r++];if(128&i){var s=63&e[r++];if(192!=(224&i)){var l=63&e[r++];if(224==(240&i)?i=(15&i)<<12|s<<6|l:(240!=(248&i)&&T("Invalid UTF-8 leading byte 0x"+i.toString(16)+" encountered when deserializing a UTF-8 string in wasm memory to a JS string!"),i=(7&i)<<18|s<<12|l<<6|63&e[r++]),i<65536)a+=String.fromCharCode(i);else{var u=i-65536;a+=String.fromCharCode(55296|u>>10,56320|1023&u)}}else a+=String.fromCharCode((31&i)<<6|s)}else a+=String.fromCharCode(i)}return a}function M(e,r){return e?F(U,e,r):""}function R(e,r,t,n){if(!(n>0))return 0;for(var o=t,a=t+n-1,i=0;i<e.length;++i){var s=e.charCodeAt(i);if(s>=55296&&s<=57343)s=65536+((1023&s)<<10)|1023&e.charCodeAt(++i);if(s<=127){if(t>=a)break;r[t++]=s}else if(s<=2047){if(t+1>=a)break;r[t++]=192|s>>6,r[t++]=128|63&s}else if(s<=65535){if(t+2>=a)break;r[t++]=224|s>>12,r[t++]=128|s>>6&63,r[t++]=128|63&s}else{if(t+3>=a)break;s>1114111&&T("Invalid Unicode code point 0x"+s.toString(16)+" encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF)."),r[t++]=240|s>>18,r[t++]=128|s>>12&63,r[t++]=128|s>>6&63,r[t++]=128|63&s}}return r[t]=0,t-o}function N(e,r,t){return D("number"==typeof t,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),R(e,U,r,t)}function I(e){for(var r=0,t=0;t<e.length;++t){var n=e.charCodeAt(t);n>=55296&&n<=57343&&(n=65536+((1023&n)<<10)|1023&e.charCodeAt(++t)),n<=127?++r:r+=n<=2047?2:n<=65535?3:4}return r}var L,x,U,B,j,$,W,z,H,G="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0;function V(e,r){D(e%2==0,"Pointer passed to UTF16ToString must be aligned to two bytes!");for(var t=e,n=t>>1,o=n+r/2;!(n>=o)&&j[n];)++n;if((t=n<<1)-e>32&&G)return G.decode(U.subarray(e,t));for(var a="",i=0;!(i>=r/2);++i){var s=B[e+2*i>>1];if(0==s)break;a+=String.fromCharCode(s)}return a}function Y(e,r,t){if(D(r%2==0,"Pointer passed to stringToUTF16 must be aligned to two bytes!"),D("number"==typeof t,"stringToUTF16(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<2)return 0;for(var n=r,o=(t-=2)<2*e.length?t/2:e.length,a=0;a<o;++a){var i=e.charCodeAt(a);B[r>>1]=i,r+=2}return B[r>>1]=0,r-n}function X(e){return 2*e.length}function q(e,r){D(e%4==0,"Pointer passed to UTF32ToString must be aligned to four bytes!");for(var t=0,n="";!(t>=r/4);){var o=$[e+4*t>>2];if(0==o)break;if(++t,o>=65536){var a=o-65536;n+=String.fromCharCode(55296|a>>10,56320|1023&a)}else n+=String.fromCharCode(o)}return n}function K(e,r,t){if(D(r%4==0,"Pointer passed to stringToUTF32 must be aligned to four bytes!"),D("number"==typeof t,"stringToUTF32(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),void 0===t&&(t=2147483647),t<4)return 0;for(var n=r,o=n+t-4,a=0;a<e.length;++a){var i=e.charCodeAt(a);if(i>=55296&&i<=57343)i=65536+((1023&i)<<10)|1023&e.charCodeAt(++a);if($[r>>2]=i,(r+=4)+4>o)break}return $[r>>2]=0,r-n}function J(e){for(var r=0,t=0;t<e.length;++t){var n=e.charCodeAt(t);n>=55296&&n<=57343&&++t,r+=4}return r}function Q(e){var r=I(e)+1,t=gt(r);return t&&R(e,x,t,r),t}function Z(e){L=e,r.HEAP8=x=new Int8Array(e),r.HEAP16=B=new Int16Array(e),r.HEAP32=$=new Int32Array(e),r.HEAPU8=U=new Uint8Array(e),r.HEAPU16=j=new Uint16Array(e),r.HEAPU32=W=new Uint32Array(e),r.HEAPF32=z=new Float32Array(e),r.HEAPF64=H=new Float64Array(e)}var ee=5242880;r.TOTAL_STACK&&D(ee===r.TOTAL_STACK,"the stack size can no longer be determined at runtime");var re,te=r.INITIAL_MEMORY||67108864;function ne(){var e=kt();D(0==(3&e)),$[e>>2]=34821223,$[e+4>>2]=2310721022,$[0]=1668509029}function oe(){if(!A){var e=kt(),r=W[e>>2],t=W[e+4>>2];34821223==r&&2310721022==t||ge("Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x"+t.toString(16)+" 0x"+r.toString(16)),1668509029!==$[0]&&ge("Runtime error: The application has corrupted its heap memory area (address zero)!")}}k("INITIAL_MEMORY","INITIAL_MEMORY"),D(te>=ee,"INITIAL_MEMORY should be larger than TOTAL_STACK, was "+te+"! (TOTAL_STACK="+"5242880)"),D("undefined"!=typeof Int32Array&&"undefined"!=typeof Float64Array&&null!=Int32Array.prototype.subarray&&null!=Int32Array.prototype.set,"JS engine does not provide full typed array support"),D(!r.wasmMemory,"Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally"),D(67108864==te,"Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically"),function(){var e=new Int16Array(1),r=new Int8Array(e.buffer);if(e[0]=25459,115!==r[0]||99!==r[1])throw"Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)"}();var ae=[],ie=[],se=[],le=!1;D(Math.imul,"This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),D(Math.fround,"This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),D(Math.clz32,"This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),D(Math.trunc,"This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");var ue=0,ce=null,de=null,fe={};function pe(e){for(var r=e;;){if(!fe[e])return e;e=r+Math.random()}}function me(e){ue++,r.monitorRunDependencies&&r.monitorRunDependencies(ue),e?(D(!fe[e]),fe[e]=1,null===ce&&"undefined"!=typeof setInterval&&(ce=setInterval((function(){if(A)return clearInterval(ce),void(ce=null);var e=!1;for(var r in fe)e||(e=!0,_("still waiting on run dependencies:")),_("dependency: "+r);e&&_("(end of list)")}),1e4))):_("warning: run dependency added without ID")}function he(e){if(ue--,r.monitorRunDependencies&&r.monitorRunDependencies(ue),e?(D(fe[e]),delete fe[e]):_("warning: run dependency removed without ID"),0==ue&&(null!==ce&&(clearInterval(ce),ce=null),de)){var t=de;de=null,t()}}function ge(e){throw r.onAbort&&r.onAbort(e),_(e="Aborted("+e+")"),A=!0,new WebAssembly.RuntimeError(e)}var ve,ye,Ee;function we(e){return e.startsWith("data:application/octet-stream;base64,")}function be(e){return e.startsWith("file://")}function _e(e,t){return function(){var n=e,o=t;return t||(o=r.asm),D(le,"native function `"+n+"` called before runtime initialization"),o[e]||D(o[e],"exported native function `"+n+"` not found"),o[e].apply(null,arguments)}}function Te(e){try{if(e==ve&&E)return new Uint8Array(E);if(p)return p(e);throw"both async and sync fetching of the wasm failed"}catch(e){ge(e)}}function ke(e){for(;e.length>0;){var t=e.shift();if("function"!=typeof t){var n=t.func;"number"==typeof n?void 0===t.arg?Ce(n)():Ce(n)(t.arg):n(void 0===t.arg?null:t.arg)}else t(r)}}function Se(e){return e.replace(/\b_Z[\w\d_]+/g,(function(e){var r,t=(r=e,T("warning: build with -sDEMANGLE_SUPPORT to link in libcxxabi demangling"),r);return e===t?e:t+" ["+e+"]"}))}function Ce(e){return re.get(e)}function Pe(){var e=new Error;if(!e.stack){try{throw new Error}catch(r){e=r}if(!e.stack)return"(no stack trace available)"}return e.stack.toString()}we(ve="decoder.wasm")||(ve=function(e){return r.locateFile?r.locateFile(e,v):v+e}(ve));var Ae={isAbs:e=>"/"===e.charAt(0),splitPath:e=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(e).slice(1),normalizeArray:(e,r)=>{for(var t=0,n=e.length-1;n>=0;n--){var o=e[n];"."===o?e.splice(n,1):".."===o?(e.splice(n,1),t++):t&&(e.splice(n,1),t--)}if(r)for(;t;t--)e.unshift("..");return e},normalize:e=>{var r=Ae.isAbs(e),t="/"===e.substr(-1);return(e=Ae.normalizeArray(e.split("/").filter((e=>!!e)),!r).join("/"))||r||(e="."),e&&t&&(e+="/"),(r?"/":"")+e},dirname:e=>{var r=Ae.splitPath(e),t=r[0],n=r[1];return t||n?(n&&(n=n.substr(0,n.length-1)),t+n):"."},basename:e=>{if("/"===e)return"/";var r=(e=(e=Ae.normalize(e)).replace(/\/$/,"")).lastIndexOf("/");return-1===r?e:e.substr(r+1)},join:function(){var e=Array.prototype.slice.call(arguments,0);return Ae.normalize(e.join("/"))},join2:(e,r)=>Ae.normalize(e+"/"+r)};var De={resolve:function(){for(var e="",r=!1,t=arguments.length-1;t>=-1&&!r;t--){var n=t>=0?arguments[t]:Ie.cwd();if("string"!=typeof n)throw new TypeError("Arguments to path.resolve must be strings");if(!n)return"";e=n+"/"+e,r=Ae.isAbs(n)}return(r?"/":"")+(e=Ae.normalizeArray(e.split("/").filter((e=>!!e)),!r).join("/"))||"."},relative:(e,r)=>{function t(e){for(var r=0;r<e.length&&""===e[r];r++);for(var t=e.length-1;t>=0&&""===e[t];t--);return r>t?[]:e.slice(r,t-r+1)}e=De.resolve(e).substr(1),r=De.resolve(r).substr(1);for(var n=t(e.split("/")),o=t(r.split("/")),a=Math.min(n.length,o.length),i=a,s=0;s<a;s++)if(n[s]!==o[s]){i=s;break}var l=[];for(s=i;s<n.length;s++)l.push("..");return(l=l.concat(o.slice(i))).join("/")}},Oe={ttys:[],init:function(){},shutdown:function(){},register:function(e,r){Oe.ttys[e]={input:[],output:[],ops:r},Ie.registerDevice(e,Oe.stream_ops)},stream_ops:{open:function(e){var r=Oe.ttys[e.node.rdev];if(!r)throw new Ie.ErrnoError(43);e.tty=r,e.seekable=!1},close:function(e){e.tty.ops.flush(e.tty)},flush:function(e){e.tty.ops.flush(e.tty)},read:function(e,r,t,n,o){if(!e.tty||!e.tty.ops.get_char)throw new Ie.ErrnoError(60);for(var a=0,i=0;i<n;i++){var s;try{s=e.tty.ops.get_char(e.tty)}catch(e){throw new Ie.ErrnoError(29)}if(void 0===s&&0===a)throw new Ie.ErrnoError(6);if(null==s)break;a++,r[t+i]=s}return a&&(e.node.timestamp=Date.now()),a},write:function(e,r,t,n,o){if(!e.tty||!e.tty.ops.put_char)throw new Ie.ErrnoError(60);try{for(var a=0;a<n;a++)e.tty.ops.put_char(e.tty,r[t+a])}catch(e){throw new Ie.ErrnoError(29)}return n&&(e.node.timestamp=Date.now()),a}},default_tty_ops:{get_char:function(e){if(!e.input.length){var r=null;if(u){var t=Buffer.alloc(256),n=0;try{n=m.readSync(process.stdin.fd,t,0,256,-1)}catch(e){if(!e.toString().includes("EOF"))throw e;n=0}r=n>0?t.slice(0,n).toString("utf-8"):null}else"undefined"!=typeof window&&"function"==typeof window.prompt?null!==(r=window.prompt("Input: "))&&(r+="\n"):"function"==typeof readline&&null!==(r=readline())&&(r+="\n");if(!r)return null;e.input=pt(r,!0)}return e.input.shift()},put_char:function(e,r){null===r||10===r?(b(F(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(b(F(e.output,0)),e.output=[])}},default_tty1_ops:{put_char:function(e,r){null===r||10===r?(_(F(e.output,0)),e.output=[]):0!=r&&e.output.push(r)},flush:function(e){e.output&&e.output.length>0&&(_(F(e.output,0)),e.output=[])}}};function Fe(e){e=function(e,r){return D(r,"alignment argument is required"),Math.ceil(e/r)*r}(e,65536);var r=bt(65536,e);return r?(function(e,r){U.fill(0,e,e+r)}(r,e),r):0}var Me={ops_table:null,mount:function(e){return Me.createNode(null,"/",16895,0)},createNode:function(e,r,t,n){if(Ie.isBlkdev(t)||Ie.isFIFO(t))throw new Ie.ErrnoError(63);Me.ops_table||(Me.ops_table={dir:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr,lookup:Me.node_ops.lookup,mknod:Me.node_ops.mknod,rename:Me.node_ops.rename,unlink:Me.node_ops.unlink,rmdir:Me.node_ops.rmdir,readdir:Me.node_ops.readdir,symlink:Me.node_ops.symlink},stream:{llseek:Me.stream_ops.llseek}},file:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr},stream:{llseek:Me.stream_ops.llseek,read:Me.stream_ops.read,write:Me.stream_ops.write,allocate:Me.stream_ops.allocate,mmap:Me.stream_ops.mmap,msync:Me.stream_ops.msync}},link:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr,readlink:Me.node_ops.readlink},stream:{}},chrdev:{node:{getattr:Me.node_ops.getattr,setattr:Me.node_ops.setattr},stream:Ie.chrdev_stream_ops}});var o=Ie.createNode(e,r,t,n);return Ie.isDir(o.mode)?(o.node_ops=Me.ops_table.dir.node,o.stream_ops=Me.ops_table.dir.stream,o.contents={}):Ie.isFile(o.mode)?(o.node_ops=Me.ops_table.file.node,o.stream_ops=Me.ops_table.file.stream,o.usedBytes=0,o.contents=null):Ie.isLink(o.mode)?(o.node_ops=Me.ops_table.link.node,o.stream_ops=Me.ops_table.link.stream):Ie.isChrdev(o.mode)&&(o.node_ops=Me.ops_table.chrdev.node,o.stream_ops=Me.ops_table.chrdev.stream),o.timestamp=Date.now(),e&&(e.contents[r]=o,e.timestamp=o.timestamp),o},getFileDataAsTypedArray:function(e){return e.contents?e.contents.subarray?e.contents.subarray(0,e.usedBytes):new Uint8Array(e.contents):new Uint8Array(0)},expandFileStorage:function(e,r){var t=e.contents?e.contents.length:0;if(!(t>=r)){r=Math.max(r,t*(t<1048576?2:1.125)>>>0),0!=t&&(r=Math.max(r,256));var n=e.contents;e.contents=new Uint8Array(r),e.usedBytes>0&&e.contents.set(n.subarray(0,e.usedBytes),0)}},resizeFileStorage:function(e,r){if(e.usedBytes!=r)if(0==r)e.contents=null,e.usedBytes=0;else{var t=e.contents;e.contents=new Uint8Array(r),t&&e.contents.set(t.subarray(0,Math.min(r,e.usedBytes))),e.usedBytes=r}},node_ops:{getattr:function(e){var r={};return r.dev=Ie.isChrdev(e.mode)?e.id:1,r.ino=e.id,r.mode=e.mode,r.nlink=1,r.uid=0,r.gid=0,r.rdev=e.rdev,Ie.isDir(e.mode)?r.size=4096:Ie.isFile(e.mode)?r.size=e.usedBytes:Ie.isLink(e.mode)?r.size=e.link.length:r.size=0,r.atime=new Date(e.timestamp),r.mtime=new Date(e.timestamp),r.ctime=new Date(e.timestamp),r.blksize=4096,r.blocks=Math.ceil(r.size/r.blksize),r},setattr:function(e,r){void 0!==r.mode&&(e.mode=r.mode),void 0!==r.timestamp&&(e.timestamp=r.timestamp),void 0!==r.size&&Me.resizeFileStorage(e,r.size)},lookup:function(e,r){throw Ie.genericErrors[44]},mknod:function(e,r,t,n){return Me.createNode(e,r,t,n)},rename:function(e,r,t){if(Ie.isDir(e.mode)){var n;try{n=Ie.lookupNode(r,t)}catch(e){}if(n)for(var o in n.contents)throw new Ie.ErrnoError(55)}delete e.parent.contents[e.name],e.parent.timestamp=Date.now(),e.name=t,r.contents[t]=e,r.timestamp=e.parent.timestamp,e.parent=r},unlink:function(e,r){delete e.contents[r],e.timestamp=Date.now()},rmdir:function(e,r){var t=Ie.lookupNode(e,r);for(var n in t.contents)throw new Ie.ErrnoError(55);delete e.contents[r],e.timestamp=Date.now()},readdir:function(e){var r=[".",".."];for(var t in e.contents)e.contents.hasOwnProperty(t)&&r.push(t);return r},symlink:function(e,r,t){var n=Me.createNode(e,r,41471,0);return n.link=t,n},readlink:function(e){if(!Ie.isLink(e.mode))throw new Ie.ErrnoError(28);return e.link}},stream_ops:{read:function(e,r,t,n,o){var a=e.node.contents;if(o>=e.node.usedBytes)return 0;var i=Math.min(e.node.usedBytes-o,n);if(D(i>=0),i>8&&a.subarray)r.set(a.subarray(o,o+i),t);else for(var s=0;s<i;s++)r[t+s]=a[o+s];return i},write:function(e,r,t,n,o,a){if(D(!(r instanceof ArrayBuffer)),r.buffer===x.buffer&&(a=!1),!n)return 0;var i=e.node;if(i.timestamp=Date.now(),r.subarray&&(!i.contents||i.contents.subarray)){if(a)return D(0===o,"canOwn must imply no weird position inside the file"),i.contents=r.subarray(t,t+n),i.usedBytes=n,n;if(0===i.usedBytes&&0===o)return i.contents=r.slice(t,t+n),i.usedBytes=n,n;if(o+n<=i.usedBytes)return i.contents.set(r.subarray(t,t+n),o),n}if(Me.expandFileStorage(i,o+n),i.contents.subarray&&r.subarray)i.contents.set(r.subarray(t,t+n),o);else for(var s=0;s<n;s++)i.contents[o+s]=r[t+s];return i.usedBytes=Math.max(i.usedBytes,o+n),n},llseek:function(e,r,t){var n=r;if(1===t?n+=e.position:2===t&&Ie.isFile(e.node.mode)&&(n+=e.node.usedBytes),n<0)throw new Ie.ErrnoError(28);return n},allocate:function(e,r,t){Me.expandFileStorage(e.node,r+t),e.node.usedBytes=Math.max(e.node.usedBytes,r+t)},mmap:function(e,r,t,n,o,a){if(0!==r)throw new Ie.ErrnoError(28);if(!Ie.isFile(e.node.mode))throw new Ie.ErrnoError(43);var i,s,l=e.node.contents;if(2&a||l.buffer!==L){if((n>0||n+t<l.length)&&(l=l.subarray?l.subarray(n,n+t):Array.prototype.slice.call(l,n,n+t)),s=!0,!(i=Fe(t)))throw new Ie.ErrnoError(48);x.set(l,i)}else s=!1,i=l.byteOffset;return{ptr:i,allocated:s}},msync:function(e,r,t,n,o){if(!Ie.isFile(e.node.mode))throw new Ie.ErrnoError(43);return 2&o||Me.stream_ops.write(e,r,0,n,t,!1),0}}};var Re={0:"Success",1:"Arg list too long",2:"Permission denied",3:"Address already in use",4:"Address not available",5:"Address family not supported by protocol family",6:"No more processes",7:"Socket already connected",8:"Bad file number",9:"Trying to read unreadable message",10:"Mount device busy",11:"Operation canceled",12:"No children",13:"Connection aborted",14:"Connection refused",15:"Connection reset by peer",16:"File locking deadlock error",17:"Destination address required",18:"Math arg out of domain of func",19:"Quota exceeded",20:"File exists",21:"Bad address",22:"File too large",23:"Host is unreachable",24:"Identifier removed",25:"Illegal byte sequence",26:"Connection already in progress",27:"Interrupted system call",28:"Invalid argument",29:"I/O error",30:"Socket is already connected",31:"Is a directory",32:"Too many symbolic links",33:"Too many open files",34:"Too many links",35:"Message too long",36:"Multihop attempted",37:"File or path name too long",38:"Network interface is not configured",39:"Connection reset by network",40:"Network is unreachable",41:"Too many open files in system",42:"No buffer space available",43:"No such device",44:"No such file or directory",45:"Exec format error",46:"No record locks available",47:"The link has been severed",48:"Not enough core",49:"No message of desired type",50:"Protocol not available",51:"No space left on device",52:"Function not implemented",53:"Socket is not connected",54:"Not a directory",55:"Directory not empty",56:"State not recoverable",57:"Socket operation on non-socket",59:"Not a typewriter",60:"No such device or address",61:"Value too large for defined data type",62:"Previous owner died",63:"Not super-user",64:"Broken pipe",65:"Protocol error",66:"Unknown protocol",67:"Protocol wrong type for socket",68:"Math result not representable",69:"Read only file system",70:"Illegal seek",71:"No such process",72:"Stale file handle",73:"Connection timed out",74:"Text file busy",75:"Cross-device link",100:"Device not a stream",101:"Bad font file fmt",102:"Invalid slot",103:"Invalid request code",104:"No anode",105:"Block device required",106:"Channel number out of range",107:"Level 3 halted",108:"Level 3 reset",109:"Link number out of range",110:"Protocol driver not attached",111:"No CSI structure available",112:"Level 2 halted",113:"Invalid exchange",114:"Invalid request descriptor",115:"Exchange full",116:"No data (for no delay io)",117:"Timer expired",118:"Out of streams resources",119:"Machine is not on the network",120:"Package not installed",121:"The object is remote",122:"Advertise error",123:"Srmount error",124:"Communication error on send",125:"Cross mount point (not really error)",126:"Given log. name not unique",127:"f.d. invalid for this operation",128:"Remote address changed",129:"Can access a needed shared lib",130:"Accessing a corrupted shared lib",131:".lib section in a.out corrupted",132:"Attempting to link in too many libs",133:"Attempting to exec a shared library",135:"Streams pipe error",136:"Too many users",137:"Socket type not supported",138:"Not supported",139:"Protocol family not supported",140:"Can't send after socket shutdown",141:"Too many references",142:"Host is down",148:"No medium (in tape drive)",156:"Level 2 not synchronized"},Ne={},Ie={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:!1,ignorePermissions:!0,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:function(e){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!(e=De.resolve(Ie.cwd(),e)))return{path:"",node:null};var t={follow_mount:!0,recurse_count:0};if(r=Object.assign(t,r),r.recurse_count>8)throw new Ie.ErrnoError(32);for(var n=Ae.normalizeArray(e.split("/").filter((e=>!!e)),!1),o=Ie.root,a="/",i=0;i<n.length;i++){var s=i===n.length-1;if(s&&r.parent)break;if(o=Ie.lookupNode(o,n[i]),a=Ae.join2(a,n[i]),Ie.isMountpoint(o)&&(!s||s&&r.follow_mount)&&(o=o.mounted.root),!s||r.follow)for(var l=0;Ie.isLink(o.mode);){var u=Ie.readlink(a);a=De.resolve(Ae.dirname(a),u);var c=Ie.lookupPath(a,{recurse_count:r.recurse_count+1});if(o=c.node,l++>40)throw new Ie.ErrnoError(32)}}return{path:a,node:o}},getPath:e=>{for(var r;;){if(Ie.isRoot(e)){var t=e.mount.mountpoint;return r?"/"!==t[t.length-1]?t+"/"+r:t+r:t}r=r?e.name+"/"+r:e.name,e=e.parent}},hashName:(e,r)=>{for(var t=0,n=0;n<r.length;n++)t=(t<<5)-t+r.charCodeAt(n)|0;return(e+t>>>0)%Ie.nameTable.length},hashAddNode:e=>{var r=Ie.hashName(e.parent.id,e.name);e.name_next=Ie.nameTable[r],Ie.nameTable[r]=e},hashRemoveNode:e=>{var r=Ie.hashName(e.parent.id,e.name);if(Ie.nameTable[r]===e)Ie.nameTable[r]=e.name_next;else for(var t=Ie.nameTable[r];t;){if(t.name_next===e){t.name_next=e.name_next;break}t=t.name_next}},lookupNode:(e,r)=>{var t=Ie.mayLookup(e);if(t)throw new Ie.ErrnoError(t,e);for(var n=Ie.hashName(e.id,r),o=Ie.nameTable[n];o;o=o.name_next){var a=o.name;if(o.parent.id===e.id&&a===r)return o}return Ie.lookup(e,r)},createNode:(e,r,t,n)=>{D("object"==typeof e);var o=new Ie.FSNode(e,r,t,n);return Ie.hashAddNode(o),o},destroyNode:e=>{Ie.hashRemoveNode(e)},isRoot:e=>e===e.parent,isMountpoint:e=>!!e.mounted,isFile:e=>32768==(61440&e),isDir:e=>16384==(61440&e),isLink:e=>40960==(61440&e),isChrdev:e=>8192==(61440&e),isBlkdev:e=>24576==(61440&e),isFIFO:e=>4096==(61440&e),isSocket:e=>49152==(49152&e),flagModes:{r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090},modeStringToFlags:e=>{var r=Ie.flagModes[e];if(void 0===r)throw new Error("Unknown file open mode: "+e);return r},flagsToPermissionString:e=>{var r=["r","w","rw"][3&e];return 512&e&&(r+="w"),r},nodePermissions:(e,r)=>Ie.ignorePermissions||(!r.includes("r")||292&e.mode)&&(!r.includes("w")||146&e.mode)&&(!r.includes("x")||73&e.mode)?0:2,mayLookup:e=>{var r=Ie.nodePermissions(e,"x");return r||(e.node_ops.lookup?0:2)},mayCreate:(e,r)=>{try{Ie.lookupNode(e,r);return 20}catch(e){}return Ie.nodePermissions(e,"wx")},mayDelete:(e,r,t)=>{var n;try{n=Ie.lookupNode(e,r)}catch(e){return e.errno}var o=Ie.nodePermissions(e,"wx");if(o)return o;if(t){if(!Ie.isDir(n.mode))return 54;if(Ie.isRoot(n)||Ie.getPath(n)===Ie.cwd())return 10}else if(Ie.isDir(n.mode))return 31;return 0},mayOpen:(e,r)=>e?Ie.isLink(e.mode)?32:Ie.isDir(e.mode)&&("r"!==Ie.flagsToPermissionString(r)||512&r)?31:Ie.nodePermissions(e,Ie.flagsToPermissionString(r)):44,MAX_OPEN_FDS:4096,nextfd:function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Ie.MAX_OPEN_FDS;for(var t=e;t<=r;t++)if(!Ie.streams[t])return t;throw new Ie.ErrnoError(33)},getStream:e=>Ie.streams[e],createStream:(e,r,t)=>{Ie.FSStream||(Ie.FSStream=function(){this.shared={}},Ie.FSStream.prototype={object:{get:function(){return this.node},set:function(e){this.node=e}},isRead:{get:function(){return 1!=(2097155&this.flags)}},isWrite:{get:function(){return 0!=(2097155&this.flags)}},isAppend:{get:function(){return 1024&this.flags}},flags:{get:function(){return this.shared.flags},set:function(e){this.shared.flags=e}},position:{get function(){return this.shared.position},set:function(e){this.shared.position=e}}}),e=Object.assign(new Ie.FSStream,e);var n=Ie.nextfd(r,t);return e.fd=n,Ie.streams[n]=e,e},closeStream:e=>{Ie.streams[e]=null},chrdev_stream_ops:{open:e=>{var r=Ie.getDevice(e.node.rdev);e.stream_ops=r.stream_ops,e.stream_ops.open&&e.stream_ops.open(e)},llseek:()=>{throw new Ie.ErrnoError(70)}},major:e=>e>>8,minor:e=>255&e,makedev:(e,r)=>e<<8|r,registerDevice:(e,r)=>{Ie.devices[e]={stream_ops:r}},getDevice:e=>Ie.devices[e],getMounts:e=>{for(var r=[],t=[e];t.length;){var n=t.pop();r.push(n),t.push.apply(t,n.mounts)}return r},syncfs:(e,r)=>{"function"==typeof e&&(r=e,e=!1),Ie.syncFSRequests++,Ie.syncFSRequests>1&&_("warning: "+Ie.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work");var t=Ie.getMounts(Ie.root.mount),n=0;function o(e){return D(Ie.syncFSRequests>0),Ie.syncFSRequests--,r(e)}function a(e){if(e)return a.errored?void 0:(a.errored=!0,o(e));++n>=t.length&&o(null)}t.forEach((r=>{if(!r.type.syncfs)return a(null);r.type.syncfs(r,e,a)}))},mount:(e,r,t)=>{if("string"==typeof e)throw e;var n,o="/"===t,a=!t;if(o&&Ie.root)throw new Ie.ErrnoError(10);if(!o&&!a){var i=Ie.lookupPath(t,{follow_mount:!1});if(t=i.path,n=i.node,Ie.isMountpoint(n))throw new Ie.ErrnoError(10);if(!Ie.isDir(n.mode))throw new Ie.ErrnoError(54)}var s={type:e,opts:r,mountpoint:t,mounts:[]},l=e.mount(s);return l.mount=s,s.root=l,o?Ie.root=l:n&&(n.mounted=s,n.mount&&n.mount.mounts.push(s)),l},unmount:e=>{var r=Ie.lookupPath(e,{follow_mount:!1});if(!Ie.isMountpoint(r.node))throw new Ie.ErrnoError(28);var t=r.node,n=t.mounted,o=Ie.getMounts(n);Object.keys(Ie.nameTable).forEach((e=>{for(var r=Ie.nameTable[e];r;){var t=r.name_next;o.includes(r.mount)&&Ie.destroyNode(r),r=t}})),t.mounted=null;var a=t.mount.mounts.indexOf(n);D(-1!==a),t.mount.mounts.splice(a,1)},lookup:(e,r)=>e.node_ops.lookup(e,r),mknod:(e,r,t)=>{var n=Ie.lookupPath(e,{parent:!0}).node,o=Ae.basename(e);if(!o||"."===o||".."===o)throw new Ie.ErrnoError(28);var a=Ie.mayCreate(n,o);if(a)throw new Ie.ErrnoError(a);if(!n.node_ops.mknod)throw new Ie.ErrnoError(63);return n.node_ops.mknod(n,o,r,t)},create:(e,r)=>(r=void 0!==r?r:438,r&=4095,r|=32768,Ie.mknod(e,r,0)),mkdir:(e,r)=>(r=void 0!==r?r:511,r&=1023,r|=16384,Ie.mknod(e,r,0)),mkdirTree:(e,r)=>{for(var t=e.split("/"),n="",o=0;o<t.length;++o)if(t[o]){n+="/"+t[o];try{Ie.mkdir(n,r)}catch(e){if(20!=e.errno)throw e}}},mkdev:(e,r,t)=>(void 0===t&&(t=r,r=438),r|=8192,Ie.mknod(e,r,t)),symlink:(e,r)=>{if(!De.resolve(e))throw new Ie.ErrnoError(44);var t=Ie.lookupPath(r,{parent:!0}).node;if(!t)throw new Ie.ErrnoError(44);var n=Ae.basename(r),o=Ie.mayCreate(t,n);if(o)throw new Ie.ErrnoError(o);if(!t.node_ops.symlink)throw new Ie.ErrnoError(63);return t.node_ops.symlink(t,n,e)},rename:(e,r)=>{var t,n,o=Ae.dirname(e),a=Ae.dirname(r),i=Ae.basename(e),s=Ae.basename(r);if(t=Ie.lookupPath(e,{parent:!0}).node,n=Ie.lookupPath(r,{parent:!0}).node,!t||!n)throw new Ie.ErrnoError(44);if(t.mount!==n.mount)throw new Ie.ErrnoError(75);var l,u=Ie.lookupNode(t,i),c=De.relative(e,a);if("."!==c.charAt(0))throw new Ie.ErrnoError(28);if("."!==(c=De.relative(r,o)).charAt(0))throw new Ie.ErrnoError(55);try{l=Ie.lookupNode(n,s)}catch(e){}if(u!==l){var d=Ie.isDir(u.mode),f=Ie.mayDelete(t,i,d);if(f)throw new Ie.ErrnoError(f);if(f=l?Ie.mayDelete(n,s,d):Ie.mayCreate(n,s))throw new Ie.ErrnoError(f);if(!t.node_ops.rename)throw new Ie.ErrnoError(63);if(Ie.isMountpoint(u)||l&&Ie.isMountpoint(l))throw new Ie.ErrnoError(10);if(n!==t&&(f=Ie.nodePermissions(t,"w")))throw new Ie.ErrnoError(f);Ie.hashRemoveNode(u);try{t.node_ops.rename(u,n,s)}catch(e){throw e}finally{Ie.hashAddNode(u)}}},rmdir:e=>{var r=Ie.lookupPath(e,{parent:!0}).node,t=Ae.basename(e),n=Ie.lookupNode(r,t),o=Ie.mayDelete(r,t,!0);if(o)throw new Ie.ErrnoError(o);if(!r.node_ops.rmdir)throw new Ie.ErrnoError(63);if(Ie.isMountpoint(n))throw new Ie.ErrnoError(10);r.node_ops.rmdir(r,t),Ie.destroyNode(n)},readdir:e=>{var r=Ie.lookupPath(e,{follow:!0}).node;if(!r.node_ops.readdir)throw new Ie.ErrnoError(54);return r.node_ops.readdir(r)},unlink:e=>{var r=Ie.lookupPath(e,{parent:!0}).node;if(!r)throw new Ie.ErrnoError(44);var t=Ae.basename(e),n=Ie.lookupNode(r,t),o=Ie.mayDelete(r,t,!1);if(o)throw new Ie.ErrnoError(o);if(!r.node_ops.unlink)throw new Ie.ErrnoError(63);if(Ie.isMountpoint(n))throw new Ie.ErrnoError(10);r.node_ops.unlink(r,t),Ie.destroyNode(n)},readlink:e=>{var r=Ie.lookupPath(e).node;if(!r)throw new Ie.ErrnoError(44);if(!r.node_ops.readlink)throw new Ie.ErrnoError(28);return De.resolve(Ie.getPath(r.parent),r.node_ops.readlink(r))},stat:(e,r)=>{var t=Ie.lookupPath(e,{follow:!r}).node;if(!t)throw new Ie.ErrnoError(44);if(!t.node_ops.getattr)throw new Ie.ErrnoError(63);return t.node_ops.getattr(t)},lstat:e=>Ie.stat(e,!0),chmod:(e,r,t)=>{var n;"string"==typeof e?n=Ie.lookupPath(e,{follow:!t}).node:n=e;if(!n.node_ops.setattr)throw new Ie.ErrnoError(63);n.node_ops.setattr(n,{mode:4095&r|-4096&n.mode,timestamp:Date.now()})},lchmod:(e,r)=>{Ie.chmod(e,r,!0)},fchmod:(e,r)=>{var t=Ie.getStream(e);if(!t)throw new Ie.ErrnoError(8);Ie.chmod(t.node,r)},chown:(e,r,t,n)=>{var o;"string"==typeof e?o=Ie.lookupPath(e,{follow:!n}).node:o=e;if(!o.node_ops.setattr)throw new Ie.ErrnoError(63);o.node_ops.setattr(o,{timestamp:Date.now()})},lchown:(e,r,t)=>{Ie.chown(e,r,t,!0)},fchown:(e,r,t)=>{var n=Ie.getStream(e);if(!n)throw new Ie.ErrnoError(8);Ie.chown(n.node,r,t)},truncate:(e,r)=>{if(r<0)throw new Ie.ErrnoError(28);var t;"string"==typeof e?t=Ie.lookupPath(e,{follow:!0}).node:t=e;if(!t.node_ops.setattr)throw new Ie.ErrnoError(63);if(Ie.isDir(t.mode))throw new Ie.ErrnoError(31);if(!Ie.isFile(t.mode))throw new Ie.ErrnoError(28);var n=Ie.nodePermissions(t,"w");if(n)throw new Ie.ErrnoError(n);t.node_ops.setattr(t,{size:r,timestamp:Date.now()})},ftruncate:(e,r)=>{var t=Ie.getStream(e);if(!t)throw new Ie.ErrnoError(8);if(0==(2097155&t.flags))throw new Ie.ErrnoError(28);Ie.truncate(t.node,r)},utime:(e,r,t)=>{var n=Ie.lookupPath(e,{follow:!0}).node;n.node_ops.setattr(n,{timestamp:Math.max(r,t)})},open:(e,t,n,o,a)=>{if(""===e)throw new Ie.ErrnoError(44);var i;if(n=void 0===n?438:n,n=64&(t="string"==typeof t?Ie.modeStringToFlags(t):t)?4095&n|32768:0,"object"==typeof e)i=e;else{e=Ae.normalize(e);try{i=Ie.lookupPath(e,{follow:!(131072&t)}).node}catch(e){}}var s=!1;if(64&t)if(i){if(128&t)throw new Ie.ErrnoError(20)}else i=Ie.mknod(e,n,0),s=!0;if(!i)throw new Ie.ErrnoError(44);if(Ie.isChrdev(i.mode)&&(t&=-513),65536&t&&!Ie.isDir(i.mode))throw new Ie.ErrnoError(54);if(!s){var l=Ie.mayOpen(i,t);if(l)throw new Ie.ErrnoError(l)}512&t&&Ie.truncate(i,0),t&=-131713;var u=Ie.createStream({node:i,path:Ie.getPath(i),flags:t,seekable:!0,position:0,stream_ops:i.stream_ops,ungotten:[],error:!1},o,a);return u.stream_ops.open&&u.stream_ops.open(u),!r.logReadFiles||1&t||(Ie.readFiles||(Ie.readFiles={}),e in Ie.readFiles||(Ie.readFiles[e]=1)),u},close:e=>{if(Ie.isClosed(e))throw new Ie.ErrnoError(8);e.getdents&&(e.getdents=null);try{e.stream_ops.close&&e.stream_ops.close(e)}catch(e){throw e}finally{Ie.closeStream(e.fd)}e.fd=null},isClosed:e=>null===e.fd,llseek:(e,r,t)=>{if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(!e.seekable||!e.stream_ops.llseek)throw new Ie.ErrnoError(70);if(0!=t&&1!=t&&2!=t)throw new Ie.ErrnoError(28);return e.position=e.stream_ops.llseek(e,r,t),e.ungotten=[],e.position},read:(e,r,t,n,o)=>{if(n<0||o<0)throw new Ie.ErrnoError(28);if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(1==(2097155&e.flags))throw new Ie.ErrnoError(8);if(Ie.isDir(e.node.mode))throw new Ie.ErrnoError(31);if(!e.stream_ops.read)throw new Ie.ErrnoError(28);var a=void 0!==o;if(a){if(!e.seekable)throw new Ie.ErrnoError(70)}else o=e.position;var i=e.stream_ops.read(e,r,t,n,o);return a||(e.position+=i),i},write:(e,r,t,n,o,a)=>{if(n<0||o<0)throw new Ie.ErrnoError(28);if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(0==(2097155&e.flags))throw new Ie.ErrnoError(8);if(Ie.isDir(e.node.mode))throw new Ie.ErrnoError(31);if(!e.stream_ops.write)throw new Ie.ErrnoError(28);e.seekable&&1024&e.flags&&Ie.llseek(e,0,2);var i=void 0!==o;if(i){if(!e.seekable)throw new Ie.ErrnoError(70)}else o=e.position;var s=e.stream_ops.write(e,r,t,n,o,a);return i||(e.position+=s),s},allocate:(e,r,t)=>{if(Ie.isClosed(e))throw new Ie.ErrnoError(8);if(r<0||t<=0)throw new Ie.ErrnoError(28);if(0==(2097155&e.flags))throw new Ie.ErrnoError(8);if(!Ie.isFile(e.node.mode)&&!Ie.isDir(e.node.mode))throw new Ie.ErrnoError(43);if(!e.stream_ops.allocate)throw new Ie.ErrnoError(138);e.stream_ops.allocate(e,r,t)},mmap:(e,r,t,n,o,a)=>{if(0!=(2&o)&&0==(2&a)&&2!=(2097155&e.flags))throw new Ie.ErrnoError(2);if(1==(2097155&e.flags))throw new Ie.ErrnoError(2);if(!e.stream_ops.mmap)throw new Ie.ErrnoError(43);return e.stream_ops.mmap(e,r,t,n,o,a)},msync:(e,r,t,n,o)=>e&&e.stream_ops.msync?e.stream_ops.msync(e,r,t,n,o):0,munmap:e=>0,ioctl:(e,r,t)=>{if(!e.stream_ops.ioctl)throw new Ie.ErrnoError(59);return e.stream_ops.ioctl(e,r,t)},readFile:function(e){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(r.flags=r.flags||0,r.encoding=r.encoding||"binary","utf8"!==r.encoding&&"binary"!==r.encoding)throw new Error('Invalid encoding type "'+r.encoding+'"');var t,n=Ie.open(e,r.flags),o=Ie.stat(e),a=o.size,i=new Uint8Array(a);return Ie.read(n,i,0,a,0),"utf8"===r.encoding?t=F(i,0):"binary"===r.encoding&&(t=i),Ie.close(n),t},writeFile:function(e,r){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};t.flags=t.flags||577;var n=Ie.open(e,t.flags,t.mode);if("string"==typeof r){var o=new Uint8Array(I(r)+1),a=R(r,o,0,o.length);Ie.write(n,o,0,a,void 0,t.canOwn)}else{if(!ArrayBuffer.isView(r))throw new Error("Unsupported data type");Ie.write(n,r,0,r.byteLength,void 0,t.canOwn)}Ie.close(n)},cwd:()=>Ie.currentPath,chdir:e=>{var r=Ie.lookupPath(e,{follow:!0});if(null===r.node)throw new Ie.ErrnoError(44);if(!Ie.isDir(r.node.mode))throw new Ie.ErrnoError(54);var t=Ie.nodePermissions(r.node,"x");if(t)throw new Ie.ErrnoError(t);Ie.currentPath=r.path},createDefaultDirectories:()=>{Ie.mkdir("/tmp"),Ie.mkdir("/home"),Ie.mkdir("/home/web_user")},createDefaultDevices:()=>{Ie.mkdir("/dev"),Ie.registerDevice(Ie.makedev(1,3),{read:()=>0,write:(e,r,t,n,o)=>n}),Ie.mkdev("/dev/null",Ie.makedev(1,3)),Oe.register(Ie.makedev(5,0),Oe.default_tty_ops),Oe.register(Ie.makedev(6,0),Oe.default_tty1_ops),Ie.mkdev("/dev/tty",Ie.makedev(5,0)),Ie.mkdev("/dev/tty1",Ie.makedev(6,0));var e=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var e=new Uint8Array(1);return function(){return crypto.getRandomValues(e),e[0]}}if(u)try{var r=i.default;return function(){return r.randomBytes(1)[0]}}catch(e){}return function(){ge("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };")}}();Ie.createDevice("/dev","random",e),Ie.createDevice("/dev","urandom",e),Ie.mkdir("/dev/shm"),Ie.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{Ie.mkdir("/proc");var e=Ie.mkdir("/proc/self");Ie.mkdir("/proc/self/fd"),Ie.mount({mount:()=>{var r=Ie.createNode(e,"fd",16895,73);return r.node_ops={lookup:(e,r)=>{var t=+r,n=Ie.getStream(t);if(!n)throw new Ie.ErrnoError(8);var o={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>n.path}};return o.parent=o,o}},r}},{},"/proc/self/fd")},createStandardStreams:()=>{r.stdin?Ie.createDevice("/dev","stdin",r.stdin):Ie.symlink("/dev/tty","/dev/stdin"),r.stdout?Ie.createDevice("/dev","stdout",null,r.stdout):Ie.symlink("/dev/tty","/dev/stdout"),r.stderr?Ie.createDevice("/dev","stderr",null,r.stderr):Ie.symlink("/dev/tty1","/dev/stderr");var e=Ie.open("/dev/stdin",0),t=Ie.open("/dev/stdout",1),n=Ie.open("/dev/stderr",1);D(0===e.fd,"invalid handle for stdin ("+e.fd+")"),D(1===t.fd,"invalid handle for stdout ("+t.fd+")"),D(2===n.fd,"invalid handle for stderr ("+n.fd+")")},ensureErrnoError:()=>{Ie.ErrnoError||(Ie.ErrnoError=function(e,r){this.node=r,this.setErrno=function(e){for(var r in this.errno=e,Ne)if(Ne[r]===e){this.code=r;break}},this.setErrno(e),this.message=Re[e],this.stack&&(Object.defineProperty(this,"stack",{value:(new Error).stack,writable:!0}),this.stack=Se(this.stack))},Ie.ErrnoError.prototype=new Error,Ie.ErrnoError.prototype.constructor=Ie.ErrnoError,[44].forEach((e=>{Ie.genericErrors[e]=new Ie.ErrnoError(e),Ie.genericErrors[e].stack="<generic error, no stack>"})))},staticInit:()=>{Ie.ensureErrnoError(),Ie.nameTable=new Array(4096),Ie.mount(Me,{},"/"),Ie.createDefaultDirectories(),Ie.createDefaultDevices(),Ie.createSpecialDirectories(),Ie.filesystems={MEMFS:Me}},init:(e,t,n)=>{D(!Ie.init.initialized,"FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)"),Ie.init.initialized=!0,Ie.ensureErrnoError(),r.stdin=e||r.stdin,r.stdout=t||r.stdout,r.stderr=n||r.stderr,Ie.createStandardStreams()},quit:()=>{Ie.init.initialized=!1,wt();for(var e=0;e<Ie.streams.length;e++){var r=Ie.streams[e];r&&Ie.close(r)}},getMode:(e,r)=>{var t=0;return e&&(t|=365),r&&(t|=146),t},findObject:(e,r)=>{var t=Ie.analyzePath(e,r);return t.exists?t.object:null},analyzePath:(e,r)=>{try{e=(n=Ie.lookupPath(e,{follow:!r})).path}catch(e){}var t={isRoot:!1,exists:!1,error:0,name:null,path:null,object:null,parentExists:!1,parentPath:null,parentObject:null};try{var n=Ie.lookupPath(e,{parent:!0});t.parentExists=!0,t.parentPath=n.path,t.parentObject=n.node,t.name=Ae.basename(e),n=Ie.lookupPath(e,{follow:!r}),t.exists=!0,t.path=n.path,t.object=n.node,t.name=n.node.name,t.isRoot="/"===n.path}catch(e){t.error=e.errno}return t},createPath:(e,r,t,n)=>{e="string"==typeof e?e:Ie.getPath(e);for(var o=r.split("/").reverse();o.length;){var a=o.pop();if(a){var i=Ae.join2(e,a);try{Ie.mkdir(i)}catch(e){}e=i}}return i},createFile:(e,r,t,n,o)=>{var a=Ae.join2("string"==typeof e?e:Ie.getPath(e),r),i=Ie.getMode(n,o);return Ie.create(a,i)},createDataFile:(e,r,t,n,o,a)=>{var i=r;e&&(e="string"==typeof e?e:Ie.getPath(e),i=r?Ae.join2(e,r):e);var s=Ie.getMode(n,o),l=Ie.create(i,s);if(t){if("string"==typeof t){for(var u=new Array(t.length),c=0,d=t.length;c<d;++c)u[c]=t.charCodeAt(c);t=u}Ie.chmod(l,146|s);var f=Ie.open(l,577);Ie.write(f,t,0,t.length,0,a),Ie.close(f),Ie.chmod(l,s)}return l},createDevice:(e,r,t,n)=>{var o=Ae.join2("string"==typeof e?e:Ie.getPath(e),r),a=Ie.getMode(!!t,!!n);Ie.createDevice.major||(Ie.createDevice.major=64);var i=Ie.makedev(Ie.createDevice.major++,0);return Ie.registerDevice(i,{open:e=>{e.seekable=!1},close:e=>{n&&n.buffer&&n.buffer.length&&n(10)},read:(e,r,n,o,a)=>{for(var i=0,s=0;s<o;s++){var l;try{l=t()}catch(e){throw new Ie.ErrnoError(29)}if(void 0===l&&0===i)throw new Ie.ErrnoError(6);if(null==l)break;i++,r[n+s]=l}return i&&(e.node.timestamp=Date.now()),i},write:(e,r,t,o,a)=>{for(var i=0;i<o;i++)try{n(r[t+i])}catch(e){throw new Ie.ErrnoError(29)}return o&&(e.node.timestamp=Date.now()),i}}),Ie.mkdev(o,a,i)},forceLoadFile:e=>{if(e.isDevice||e.isFolder||e.link||e.contents)return!0;if("undefined"!=typeof XMLHttpRequest)throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");if(!d)throw new Error("Cannot load without read() or XMLHttpRequest.");try{e.contents=pt(d(e.url),!0),e.usedBytes=e.contents.length}catch(e){throw new Ie.ErrnoError(29)}},createLazyFile:(e,r,t,n,o)=>{function a(){this.lengthKnown=!1,this.chunks=[]}if(a.prototype.get=function(e){if(!(e>this.length-1||e<0)){var r=e%this.chunkSize,t=e/this.chunkSize|0;return this.getter(t)[r]}},a.prototype.setDataGetter=function(e){this.getter=e},a.prototype.cacheLength=function(){var e=new XMLHttpRequest;if(e.open("HEAD",t,!1),e.send(null),!(e.status>=200&&e.status<300||304===e.status))throw new Error("Couldn't load "+t+". Status: "+e.status);var r,n=Number(e.getResponseHeader("Content-length")),o=(r=e.getResponseHeader("Accept-Ranges"))&&"bytes"===r,a=(r=e.getResponseHeader("Content-Encoding"))&&"gzip"===r,i=1048576;o||(i=n);var s=this;s.setDataGetter((e=>{var r=e*i,o=(e+1)*i-1;if(o=Math.min(o,n-1),void 0===s.chunks[e]&&(s.chunks[e]=((e,r)=>{if(e>r)throw new Error("invalid range ("+e+", "+r+") or no bytes requested!");if(r>n-1)throw new Error("only "+n+" bytes available! programmer error!");var o=new XMLHttpRequest;if(o.open("GET",t,!1),n!==i&&o.setRequestHeader("Range","bytes="+e+"-"+r),o.responseType="arraybuffer",o.overrideMimeType&&o.overrideMimeType("text/plain; charset=x-user-defined"),o.send(null),!(o.status>=200&&o.status<300||304===o.status))throw new Error("Couldn't load "+t+". Status: "+o.status);return void 0!==o.response?new Uint8Array(o.response||[]):pt(o.responseText||"",!0)})(r,o)),void 0===s.chunks[e])throw new Error("doXHR failed!");return s.chunks[e]})),!a&&n||(i=n=1,n=this.getter(0).length,i=n,b("LazyFiles on gzip forces download of the whole file when length is accessed")),this._length=n,this._chunkSize=i,this.lengthKnown=!0},"undefined"!=typeof XMLHttpRequest){if(!l)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var i=new a;Object.defineProperties(i,{length:{get:function(){return this.lengthKnown||this.cacheLength(),this._length}},chunkSize:{get:function(){return this.lengthKnown||this.cacheLength(),this._chunkSize}}});var s={isDevice:!1,contents:i}}else s={isDevice:!1,url:t};var u=Ie.createFile(e,r,s,n,o);s.contents?u.contents=s.contents:s.url&&(u.contents=null,u.url=s.url),Object.defineProperties(u,{usedBytes:{get:function(){return this.contents.length}}});var c={};return Object.keys(u.stream_ops).forEach((e=>{var r=u.stream_ops[e];c[e]=function(){return Ie.forceLoadFile(u),r.apply(null,arguments)}})),c.read=(e,r,t,n,o)=>{Ie.forceLoadFile(u);var a=e.node.contents;if(o>=a.length)return 0;var i=Math.min(a.length-o,n);if(D(i>=0),a.slice)for(var s=0;s<i;s++)r[t+s]=a[o+s];else for(s=0;s<i;s++)r[t+s]=a.get(o+s);return i},u.stream_ops=c,u},createPreloadedFile:(e,r,t,n,o,a,i,s,l,u)=>{var c=r?De.resolve(Ae.join2(e,r)):e,d=pe("cp "+c);function p(t){function f(t){u&&u(),s||Ie.createDataFile(e,r,t,n,o,l),a&&a(),he(d)}Browser.handledByPreloadPlugin(t,c,f,(()=>{i&&i(),he(d)}))||f(t)}me(d),"string"==typeof t?function(e,r,t,n){var o=n?"":pe("al "+e);f(e,(function(t){D(t,'Loading data file "'+e+'" failed (no arrayBuffer).'),r(new Uint8Array(t)),o&&he(o)}),(function(r){if(!t)throw'Loading data file "'+e+'" failed.';t()})),o&&me(o)}(t,(e=>p(e)),i):p(t)},indexedDB:()=>window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB,DB_NAME:()=>"EM_FS_"+window.location.pathname,DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Ie.indexedDB();try{var o=n.open(Ie.DB_NAME(),Ie.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=()=>{b("creating db"),o.result.createObjectStore(Ie.DB_STORE_NAME)},o.onsuccess=()=>{var n=o.result.transaction([Ie.DB_STORE_NAME],"readwrite"),a=n.objectStore(Ie.DB_STORE_NAME),i=0,s=0,l=e.length;function u(){0==s?r():t()}e.forEach((e=>{var r=a.put(Ie.analyzePath(e).object.contents,e);r.onsuccess=()=>{++i+s==l&&u()},r.onerror=()=>{s++,i+s==l&&u()}})),n.onerror=t},o.onerror=t},loadFilesFromDB:(e,r,t)=>{r=r||(()=>{}),t=t||(()=>{});var n=Ie.indexedDB();try{var o=n.open(Ie.DB_NAME(),Ie.DB_VERSION)}catch(e){return t(e)}o.onupgradeneeded=t,o.onsuccess=()=>{var n=o.result;try{var a=n.transaction([Ie.DB_STORE_NAME],"readonly")}catch(e){return void t(e)}var i=a.objectStore(Ie.DB_STORE_NAME),s=0,l=0,u=e.length;function c(){0==l?r():t()}e.forEach((e=>{var r=i.get(e);r.onsuccess=()=>{Ie.analyzePath(e).exists&&Ie.unlink(e),Ie.createDataFile(Ae.dirname(e),Ae.basename(e),r.result,!0,!0,!0),++s+l==u&&c()},r.onerror=()=>{l++,s+l==u&&c()}})),a.onerror=t},o.onerror=t},absolutePath:()=>{ge("FS.absolutePath has been removed; use PATH_FS.resolve instead")},createFolder:()=>{ge("FS.createFolder has been removed; use FS.mkdir instead")},createLink:()=>{ge("FS.createLink has been removed; use FS.symlink instead")},joinPath:()=>{ge("FS.joinPath has been removed; use PATH.join instead")},mmapAlloc:()=>{ge("FS.mmapAlloc has been replaced by the top level function mmapAlloc")},standardizePath:()=>{ge("FS.standardizePath has been removed; use PATH.normalize instead")}},Le={DEFAULT_POLLMASK:5,calculateAt:function(e,r,t){if(Ae.isAbs(r))return r;var n;if(-100===e)n=Ie.cwd();else{var o=Ie.getStream(e);if(!o)throw new Ie.ErrnoError(8);n=o.path}if(0==r.length){if(!t)throw new Ie.ErrnoError(44);return n}return Ae.join2(n,r)},doStat:function(e,r,t){try{var n=e(r)}catch(e){if(e&&e.node&&Ae.normalize(r)!==Ae.normalize(Ie.getPath(e.node)))return-54;throw e}return $[t>>2]=n.dev,$[t+4>>2]=0,$[t+8>>2]=n.ino,$[t+12>>2]=n.mode,$[t+16>>2]=n.nlink,$[t+20>>2]=n.uid,$[t+24>>2]=n.gid,$[t+28>>2]=n.rdev,$[t+32>>2]=0,Ee=[n.size>>>0,(ye=n.size,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[t+40>>2]=Ee[0],$[t+44>>2]=Ee[1],$[t+48>>2]=4096,$[t+52>>2]=n.blocks,$[t+56>>2]=n.atime.getTime()/1e3|0,$[t+60>>2]=0,$[t+64>>2]=n.mtime.getTime()/1e3|0,$[t+68>>2]=0,$[t+72>>2]=n.ctime.getTime()/1e3|0,$[t+76>>2]=0,Ee=[n.ino>>>0,(ye=n.ino,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[t+80>>2]=Ee[0],$[t+84>>2]=Ee[1],0},doMsync:function(e,r,t,n,o){var a=U.slice(e,e+t);Ie.msync(r,a,o,t,n)},doMknod:function(e,r,t){switch(61440&r){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}return Ie.mknod(e,r,t),0},doReadlink:function(e,r,t){if(t<=0)return-28;var n=Ie.readlink(e),o=Math.min(t,I(n)),a=x[r+o];return N(n,r,t+1),x[r+o]=a,o},doAccess:function(e,r){if(-8&r)return-28;var t=Ie.lookupPath(e,{follow:!0}).node;if(!t)return-44;var n="";return 4&r&&(n+="r"),2&r&&(n+="w"),1&r&&(n+="x"),n&&Ie.nodePermissions(t,n)?-2:0},doReadv:function(e,r,t,n){for(var o=0,a=0;a<t;a++){var i=$[r>>2],s=$[r+4>>2];r+=8;var l=Ie.read(e,x,i,s,n);if(l<0)return-1;if(o+=l,l<s)break}return o},doWritev:function(e,r,t,n){for(var o=0,a=0;a<t;a++){var i=$[r>>2],s=$[r+4>>2];r+=8;var l=Ie.write(e,x,i,s,n);if(l<0)return-1;o+=l}return o},varargs:void 0,get:function(){return D(null!=Le.varargs),Le.varargs+=4,$[Le.varargs-4>>2]},getStr:function(e){return M(e)},getStreamFromFD:function(e){var r=Ie.getStream(e);if(!r)throw new Ie.ErrnoError(8);return r}};function xe(e){switch(e){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+e)}}var Ue=void 0;function Be(e){for(var r="",t=e;U[t];)r+=Ue[U[t++]];return r}var je={},$e={},We={};function ze(e){if(void 0===e)return"_unknown";var r=(e=e.replace(/[^a-zA-Z0-9_]/g,"$")).charCodeAt(0);return r>=48&&r<=57?"_"+e:e}function He(e,r){return e=ze(e),new Function("body","return function "+e+'() {\n "use strict"; return body.apply(this, arguments);\n};\n')(r)}function Ge(e,r){var t=He(r,(function(e){this.name=r,this.message=e;var t=new Error(e).stack;void 0!==t&&(this.stack=this.toString()+"\n"+t.replace(/^Error(:[^\n]*)?\n/,""))}));return t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message},t}var Ve=void 0;function Ye(e){throw new Ve(e)}var Xe=void 0;function qe(e){throw new Xe(e)}function Ke(e,r,t){function n(r){var n=t(r);n.length!==e.length&&qe("Mismatched type converter count");for(var o=0;o<e.length;++o)Je(e[o],n[o])}e.forEach((function(e){We[e]=r}));var o=new Array(r.length),a=[],i=0;r.forEach(((e,r)=>{$e.hasOwnProperty(e)?o[r]=$e[e]:(a.push(e),je.hasOwnProperty(e)||(je[e]=[]),je[e].push((()=>{o[r]=$e[e],++i===a.length&&n(o)})))})),0===a.length&&n(o)}function Je(e,r){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!("argPackAdvance"in r))throw new TypeError("registerType registeredInstance requires argPackAdvance");var n=r.name;if(e||Ye('type "'+n+'" must have a positive integer typeid pointer'),$e.hasOwnProperty(e)){if(t.ignoreDuplicateRegistrations)return;Ye("Cannot register type '"+n+"' twice")}if($e[e]=r,delete We[e],je.hasOwnProperty(e)){var o=je[e];delete je[e],o.forEach((e=>e()))}}function Qe(e){if(!(this instanceof wr))return!1;if(!(e instanceof wr))return!1;for(var r=this.$$.ptrType.registeredClass,t=this.$$.ptr,n=e.$$.ptrType.registeredClass,o=e.$$.ptr;r.baseClass;)t=r.upcast(t),r=r.baseClass;for(;n.baseClass;)o=n.upcast(o),n=n.baseClass;return r===n&&t===o}function Ze(e){Ye(e.$$.ptrType.registeredClass.name+" instance already deleted")}var er=!1;function rr(e){}function tr(e){e.count.value-=1,0===e.count.value&&function(e){e.smartPtr?e.smartPtrType.rawDestructor(e.smartPtr):e.ptrType.registeredClass.rawDestructor(e.ptr)}(e)}function nr(e,r,t){if(r===t)return e;if(void 0===t.baseClass)return null;var n=nr(e,r,t.baseClass);return null===n?null:t.downcast(n)}var or={};function ar(){return Object.keys(dr).length}function ir(){var e=[];for(var r in dr)dr.hasOwnProperty(r)&&e.push(dr[r]);return e}var sr=[];function lr(){for(;sr.length;){var e=sr.pop();e.$$.deleteScheduled=!1,e.delete()}}var ur=void 0;function cr(e){ur=e,sr.length&&ur&&ur(lr)}var dr={};function fr(e,r){return r=function(e,r){for(void 0===r&&Ye("ptr should not be undefined");e.baseClass;)r=e.upcast(r),e=e.baseClass;return r}(e,r),dr[r]}function pr(e,r){return r.ptrType&&r.ptr||qe("makeClassHandle requires ptr and ptrType"),!!r.smartPtrType!==!!r.smartPtr&&qe("Both smartPtrType and smartPtr must be specified"),r.count={value:1},hr(Object.create(e,{$$:{value:r}}))}function mr(e){var r=this.getPointee(e);if(!r)return this.destructor(e),null;var t=fr(this.registeredClass,r);if(void 0!==t){if(0===t.$$.count.value)return t.$$.ptr=r,t.$$.smartPtr=e,t.clone();var n=t.clone();return this.destructor(e),n}function o(){return this.isSmartPointer?pr(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:r,smartPtrType:this,smartPtr:e}):pr(this.registeredClass.instancePrototype,{ptrType:this,ptr:e})}var a,i=this.registeredClass.getActualType(r),s=or[i];if(!s)return o.call(this);a=this.isConst?s.constPointerType:s.pointerType;var l=nr(r,this.registeredClass,a.registeredClass);return null===l?o.call(this):this.isSmartPointer?pr(a.registeredClass.instancePrototype,{ptrType:a,ptr:l,smartPtrType:this,smartPtr:e}):pr(a.registeredClass.instancePrototype,{ptrType:a,ptr:l})}function hr(e){return"undefined"==typeof FinalizationRegistry?(hr=e=>e,e):(er=new FinalizationRegistry((e=>{console.warn(e.leakWarning.stack.replace(/^Error: /,"")),tr(e.$$)})),hr=e=>{var r=e.$$;if(!!r.smartPtr){var t={$$:r},n=r.ptrType.registeredClass;t.leakWarning=new Error("Embind found a leaked C++ instance "+n.name+" <0x"+r.ptr.toString(16)+">.\nWe'll free it automatically in this case, but this functionality is not reliable across various environments.\nMake sure to invoke .delete() manually once you're done with the instance instead.\nOriginally allocated"),"captureStackTrace"in Error&&Error.captureStackTrace(t.leakWarning,mr),er.register(e,t,e)}return e},rr=e=>er.unregister(e),hr(e))}function gr(){if(this.$$.ptr||Ze(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var e,r=hr(Object.create(Object.getPrototypeOf(this),{$$:{value:(e=this.$$,{count:e.count,deleteScheduled:e.deleteScheduled,preservePointerOnDelete:e.preservePointerOnDelete,ptr:e.ptr,ptrType:e.ptrType,smartPtr:e.smartPtr,smartPtrType:e.smartPtrType})}}));return r.$$.count.value+=1,r.$$.deleteScheduled=!1,r}function vr(){this.$$.ptr||Ze(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ye("Object already scheduled for deletion"),rr(this),tr(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)}function yr(){return!this.$$.ptr}function Er(){return this.$$.ptr||Ze(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&Ye("Object already scheduled for deletion"),sr.push(this),1===sr.length&&ur&&ur(lr),this.$$.deleteScheduled=!0,this}function wr(){}function br(e,r,t){if(void 0===e[r].overloadTable){var n=e[r];e[r]=function(){return e[r].overloadTable.hasOwnProperty(arguments.length)||Ye("Function '"+t+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+e[r].overloadTable+")!"),e[r].overloadTable[arguments.length].apply(this,arguments)},e[r].overloadTable=[],e[r].overloadTable[n.argCount]=n}}function _r(e,r,t,n,o,a,i,s){this.name=e,this.constructor=r,this.instancePrototype=t,this.rawDestructor=n,this.baseClass=o,this.getActualType=a,this.upcast=i,this.downcast=s,this.pureVirtualFunctions=[]}function Tr(e,r,t){for(;r!==t;)r.upcast||Ye("Expected null or instance of "+t.name+", got an instance of "+r.name),e=r.upcast(e),r=r.baseClass;return e}function kr(e,r){if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),0;r.$$||Ye('Cannot pass "'+Xr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name);var t=r.$$.ptrType.registeredClass;return Tr(r.$$.ptr,t,this.registeredClass)}function Sr(e,r){var t;if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),this.isSmartPointer?(t=this.rawConstructor(),null!==e&&e.push(this.rawDestructor,t),t):0;r.$$||Ye('Cannot pass "'+Xr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name),!this.isConst&&r.$$.ptrType.isConst&&Ye("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);var n=r.$$.ptrType.registeredClass;if(t=Tr(r.$$.ptr,n,this.registeredClass),this.isSmartPointer)switch(void 0===r.$$.smartPtr&&Ye("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:r.$$.smartPtrType===this?t=r.$$.smartPtr:Ye("Cannot convert argument of type "+(r.$$.smartPtrType?r.$$.smartPtrType.name:r.$$.ptrType.name)+" to parameter type "+this.name);break;case 1:t=r.$$.smartPtr;break;case 2:if(r.$$.smartPtrType===this)t=r.$$.smartPtr;else{var o=r.clone();t=this.rawShare(t,Yr.toHandle((function(){o.delete()}))),null!==e&&e.push(this.rawDestructor,t)}break;default:Ye("Unsupporting sharing policy")}return t}function Cr(e,r){if(null===r)return this.isReference&&Ye("null is not a valid "+this.name),0;r.$$||Ye('Cannot pass "'+Xr(r)+'" as a '+this.name),r.$$.ptr||Ye("Cannot pass deleted object as a pointer of type "+this.name),r.$$.ptrType.isConst&&Ye("Cannot convert argument of type "+r.$$.ptrType.name+" to parameter type "+this.name);var t=r.$$.ptrType.registeredClass;return Tr(r.$$.ptr,t,this.registeredClass)}function Pr(e){return this.fromWireType(W[e>>2])}function Ar(e){return this.rawGetPointee&&(e=this.rawGetPointee(e)),e}function Dr(e){this.rawDestructor&&this.rawDestructor(e)}function Or(e){null!==e&&e.delete()}function Fr(e,r,t,n,o,a,i,s,l,u,c){this.name=e,this.registeredClass=r,this.isReference=t,this.isConst=n,this.isSmartPointer=o,this.pointeeType=a,this.sharingPolicy=i,this.rawGetPointee=s,this.rawConstructor=l,this.rawShare=u,this.rawDestructor=c,o||void 0!==r.baseClass?this.toWireType=Sr:n?(this.toWireType=kr,this.destructorFunction=null):(this.toWireType=Cr,this.destructorFunction=null)}function Mr(e,t,n){return e.includes("j")?function(e,t,n){D("dynCall_"+e in r,"bad function pointer type - no table for sig '"+e+"'"),n&&n.length?D(n.length===e.substring(1).replace(/j/g,"--").length):D(1==e.length);var o=r["dynCall_"+e];return n&&n.length?o.apply(null,[t].concat(n)):o.call(null,t)}(e,t,n):(D(Ce(t),"missing table entry in dynCall: "+t),Ce(t).apply(null,n))}function Rr(e,r){var t=(e=Be(e)).includes("j")?function(e,r){D(e.includes("j"),"getDynCaller should only be called with i64 sigs");var t=[];return function(){return t.length=0,Object.assign(t,arguments),Mr(e,r,t)}}(e,r):Ce(r);return"function"!=typeof t&&Ye("unknown function pointer with signature "+e+": "+r),t}var Nr=void 0;function Ir(e){var r=Et(e),t=Be(r);return ht(r),t}function Lr(e,r){var t=[],n={};throw r.forEach((function e(r){n[r]||$e[r]||(We[r]?We[r].forEach(e):(t.push(r),n[r]=!0))})),new Nr(e+": "+t.map(Ir).join([", "]))}function xr(e,r){for(var t=[],n=0;n<e;n++)t.push($[(r>>2)+n]);return t}function Ur(e){for(;e.length;){var r=e.pop();e.pop()(r)}}function Br(e,r){if(!(e instanceof Function))throw new TypeError("new_ called with constructor type "+typeof e+" which is not a function");var t=He(e.name||"unknownFunctionName",(function(){}));t.prototype=e.prototype;var n=new t,o=e.apply(n,r);return o instanceof Object?o:n}function jr(e,r,t,n,o){var a=r.length;a<2&&Ye("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var i=null!==r[1]&&null!==t,s=!1,l=1;l<r.length;++l)if(null!==r[l]&&void 0===r[l].destructorFunction){s=!0;break}var u="void"!==r[0].name,c="",d="";for(l=0;l<a-2;++l)c+=(0!==l?", ":"")+"arg"+l,d+=(0!==l?", ":"")+"arg"+l+"Wired";var f="return function "+ze(e)+"("+c+") {\nif (arguments.length !== "+(a-2)+") {\nthrowBindingError('function "+e+" called with ' + arguments.length + ' arguments, expected "+(a-2)+" args!');\n}\n";s&&(f+="var destructors = [];\n");var p=s?"destructors":"null",m=["throwBindingError","invoker","fn","runDestructors","retType","classParam"],h=[Ye,n,o,Ur,r[0],r[1]];i&&(f+="var thisWired = classParam.toWireType("+p+", this);\n");for(l=0;l<a-2;++l)f+="var arg"+l+"Wired = argType"+l+".toWireType("+p+", arg"+l+"); // "+r[l+2].name+"\n",m.push("argType"+l),h.push(r[l+2]);if(i&&(d="thisWired"+(d.length>0?", ":"")+d),f+=(u?"var rv = ":"")+"invoker(fn"+(d.length>0?", ":"")+d+");\n",s)f+="runDestructors(destructors);\n";else for(l=i?1:2;l<r.length;++l){var g=1===l?"thisWired":"arg"+(l-2)+"Wired";null!==r[l].destructorFunction&&(f+=g+"_dtor("+g+"); // "+r[l].name+"\n",m.push(g+"_dtor"),h.push(r[l].destructorFunction))}return u&&(f+="var ret = retType.fromWireType(rv);\nreturn ret;\n"),f+="}\n",m.push(f),Br(Function,m).apply(null,h)}function $r(e,r,t){return e instanceof Object||Ye(t+' with invalid "this": '+e),e instanceof r.registeredClass.constructor||Ye(t+' incompatible with "this" of type '+e.constructor.name),e.$$.ptr||Ye("cannot call emscripten binding method "+t+" on deleted object"),Tr(e.$$.ptr,e.$$.ptrType.registeredClass,r.registeredClass)}var Wr=[],zr=[{},{value:void 0},{value:null},{value:!0},{value:!1}];function Hr(e){e>4&&0==--zr[e].refcount&&(zr[e]=void 0,Wr.push(e))}function Gr(){for(var e=0,r=5;r<zr.length;++r)void 0!==zr[r]&&++e;return e}function Vr(){for(var e=5;e<zr.length;++e)if(void 0!==zr[e])return zr[e];return null}var Yr={toValue:e=>(e||Ye("Cannot use deleted val. handle = "+e),zr[e].value),toHandle:e=>{switch(e){case void 0:return 1;case null:return 2;case!0:return 3;case!1:return 4;default:var r=Wr.length?Wr.pop():zr.length;return zr[r]={refcount:1,value:e},r}}};function Xr(e){if(null===e)return"null";var r=typeof e;return"object"===r||"array"===r||"function"===r?e.toString():""+e}function qr(e,r){switch(r){case 2:return function(e){return this.fromWireType(z[e>>2])};case 3:return function(e){return this.fromWireType(H[e>>3])};default:throw new TypeError("Unknown float type: "+e)}}function Kr(e,r,t){switch(r){case 0:return t?function(e){return x[e]}:function(e){return U[e]};case 1:return t?function(e){return B[e>>1]}:function(e){return j[e>>1]};case 2:return t?function(e){return $[e>>2]}:function(e){return W[e>>2]};default:throw new TypeError("Unknown integer type: "+e)}}function Jr(e,r){var t=$e[e];return void 0===t&&Ye(r+" has unknown type "+Ir(e)),t}var Qr={};var Zr=[];var et=[];function rt(e,r){return D(r===(0|r)),(e>>>0)+4294967296*r}function tt(e,r){if(e<=0)return e;var t=r<=32?Math.abs(1<<r-1):Math.pow(2,r-1);return e>=t&&(r<=32||e>t)&&(e=-2*t+e),e}function nt(e,r){return e>=0?e:r<=32?2*Math.abs(1<<r-1)+e:Math.pow(2,r)+e}function ot(e,r){D(0==(3&r));var t=e,n=r;function o(e){var r;return n=function(e,r){return"double"===r||"i64"===r?7&e&&(D(4==(7&e)),e+=4):D(0==(3&e)),e}(n,e),"double"===e?(r=Number(H[n>>3]),n+=8):"i64"==e?(r=[$[n>>2],$[n+4>>2]],n+=8):(D(0==(3&n)),e="i32",r=$[n>>2],n+=4),r}for(var a,i,s,l,u,c,d=[];;){var f=t;if(0===(a=x[t>>0]))break;if(i=x[t+1>>0],37==a){var p=!1,m=!1,h=!1,g=!1,v=!1;e:for(;;){switch(i){case 43:p=!0;break;case 45:m=!0;break;case 35:h=!0;break;case 48:if(g)break e;g=!0;break;case 32:v=!0;break;default:break e}t++,i=x[t+1>>0]}var y=0;if(42==i)y=o("i32"),t++,i=x[t+1>>0];else for(;i>=48&&i<=57;)y=10*y+(i-48),t++,i=x[t+1>>0];var E,w=!1,b=-1;if(46==i){if(b=0,w=!0,t++,42==(i=x[t+1>>0]))b=o("i32"),t++;else for(;;){var _=x[t+1>>0];if(_<48||_>57)break;b=10*b+(_-48),t++}i=x[t+1>>0]}switch(b<0&&(b=6,w=!1),String.fromCharCode(i)){case"h":104==x[t+2>>0]?(t++,E=1):E=2;break;case"l":108==x[t+2>>0]?(t++,E=8):E=4;break;case"L":case"q":case"j":E=8;break;case"z":case"t":case"I":E=4;break;default:E=null}switch(E&&t++,i=x[t+1>>0],String.fromCharCode(i)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":var T=100==i||105==i;if(s=o("i"+8*(E=E||4)),8==E&&(s=117==i?(u=s[0],c=s[1],(u>>>0)+4294967296*(c>>>0)):rt(s[0],s[1])),E<=4)s=(T?tt:nt)(s&Math.pow(256,E)-1,8*E);var k=Math.abs(s),S="";if(100==i||105==i)A=tt(s,8*E).toString(10);else if(117==i)A=nt(s,8*E).toString(10),s=Math.abs(s);else if(111==i)A=(h?"0":"")+k.toString(8);else if(120==i||88==i){if(S=h&&0!=s?"0x":"",s<0){s=-s,A=(k-1).toString(16);for(var C=[],P=0;P<A.length;P++)C.push((15-parseInt(A[P],16)).toString(16));for(A=C.join("");A.length<2*E;)A="f"+A}else A=k.toString(16);88==i&&(S=S.toUpperCase(),A=A.toUpperCase())}else 112==i&&(0===k?A="(nil)":(S="0x",A=k.toString(16)));if(w)for(;A.length<b;)A="0"+A;for(s>=0&&(p?S="+"+S:v&&(S=" "+S)),"-"==A.charAt(0)&&(S="-"+S,A=A.substr(1));S.length+A.length<y;)m?A+=" ":g?A="0"+A:S=" "+S;(A=S+A).split("").forEach((function(e){d.push(e.charCodeAt(0))}));break;case"f":case"F":case"e":case"E":case"g":case"G":var A;if(s=o("double"),isNaN(s))A="nan",g=!1;else if(isFinite(s)){var O=!1,F=Math.min(b,20);if(103==i||71==i){O=!0,b=b||1;var M=parseInt(s.toExponential(F).split("e")[1],10);b>M&&M>=-4?(i=(103==i?"f":"F").charCodeAt(0),b-=M+1):(i=(103==i?"e":"E").charCodeAt(0),b--),F=Math.min(b,20)}101==i||69==i?(A=s.toExponential(F),/[eE][-+]\d$/.test(A)&&(A=A.slice(0,-1)+"0"+A.slice(-1))):102!=i&&70!=i||(A=s.toFixed(F),0===s&&((l=s)<0||0===l&&1/l==-1/0)&&(A="-"+A));var R=A.split("e");if(O&&!h)for(;R[0].length>1&&R[0].includes(".")&&("0"==R[0].slice(-1)||"."==R[0].slice(-1));)R[0]=R[0].slice(0,-1);else for(h&&-1==A.indexOf(".")&&(R[0]+=".");b>F++;)R[0]+="0";A=R[0]+(R.length>1?"e"+R[1]:""),69==i&&(A=A.toUpperCase()),s>=0&&(p?A="+"+A:v&&(A=" "+A))}else A=(s<0?"-":"")+"inf",g=!1;for(;A.length<y;)m?A+=" ":A=!g||"-"!=A[0]&&"+"!=A[0]?(g?"0":" ")+A:A[0]+"0"+A.slice(1);i<97&&(A=A.toUpperCase()),A.split("").forEach((function(e){d.push(e.charCodeAt(0))}));break;case"s":var N=o("i8*"),I=N?vt(N):"(null)".length;if(w&&(I=Math.min(I,b)),!m)for(;I<y--;)d.push(32);if(N)for(P=0;P<I;P++)d.push(U[N++>>0]);else d=d.concat(pt("(null)".substr(0,I),!0));if(m)for(;I<y--;)d.push(32);break;case"c":for(m&&d.push(o("i8"));--y>0;)d.push(32);m||d.push(o("i8"));break;case"n":var L=o("i32*");$[L>>2]=d.length;break;case"%":d.push(a);break;default:for(P=f;P<t+2;P++)d.push(x[P>>0])}t+=2}else d.push(a),t+=1}return d}function at(e){if(!e||!e.callee||!e.callee.name)return[null,"",""];e.callee.toString();var r=e.callee.name,t="(",n=!0;for(var o in e){var a=e[o];n||(t+=", "),n=!1,t+="number"==typeof a||"string"==typeof a?a:"("+typeof a+")"}t+=")";var i=e.callee.caller;return n&&(t=""),[e=i?i.arguments:[],r,t]}function it(e,r){24&e&&(r=r.replace(/\s+$/,""),r+=(r.length>0?"\n":"")+function(e){var r=Pe(),t=r.lastIndexOf("_emscripten_log"),n=r.lastIndexOf("_emscripten_get_callstack"),o=r.indexOf("\n",Math.max(t,n))+1;r=r.slice(o),32&e&&T("EM_LOG_DEMANGLE is deprecated; ignoring"),8&e&&"undefined"==typeof emscripten_source_map&&(T('Source map information is not available, emscripten_log with EM_LOG_C_STACK will be ignored. Build with "--pre-js $EMSCRIPTEN/src/emscripten-source-map.min.js" linker flag to add source map loading to code.'),e^=8,e|=16);var a=null;if(128&e)for(a=at(arguments);a[1].includes("_emscripten_");)a=at(a[0]);var i=r.split("\n");r="";var s=new RegExp("\\s*(.*?)@(.*?):([0-9]+):([0-9]+)"),l=new RegExp("\\s*(.*?)@(.*):(.*)(:(.*))?"),u=new RegExp("\\s*at (.*?) \\((.*):(.*):(.*)\\)");for(var c in i){var d=i[c],f="",p="",m=0,h=0,g=u.exec(d);if(g&&5==g.length)f=g[1],p=g[2],m=g[3],h=g[4];else{if((g=s.exec(d))||(g=l.exec(d)),!(g&&g.length>=4)){r+=d+"\n";continue}f=g[1],p=g[2],m=g[3],h=0|g[4]}var v=!1;if(8&e){var y=emscripten_source_map.originalPositionFor({line:m,column:h});(v=y&&y.source)&&(64&e&&(y.source=y.source.substring(y.source.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=" at "+f+" ("+y.source+":"+y.line+":"+y.column+")\n")}(16&e||!v)&&(64&e&&(p=p.substring(p.replace(/\\/g,"/").lastIndexOf("/")+1)),r+=(v?" = "+f:" at "+f)+" ("+p+":"+m+":"+h+")\n"),128&e&&a[0]&&(a[1]==f&&a[2].length>0&&(r=r.replace(/\s+$/,""),r+=" with values: "+a[1]+a[2]+"\n"),a=at(a[0]))}return r.replace(/\s+$/,"")}(e)),1&e?4&e?console.error(r):2&e?console.warn(r):512&e?console.info(r):256&e?console.debug(r):console.log(r):6&e?_(r):b(r)}function st(e){try{return w.grow(e-L.byteLength+65535>>>16),Z(w.buffer),1}catch(r){_("emscripten_realloc_buffer: Attempted to grow heap from "+L.byteLength+" bytes to "+e+" bytes, but got error: "+r)}}var lt={};function ut(){if(!ut.strings){var e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:n||"./this.program"};for(var r in lt)void 0===lt[r]?delete e[r]:e[r]=lt[r];var t=[];for(var r in e)t.push(r+"="+e[r]);ut.strings=t}return ut.strings}var ct=function(e,r,t,n){e||(e=this),this.parent=e,this.mount=e.mount,this.mounted=null,this.id=Ie.nextInode++,this.name=r,this.mode=t,this.node_ops={},this.stream_ops={},this.rdev=n},dt=365,ft=146;function pt(e,r,t){var n=t>0?t:I(e)+1,o=new Array(n),a=R(e,o,0,o.length);return r&&(o.length=a),o}Object.defineProperties(ct.prototype,{read:{get:function(){return(this.mode&dt)===dt},set:function(e){e?this.mode|=dt:this.mode&=-366}},write:{get:function(){return(this.mode&ft)===ft},set:function(e){e?this.mode|=ft:this.mode&=-147}},isFolder:{get:function(){return Ie.isDir(this.mode)}},isDevice:{get:function(){return Ie.isChrdev(this.mode)}}}),Ie.FSNode=ct,Ie.staticInit(),Ne={EPERM:63,ENOENT:44,ESRCH:71,EINTR:27,EIO:29,ENXIO:60,E2BIG:1,ENOEXEC:45,EBADF:8,ECHILD:12,EAGAIN:6,EWOULDBLOCK:6,ENOMEM:48,EACCES:2,EFAULT:21,ENOTBLK:105,EBUSY:10,EEXIST:20,EXDEV:75,ENODEV:43,ENOTDIR:54,EISDIR:31,EINVAL:28,ENFILE:41,EMFILE:33,ENOTTY:59,ETXTBSY:74,EFBIG:22,ENOSPC:51,ESPIPE:70,EROFS:69,EMLINK:34,EPIPE:64,EDOM:18,ERANGE:68,ENOMSG:49,EIDRM:24,ECHRNG:106,EL2NSYNC:156,EL3HLT:107,EL3RST:108,ELNRNG:109,EUNATCH:110,ENOCSI:111,EL2HLT:112,EDEADLK:16,ENOLCK:46,EBADE:113,EBADR:114,EXFULL:115,ENOANO:104,EBADRQC:103,EBADSLT:102,EDEADLOCK:16,EBFONT:101,ENOSTR:100,ENODATA:116,ETIME:117,ENOSR:118,ENONET:119,ENOPKG:120,EREMOTE:121,ENOLINK:47,EADV:122,ESRMNT:123,ECOMM:124,EPROTO:65,EMULTIHOP:36,EDOTDOT:125,EBADMSG:9,ENOTUNIQ:126,EBADFD:127,EREMCHG:128,ELIBACC:129,ELIBBAD:130,ELIBSCN:131,ELIBMAX:132,ELIBEXEC:133,ENOSYS:52,ENOTEMPTY:55,ENAMETOOLONG:37,ELOOP:32,EOPNOTSUPP:138,EPFNOSUPPORT:139,ECONNRESET:15,ENOBUFS:42,EAFNOSUPPORT:5,EPROTOTYPE:67,ENOTSOCK:57,ENOPROTOOPT:50,ESHUTDOWN:140,ECONNREFUSED:14,EADDRINUSE:3,ECONNABORTED:13,ENETUNREACH:40,ENETDOWN:38,ETIMEDOUT:73,EHOSTDOWN:142,EHOSTUNREACH:23,EINPROGRESS:26,EALREADY:7,EDESTADDRREQ:17,EMSGSIZE:35,EPROTONOSUPPORT:66,ESOCKTNOSUPPORT:137,EADDRNOTAVAIL:4,ENETRESET:39,EISCONN:30,ENOTCONN:53,ETOOMANYREFS:141,EUSERS:136,EDQUOT:19,ESTALE:72,ENOTSUP:138,ENOMEDIUM:148,EILSEQ:25,EOVERFLOW:61,ECANCELED:11,ENOTRECOVERABLE:56,EOWNERDEAD:62,ESTRPIPE:135},function(){for(var e=new Array(256),r=0;r<256;++r)e[r]=String.fromCharCode(r);Ue=e}(),Ve=r.BindingError=Ge(Error,"BindingError"),Xe=r.InternalError=Ge(Error,"InternalError"),wr.prototype.isAliasOf=Qe,wr.prototype.clone=gr,wr.prototype.delete=vr,wr.prototype.isDeleted=yr,wr.prototype.deleteLater=Er,r.getInheritedInstanceCount=ar,r.getLiveInheritedInstances=ir,r.flushPendingDeletes=lr,r.setDelayFunction=cr,Fr.prototype.getPointee=Ar,Fr.prototype.destructor=Dr,Fr.prototype.argPackAdvance=8,Fr.prototype.readValueFromPointer=Pr,Fr.prototype.deleteObject=Or,Fr.prototype.fromWireType=mr,Nr=r.UnboundTypeError=Ge(Error,"UnboundTypeError"),r.count_emval_handles=Gr,r.get_first_emval=Vr;var mt={__syscall_fcntl64:function(e,r,t){Le.varargs=t;try{var n=Le.getStreamFromFD(e);switch(r){case 0:return(o=Le.get())<0?-28:Ie.createStream(n,o).fd;case 1:case 2:case 6:case 7:return 0;case 3:return n.flags;case 4:var o=Le.get();return n.flags|=o,0;case 5:o=Le.get();return B[o+0>>1]=2,0;case 16:case 8:default:return-28;case 9:return a=28,$[yt()>>2]=a,-1}}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return-e.errno}var a},__syscall_openat:function(e,r,t,n){Le.varargs=n;try{r=Le.getStr(r),r=Le.calculateAt(e,r);var o=n?Le.get():0;return Ie.open(r,t,o).fd}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return-e.errno}},_embind_register_bigint:function(e,r,t,n,o){},_embind_register_bool:function(e,r,t,n,o){var a=xe(t);Je(e,{name:r=Be(r),fromWireType:function(e){return!!e},toWireType:function(e,r){return r?n:o},argPackAdvance:8,readValueFromPointer:function(e){var n;if(1===t)n=x;else if(2===t)n=B;else{if(4!==t)throw new TypeError("Unknown boolean type size: "+r);n=$}return this.fromWireType(n[e>>a])},destructorFunction:null})},_embind_register_class:function(e,t,n,o,a,i,s,l,u,c,d,f,p){d=Be(d),i=Rr(a,i),l&&(l=Rr(s,l)),c&&(c=Rr(u,c)),p=Rr(f,p);var m=ze(d);!function(e,t,n){r.hasOwnProperty(e)?((void 0===n||void 0!==r[e].overloadTable&&void 0!==r[e].overloadTable[n])&&Ye("Cannot register public name '"+e+"' twice"),br(r,e,e),r.hasOwnProperty(n)&&Ye("Cannot register multiple overloads of a function with the same number of arguments ("+n+")!"),r[e].overloadTable[n]=t):(r[e]=t,void 0!==n&&(r[e].numArguments=n))}(m,(function(){Lr("Cannot construct "+d+" due to unbound types",[o])})),Ke([e,t,n],o?[o]:[],(function(t){var n,a;t=t[0],a=o?(n=t.registeredClass).instancePrototype:wr.prototype;var s=He(m,(function(){if(Object.getPrototypeOf(this)!==u)throw new Ve("Use 'new' to construct "+d);if(void 0===f.constructor_body)throw new Ve(d+" has no accessible constructor");var e=f.constructor_body[arguments.length];if(void 0===e)throw new Ve("Tried to invoke ctor of "+d+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(f.constructor_body).toString()+") parameters instead!");return e.apply(this,arguments)})),u=Object.create(a,{constructor:{value:s}});s.prototype=u;var f=new _r(d,s,u,p,n,i,l,c),h=new Fr(d,f,!0,!1,!1),g=new Fr(d+"*",f,!1,!1,!1),v=new Fr(d+" const*",f,!1,!0,!1);return or[e]={pointerType:g,constPointerType:v},function(e,t,n){r.hasOwnProperty(e)||qe("Replacing nonexistant public symbol"),void 0!==r[e].overloadTable&&void 0!==n?r[e].overloadTable[n]=t:(r[e]=t,r[e].argCount=n)}(m,s),[h,g,v]}))},_embind_register_class_constructor:function(e,r,t,n,o,a){D(r>0);var i=xr(r,t);o=Rr(n,o),Ke([],[e],(function(e){var t="constructor "+(e=e[0]).name;if(void 0===e.registeredClass.constructor_body&&(e.registeredClass.constructor_body=[]),void 0!==e.registeredClass.constructor_body[r-1])throw new Ve("Cannot register multiple constructors with identical number of parameters ("+(r-1)+") for class '"+e.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!");return e.registeredClass.constructor_body[r-1]=()=>{Lr("Cannot construct "+e.name+" due to unbound types",i)},Ke([],i,(function(n){return n.splice(1,0,null),e.registeredClass.constructor_body[r-1]=jr(t,n,null,o,a),[]})),[]}))},_embind_register_class_function:function(e,r,t,n,o,a,i,s){var l=xr(t,n);r=Be(r),a=Rr(o,a),Ke([],[e],(function(e){var n=(e=e[0]).name+"."+r;function o(){Lr("Cannot call "+n+" due to unbound types",l)}r.startsWith("@@")&&(r=Symbol[r.substring(2)]),s&&e.registeredClass.pureVirtualFunctions.push(r);var u=e.registeredClass.instancePrototype,c=u[r];return void 0===c||void 0===c.overloadTable&&c.className!==e.name&&c.argCount===t-2?(o.argCount=t-2,o.className=e.name,u[r]=o):(br(u,r,n),u[r].overloadTable[t-2]=o),Ke([],l,(function(o){var s=jr(n,o,e,a,i);return void 0===u[r].overloadTable?(s.argCount=t-2,u[r]=s):u[r].overloadTable[t-2]=s,[]})),[]}))},_embind_register_class_property:function(e,r,t,n,o,a,i,s,l,u){r=Be(r),o=Rr(n,o),Ke([],[e],(function(e){var n=(e=e[0]).name+"."+r,c={get:function(){Lr("Cannot access "+n+" due to unbound types",[t,i])},enumerable:!0,configurable:!0};return c.set=l?()=>{Lr("Cannot access "+n+" due to unbound types",[t,i])}:e=>{Ye(n+" is a read-only property")},Object.defineProperty(e.registeredClass.instancePrototype,r,c),Ke([],l?[t,i]:[t],(function(t){var i=t[0],c={get:function(){var r=$r(this,e,n+" getter");return i.fromWireType(o(a,r))},enumerable:!0};if(l){l=Rr(s,l);var d=t[1];c.set=function(r){var t=$r(this,e,n+" setter"),o=[];l(u,t,d.toWireType(o,r)),Ur(o)}}return Object.defineProperty(e.registeredClass.instancePrototype,r,c),[]})),[]}))},_embind_register_emval:function(e,r){Je(e,{name:r=Be(r),fromWireType:function(e){var r=Yr.toValue(e);return Hr(e),r},toWireType:function(e,r){return Yr.toHandle(r)},argPackAdvance:8,readValueFromPointer:Pr,destructorFunction:null})},_embind_register_float:function(e,r,t){var n=xe(t);Je(e,{name:r=Be(r),fromWireType:function(e){return e},toWireType:function(e,r){if("number"!=typeof r&&"boolean"!=typeof r)throw new TypeError('Cannot convert "'+Xr(r)+'" to '+this.name);return r},argPackAdvance:8,readValueFromPointer:qr(r,n),destructorFunction:null})},_embind_register_integer:function(e,r,t,n,o){r=Be(r),-1===o&&(o=4294967295);var a=xe(t),i=e=>e;if(0===n){var s=32-8*t;i=e=>e<<s>>>s}var l=r.includes("unsigned"),u=(e,t)=>{if("number"!=typeof e&&"boolean"!=typeof e)throw new TypeError('Cannot convert "'+Xr(e)+'" to '+t);if(e<n||e>o)throw new TypeError('Passing a number "'+Xr(e)+'" from JS side to C/C++ side to an argument of type "'+r+'", which is outside the valid range ['+n+", "+o+"]!")};Je(e,{name:r,fromWireType:i,toWireType:l?function(e,r){return u(r,this.name),r>>>0}:function(e,r){return u(r,this.name),r},argPackAdvance:8,readValueFromPointer:Kr(r,a,0!==n),destructorFunction:null})},_embind_register_memory_view:function(e,r,t){var n=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][r];function o(e){var r=W,t=r[e>>=2],o=r[e+1];return new n(L,o,t)}Je(e,{name:t=Be(t),fromWireType:o,argPackAdvance:8,readValueFromPointer:o},{ignoreDuplicateRegistrations:!0})},_embind_register_std_string:function(e,r){var t="std::string"===(r=Be(r));Je(e,{name:r,fromWireType:function(e){var r,n=W[e>>2];if(t)for(var o=e+4,a=0;a<=n;++a){var i=e+4+a;if(a==n||0==U[i]){var s=M(o,i-o);void 0===r?r=s:(r+=String.fromCharCode(0),r+=s),o=i+1}}else{var l=new Array(n);for(a=0;a<n;++a)l[a]=String.fromCharCode(U[e+4+a]);r=l.join("")}return ht(e),r},toWireType:function(e,r){r instanceof ArrayBuffer&&(r=new Uint8Array(r));var n="string"==typeof r;n||r instanceof Uint8Array||r instanceof Uint8ClampedArray||r instanceof Int8Array||Ye("Cannot pass non-string to std::string");var o=(t&&n?()=>I(r):()=>r.length)(),a=gt(4+o+1);if(W[a>>2]=o,t&&n)N(r,a+4,o+1);else if(n)for(var i=0;i<o;++i){var s=r.charCodeAt(i);s>255&&(ht(a),Ye("String has UTF-16 code units that do not fit in 8 bits")),U[a+4+i]=s}else for(i=0;i<o;++i)U[a+4+i]=r[i];return null!==e&&e.push(ht,a),a},argPackAdvance:8,readValueFromPointer:Pr,destructorFunction:function(e){ht(e)}})},_embind_register_std_wstring:function(e,r,t){var n,o,a,i,s;t=Be(t),2===r?(n=V,o=Y,i=X,a=()=>j,s=1):4===r&&(n=q,o=K,i=J,a=()=>W,s=2),Je(e,{name:t,fromWireType:function(e){for(var t,o=W[e>>2],i=a(),l=e+4,u=0;u<=o;++u){var c=e+4+u*r;if(u==o||0==i[c>>s]){var d=n(l,c-l);void 0===t?t=d:(t+=String.fromCharCode(0),t+=d),l=c+r}}return ht(e),t},toWireType:function(e,n){"string"!=typeof n&&Ye("Cannot pass non-string to C++ string type "+t);var a=i(n),l=gt(4+a+r);return W[l>>2]=a>>s,o(n,l+4,a+r),null!==e&&e.push(ht,l),l},argPackAdvance:8,readValueFromPointer:Pr,destructorFunction:function(e){ht(e)}})},_embind_register_void:function(e,r){Je(e,{isVoid:!0,name:r=Be(r),argPackAdvance:0,fromWireType:function(){},toWireType:function(e,r){}})},_emscripten_date_now:function(){return Date.now()},_emval_as:function(e,r,t){e=Yr.toValue(e),r=Jr(r,"emval::as");var n=[],o=Yr.toHandle(n);return $[t>>2]=o,r.toWireType(n,e)},_emval_call_void_method:function(e,r,t,n){var o,a;(e=Zr[e])(r=Yr.toValue(r),t=void 0===(a=Qr[o=t])?Be(o):a,null,n)},_emval_decref:Hr,_emval_get_method_caller:function(e,r){var t=function(e,r){for(var t=new Array(e),n=0;n<e;++n)t[n]=Jr($[(r>>2)+n],"parameter "+n);return t}(e,r),n=t[0],o=n.name+"_$"+t.slice(1).map((function(e){return e.name})).join("_")+"$",a=et[o];if(void 0!==a)return a;for(var i=["retType"],s=[n],l="",u=0;u<e-1;++u)l+=(0!==u?", ":"")+"arg"+u,i.push("argType"+u),s.push(t[1+u]);var c="return function "+ze("methodCaller_"+o)+"(handle, name, destructors, args) {\n",d=0;for(u=0;u<e-1;++u)c+=" var arg"+u+" = argType"+u+".readValueFromPointer(args"+(d?"+"+d:"")+");\n",d+=t[u+1].argPackAdvance;for(c+=" var rv = handle[name]("+l+");\n",u=0;u<e-1;++u)t[u+1].deleteObject&&(c+=" argType"+u+".deleteObject(arg"+u+");\n");n.isVoid||(c+=" return retType.toWireType(destructors, rv);\n"),c+="};\n",i.push(c);var f,p,m=Br(Function,i).apply(null,s);return f=m,p=Zr.length,Zr.push(f),a=p,et[o]=a,a},_emval_incref:function(e){e>4&&(zr[e].refcount+=1)},_emval_run_destructors:function(e){Ur(Yr.toValue(e)),Hr(e)},_emval_take_value:function(e,r){var t=(e=Jr(e,"_emval_take_value")).readValueFromPointer(r);return Yr.toHandle(t)},_gmtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getUTCSeconds(),$[r+4>>2]=t.getUTCMinutes(),$[r+8>>2]=t.getUTCHours(),$[r+12>>2]=t.getUTCDate(),$[r+16>>2]=t.getUTCMonth(),$[r+20>>2]=t.getUTCFullYear()-1900,$[r+24>>2]=t.getUTCDay();var n=Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0),o=(t.getTime()-n)/864e5|0;$[r+28>>2]=o},_localtime_js:function(e,r){var t=new Date(1e3*$[e>>2]);$[r>>2]=t.getSeconds(),$[r+4>>2]=t.getMinutes(),$[r+8>>2]=t.getHours(),$[r+12>>2]=t.getDate(),$[r+16>>2]=t.getMonth(),$[r+20>>2]=t.getFullYear()-1900,$[r+24>>2]=t.getDay();var n=new Date(t.getFullYear(),0,1),o=(t.getTime()-n.getTime())/864e5|0;$[r+28>>2]=o,$[r+36>>2]=-60*t.getTimezoneOffset();var a=new Date(t.getFullYear(),6,1).getTimezoneOffset(),i=n.getTimezoneOffset(),s=0|(a!=i&&t.getTimezoneOffset()==Math.min(i,a));$[r+32>>2]=s},_mktime_js:function(e){var r=new Date($[e+20>>2]+1900,$[e+16>>2],$[e+12>>2],$[e+8>>2],$[e+4>>2],$[e>>2],0),t=$[e+32>>2],n=r.getTimezoneOffset(),o=new Date(r.getFullYear(),0,1),a=new Date(r.getFullYear(),6,1).getTimezoneOffset(),i=o.getTimezoneOffset(),s=Math.min(i,a);if(t<0)$[e+32>>2]=Number(a!=i&&s==n);else if(t>0!=(s==n)){var l=Math.max(i,a),u=t>0?s:l;r.setTime(r.getTime()+6e4*(u-n))}$[e+24>>2]=r.getDay();var c=(r.getTime()-o.getTime())/864e5|0;return $[e+28>>2]=c,$[e>>2]=r.getSeconds(),$[e+4>>2]=r.getMinutes(),$[e+8>>2]=r.getHours(),$[e+12>>2]=r.getDate(),$[e+16>>2]=r.getMonth(),r.getTime()/1e3|0},_tzset_js:function e(r,t,n){e.called||(e.called=!0,function(e,r,t){var n=(new Date).getFullYear(),o=new Date(n,0,1),a=new Date(n,6,1),i=o.getTimezoneOffset(),s=a.getTimezoneOffset(),l=Math.max(i,s);function u(e){var r=e.toTimeString().match(/\(([A-Za-z ]+)\)$/);return r?r[1]:"GMT"}$[e>>2]=60*l,$[r>>2]=Number(i!=s);var c=u(o),d=u(a),f=Q(c),p=Q(d);s<i?($[t>>2]=f,$[t+4>>2]=p):($[t>>2]=p,$[t+4>>2]=f)}(r,t,n))},abort:function(){ge("native code called abort()")},emscripten_log:function(e,r,t){it(e,F(ot(r,t),0))},emscripten_resize_heap:function(e){var r=U.length;D((e>>>=0)>r);var t,n,o=2147483648;if(e>o)return _("Cannot enlarge memory, asked to go up to "+e+" bytes, but the limit is "+"2147483648 bytes!"),!1;for(var a=1;a<=4;a*=2){var i=r*(1+.2/a);i=Math.min(i,e+100663296);var s=Math.min(o,(t=Math.max(e,i))+((n=65536)-t%n)%n);if(st(s))return!0}return _("Failed to grow the heap from "+r+" bytes to "+s+" bytes, not enough memory!"),!1},environ_get:function(e,r){var t=0;return ut().forEach((function(n,o){var a=r+t;$[e+4*o>>2]=a,function(e,r,t){for(var n=0;n<e.length;++n)D(e.charCodeAt(n)===(255&e.charCodeAt(n))),x[r++>>0]=e.charCodeAt(n);t||(x[r>>0]=0)}(n,a),t+=n.length+1})),0},environ_sizes_get:function(e,r){var t=ut();$[e>>2]=t.length;var n=0;return t.forEach((function(e){n+=e.length+1})),$[r>>2]=n,0},fd_close:function(e){try{var r=Le.getStreamFromFD(e);return Ie.close(r),0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_fdstat_get:function(e,r){try{var t=Le.getStreamFromFD(e),n=t.tty?2:Ie.isDir(t.mode)?3:Ie.isLink(t.mode)?7:4;return x[r>>0]=n,0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_read:function(e,r,t,n){try{var o=Le.getStreamFromFD(e),a=Le.doReadv(o,r,t);return $[n>>2]=a,0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_seek:function(e,r,t,n,o){try{var a=Le.getStreamFromFD(e),i=4294967296*t+(r>>>0),s=9007199254740992;return i<=-s||i>=s?-61:(Ie.llseek(a,i,n),Ee=[a.position>>>0,(ye=a.position,+Math.abs(ye)>=1?ye>0?(0|Math.min(+Math.floor(ye/4294967296),4294967295))>>>0:~~+Math.ceil((ye-+(~~ye>>>0))/4294967296)>>>0:0)],$[o>>2]=Ee[0],$[o+4>>2]=Ee[1],a.getdents&&0===i&&0===n&&(a.getdents=null),0)}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},fd_write:function(e,r,t,n){try{var o=Le.getStreamFromFD(e),a=Le.doWritev(o,r,t);return $[n>>2]=a,0}catch(e){if(void 0===Ie||!(e instanceof Ie.ErrnoError))throw e;return e.errno}},setTempRet0:function(e){}};!function(){var e={env:mt,wasi_snapshot_preview1:mt};function t(e,t){var n,o=e.exports;r.asm=o,D(w=r.asm.memory,"memory not found in wasm exports"),Z(w.buffer),D(re=r.asm.__indirect_function_table,"table not found in wasm exports"),n=r.asm.__wasm_call_ctors,ie.unshift(n),he("wasm-instantiate")}me("wasm-instantiate");var n=r;function o(e){D(r===n,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?"),n=null,t(e.instance)}function a(r){return function(){if(!E&&(s||l)){if("function"==typeof fetch&&!be(ve))return fetch(ve,{credentials:"same-origin"}).then((function(e){if(!e.ok)throw"failed to load wasm binary file at '"+ve+"'";return e.arrayBuffer()})).catch((function(){return Te(ve)}));if(f)return new Promise((function(e,r){f(ve,(function(r){e(new Uint8Array(r))}),r)}))}return Promise.resolve().then((function(){return Te(ve)}))}().then((function(r){return WebAssembly.instantiate(r,e)})).then((function(e){return e})).then(r,(function(e){_("failed to asynchronously prepare wasm: "+e),be(ve)&&_("warning: Loading from a file URI ("+ve+") is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing"),ge(e)}))}if(r.instantiateWasm)try{return r.instantiateWasm(e,t)}catch(e){return _("Module.instantiateWasm callback failed with error: "+e),!1}E||"function"!=typeof WebAssembly.instantiateStreaming||we(ve)||be(ve)||"function"!=typeof fetch?a(o):fetch(ve,{credentials:"same-origin"}).then((function(r){return WebAssembly.instantiateStreaming(r,e).then(o,(function(e){return _("wasm streaming compile failed: "+e),_("falling back to ArrayBuffer instantiation"),a(o)}))}))}(),r.___wasm_call_ctors=_e("__wasm_call_ctors");var ht=r._free=_e("free"),gt=r._malloc=_e("malloc"),vt=r._strlen=_e("strlen"),yt=r.___errno_location=_e("__errno_location"),Et=r.___getTypeName=_e("__getTypeName");r.___embind_register_native_and_builtin_types=_e("__embind_register_native_and_builtin_types");var wt=r.___stdio_exit=_e("__stdio_exit"),bt=r._emscripten_builtin_memalign=_e("emscripten_builtin_memalign"),_t=r._emscripten_stack_init=function(){return(_t=r._emscripten_stack_init=r.asm.emscripten_stack_init).apply(null,arguments)};r._emscripten_stack_get_free=function(){return(r._emscripten_stack_get_free=r.asm.emscripten_stack_get_free).apply(null,arguments)},r._emscripten_stack_get_base=function(){return(r._emscripten_stack_get_base=r.asm.emscripten_stack_get_base).apply(null,arguments)};var Tt,kt=r._emscripten_stack_get_end=function(){return(kt=r._emscripten_stack_get_end=r.asm.emscripten_stack_get_end).apply(null,arguments)};function St(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function Ct(e){function t(){Tt||(Tt=!0,r.calledRun=!0,A||(oe(),D(!le),le=!0,r.noFSInit||Ie.init.initialized||Ie.init(),Ie.ignorePermissions=!1,ke(ie),r.onRuntimeInitialized&&r.onRuntimeInitialized(),D(!r._main,'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'),function(){if(oe(),r.postRun)for("function"==typeof r.postRun&&(r.postRun=[r.postRun]);r.postRun.length;)e=r.postRun.shift(),se.unshift(e);var e;ke(se)}()))}ue>0||(_t(),ne(),function(){if(r.preRun)for("function"==typeof r.preRun&&(r.preRun=[r.preRun]);r.preRun.length;)e=r.preRun.shift(),ae.unshift(e);var e;ke(ae)}(),ue>0||(r.setStatus?(r.setStatus("Running..."),setTimeout((function(){setTimeout((function(){r.setStatus("")}),1),t()}),1)):t(),oe()))}if(r.stackSave=_e("stackSave"),r.stackRestore=_e("stackRestore"),r.stackAlloc=_e("stackAlloc"),r.dynCall_ijiii=_e("dynCall_ijiii"),r.dynCall_viiijj=_e("dynCall_viiijj"),r.dynCall_jij=_e("dynCall_jij"),r.dynCall_jii=_e("dynCall_jii"),r.dynCall_jiji=_e("dynCall_jiji"),r._ff_h264_cabac_tables=112940,P("intArrayFromString",!1),P("intArrayToString",!1),P("ccall",!1),P("cwrap",!1),P("setValue",!1),P("getValue",!1),P("allocate",!1),P("UTF8ArrayToString",!1),P("UTF8ToString",!1),P("stringToUTF8Array",!1),P("stringToUTF8",!1),P("lengthBytesUTF8",!1),P("stackTrace",!1),P("addOnPreRun",!1),P("addOnInit",!1),P("addOnPreMain",!1),P("addOnExit",!1),P("addOnPostRun",!1),P("writeStringToMemory",!1),P("writeArrayToMemory",!1),P("writeAsciiToMemory",!1),P("addRunDependency",!0),P("removeRunDependency",!0),P("FS_createFolder",!1),P("FS_createPath",!0),P("FS_createDataFile",!0),P("FS_createPreloadedFile",!0),P("FS_createLazyFile",!0),P("FS_createLink",!1),P("FS_createDevice",!0),P("FS_unlink",!0),P("getLEB",!1),P("getFunctionTables",!1),P("alignFunctionTables",!1),P("registerFunctions",!1),P("addFunction",!1),P("removeFunction",!1),P("prettyPrint",!1),P("dynCall",!1),P("getCompilerSetting",!1),P("print",!1),P("printErr",!1),P("getTempRet0",!1),P("setTempRet0",!1),P("callMain",!1),P("abort",!1),P("keepRuntimeAlive",!1),P("ptrToString",!1),P("zeroMemory",!1),P("stringToNewUTF8",!1),P("emscripten_realloc_buffer",!1),P("ENV",!1),P("ERRNO_CODES",!1),P("ERRNO_MESSAGES",!1),P("setErrNo",!1),P("inetPton4",!1),P("inetNtop4",!1),P("inetPton6",!1),P("inetNtop6",!1),P("readSockaddr",!1),P("writeSockaddr",!1),P("DNS",!1),P("getHostByName",!1),P("Protocols",!1),P("Sockets",!1),P("getRandomDevice",!1),P("traverseStack",!1),P("UNWIND_CACHE",!1),P("convertPCtoSourceLocation",!1),P("readAsmConstArgsArray",!1),P("readAsmConstArgs",!1),P("mainThreadEM_ASM",!1),P("jstoi_q",!1),P("jstoi_s",!1),P("getExecutableName",!1),P("listenOnce",!1),P("autoResumeAudioContext",!1),P("dynCallLegacy",!1),P("getDynCaller",!1),P("dynCall",!1),P("setWasmTableEntry",!1),P("getWasmTableEntry",!1),P("handleException",!1),P("runtimeKeepalivePush",!1),P("runtimeKeepalivePop",!1),P("callUserCallback",!1),P("maybeExit",!1),P("safeSetTimeout",!1),P("asmjsMangle",!1),P("asyncLoad",!1),P("alignMemory",!1),P("mmapAlloc",!1),P("reallyNegative",!1),P("unSign",!1),P("reSign",!1),P("formatString",!1),P("PATH",!1),P("PATH_FS",!1),P("SYSCALLS",!1),P("getSocketFromFD",!1),P("getSocketAddress",!1),P("JSEvents",!1),P("registerKeyEventCallback",!1),P("specialHTMLTargets",!1),P("maybeCStringToJsString",!1),P("findEventTarget",!1),P("findCanvasEventTarget",!1),P("getBoundingClientRect",!1),P("fillMouseEventData",!1),P("registerMouseEventCallback",!1),P("registerWheelEventCallback",!1),P("registerUiEventCallback",!1),P("registerFocusEventCallback",!1),P("fillDeviceOrientationEventData",!1),P("registerDeviceOrientationEventCallback",!1),P("fillDeviceMotionEventData",!1),P("registerDeviceMotionEventCallback",!1),P("screenOrientation",!1),P("fillOrientationChangeEventData",!1),P("registerOrientationChangeEventCallback",!1),P("fillFullscreenChangeEventData",!1),P("registerFullscreenChangeEventCallback",!1),P("registerRestoreOldStyle",!1),P("hideEverythingExceptGivenElement",!1),P("restoreHiddenElements",!1),P("setLetterbox",!1),P("currentFullscreenStrategy",!1),P("restoreOldWindowedStyle",!1),P("softFullscreenResizeWebGLRenderTarget",!1),P("doRequestFullscreen",!1),P("fillPointerlockChangeEventData",!1),P("registerPointerlockChangeEventCallback",!1),P("registerPointerlockErrorEventCallback",!1),P("requestPointerLock",!1),P("fillVisibilityChangeEventData",!1),P("registerVisibilityChangeEventCallback",!1),P("registerTouchEventCallback",!1),P("fillGamepadEventData",!1),P("registerGamepadEventCallback",!1),P("registerBeforeUnloadEventCallback",!1),P("fillBatteryEventData",!1),P("battery",!1),P("registerBatteryEventCallback",!1),P("setCanvasElementSize",!1),P("getCanvasElementSize",!1),P("demangle",!1),P("demangleAll",!1),P("jsStackTrace",!1),P("stackTrace",!1),P("getEnvStrings",!1),P("checkWasiClock",!1),P("writeI53ToI64",!1),P("writeI53ToI64Clamped",!1),P("writeI53ToI64Signaling",!1),P("writeI53ToU64Clamped",!1),P("writeI53ToU64Signaling",!1),P("readI53FromI64",!1),P("readI53FromU64",!1),P("convertI32PairToI53",!1),P("convertU32PairToI53",!1),P("dlopenMissingError",!1),P("setImmediateWrapped",!1),P("clearImmediateWrapped",!1),P("polyfillSetImmediate",!1),P("uncaughtExceptionCount",!1),P("exceptionLast",!1),P("exceptionCaught",!1),P("ExceptionInfo",!1),P("exception_addRef",!1),P("exception_decRef",!1),P("Browser",!1),P("setMainLoop",!1),P("wget",!1),P("FS",!1),P("MEMFS",!1),P("TTY",!1),P("PIPEFS",!1),P("SOCKFS",!1),P("_setNetworkCallback",!1),P("tempFixedLengthArray",!1),P("miniTempWebGLFloatBuffers",!1),P("heapObjectForWebGLType",!1),P("heapAccessShiftForWebGLHeap",!1),P("GL",!1),P("emscriptenWebGLGet",!1),P("computeUnpackAlignedImageSize",!1),P("emscriptenWebGLGetTexPixelData",!1),P("emscriptenWebGLGetUniform",!1),P("webglGetUniformLocation",!1),P("webglPrepareUniformLocationsBeforeFirstUse",!1),P("webglGetLeftBracePos",!1),P("emscriptenWebGLGetVertexAttrib",!1),P("writeGLArray",!1),P("AL",!1),P("SDL_unicode",!1),P("SDL_ttfContext",!1),P("SDL_audio",!1),P("SDL",!1),P("SDL_gfx",!1),P("GLUT",!1),P("EGL",!1),P("GLFW_Window",!1),P("GLFW",!1),P("GLEW",!1),P("IDBStore",!1),P("runAndAbortIfError",!1),P("InternalError",!1),P("BindingError",!1),P("UnboundTypeError",!1),P("PureVirtualError",!1),P("init_embind",!1),P("throwInternalError",!1),P("throwBindingError",!1),P("throwUnboundTypeError",!1),P("ensureOverloadTable",!1),P("exposePublicSymbol",!1),P("replacePublicSymbol",!1),P("extendError",!1),P("createNamedFunction",!1),P("registeredInstances",!1),P("getBasestPointer",!1),P("registerInheritedInstance",!1),P("unregisterInheritedInstance",!1),P("getInheritedInstance",!1),P("getInheritedInstanceCount",!1),P("getLiveInheritedInstances",!1),P("registeredTypes",!1),P("awaitingDependencies",!1),P("typeDependencies",!1),P("registeredPointers",!1),P("registerType",!1),P("whenDependentTypesAreResolved",!1),P("embind_charCodes",!1),P("embind_init_charCodes",!1),P("readLatin1String",!1),P("getTypeName",!1),P("heap32VectorToArray",!1),P("requireRegisteredType",!1),P("getShiftFromSize",!1),P("integerReadValueFromPointer",!1),P("enumReadValueFromPointer",!1),P("floatReadValueFromPointer",!1),P("simpleReadValueFromPointer",!1),P("runDestructors",!1),P("new_",!1),P("craftInvokerFunction",!1),P("embind__requireFunction",!1),P("tupleRegistrations",!1),P("structRegistrations",!1),P("genericPointerToWireType",!1),P("constNoSmartPtrRawPointerToWireType",!1),P("nonConstNoSmartPtrRawPointerToWireType",!1),P("init_RegisteredPointer",!1),P("RegisteredPointer",!1),P("RegisteredPointer_getPointee",!1),P("RegisteredPointer_destructor",!1),P("RegisteredPointer_deleteObject",!1),P("RegisteredPointer_fromWireType",!1),P("runDestructor",!1),P("releaseClassHandle",!1),P("finalizationRegistry",!1),P("detachFinalizer_deps",!1),P("detachFinalizer",!1),P("attachFinalizer",!1),P("makeClassHandle",!1),P("init_ClassHandle",!1),P("ClassHandle",!1),P("ClassHandle_isAliasOf",!1),P("throwInstanceAlreadyDeleted",!1),P("ClassHandle_clone",!1),P("ClassHandle_delete",!1),P("deletionQueue",!1),P("ClassHandle_isDeleted",!1),P("ClassHandle_deleteLater",!1),P("flushPendingDeletes",!1),P("delayFunction",!1),P("setDelayFunction",!1),P("RegisteredClass",!1),P("shallowCopyInternalPointer",!1),P("downcastPointer",!1),P("upcastPointer",!1),P("validateThis",!1),P("char_0",!1),P("char_9",!1),P("makeLegalFunctionName",!1),P("emval_handle_array",!1),P("emval_free_list",!1),P("emval_symbols",!1),P("init_emval",!1),P("count_emval_handles",!1),P("get_first_emval",!1),P("getStringOrSymbol",!1),P("Emval",!1),P("emval_newers",!1),P("craftEmvalAllocator",!1),P("emval_get_global",!1),P("emval_methodCallers",!1),P("emval_registeredMethods",!1),P("warnOnce",!1),P("stackSave",!1),P("stackRestore",!1),P("stackAlloc",!1),P("AsciiToString",!1),P("stringToAscii",!1),P("UTF16ToString",!1),P("stringToUTF16",!1),P("lengthBytesUTF16",!1),P("UTF32ToString",!1),P("stringToUTF32",!1),P("lengthBytesUTF32",!1),P("allocateUTF8",!1),P("allocateUTF8OnStack",!1),r.writeStackCookie=ne,r.checkStackCookie=oe,C("ALLOC_NORMAL",!1),C("ALLOC_STACK",!1),de=function e(){Tt||Ct(),Tt||(de=e)},r.run=Ct,r.preInit)for("function"==typeof r.preInit&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();Ct(),e.exports=r}));const l=1e3,u=1e3,c=!1,d=!1,f=!1,p=!1,m="initVideo",h="render",g="playAudio",v="initAudio",y="audioCode",E="videoCode",w=1,b=2,_="init",T="decode",k="audioDecode",S="videoDecode",C="close",P="updateConfig",A="key",D="delta";function O(e){let r=0;for(let t=0;t<10;t++){0===e[t]&&(r+=1)}return 10===r}(()=>{try{if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate){const e=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(e instanceof WebAssembly.Module)return new WebAssembly.Instance(e)instanceof WebAssembly.Instance}}catch(e){}})(),Date.now||(Date.now=function(){return(new Date).getTime()}),s.postRun=function(){var e=[],r=[],t={};"VideoEncoder"in self&&(t={hasInit:!1,isEmitInfo:!1,offscreenCanvas:null,offscreenCanvasCtx:null,decoder:new VideoDecoder({output:function(e){t.isEmitInfo||(n.opt.debug&&console.log("Jessibuca: [worker] Webcodecs Video Decoder initSize"),postMessage({cmd:m,w:e.codedWidth,h:e.codedHeight}),t.isEmitInfo=!0,t.offscreenCanvas=new OffscreenCanvas(e.codedWidth,e.codedHeight),t.offscreenCanvasCtx=t.offscreenCanvas.getContext("2d")),t.offscreenCanvasCtx.drawImage(e,0,0,e.codedWidth,e.codedHeight);let r=t.offscreenCanvas.transferToImageBitmap();postMessage({cmd:h,buffer:r,delay:n.delay,ts:0},[r]),setTimeout((function(){e.close?e.close():e.destroy()}),100)},error:function(e){console.error(e)}}),decode:function(e,r){const o=e[0]>>4==1;if(t.hasInit){const n=new EncodedVideoChunk({data:e.slice(5),timestamp:r,type:o?A:D});t.decoder.decode(n)}else if(o&&0===e[1]){const r=15&e[0];n.setVideoCodec(r);const o=function(e){let r=e.subarray(1,4),t="avc1.";for(let e=0;e<3;e++){let n=r[e].toString(16);n.length<2&&(n="0"+n),t+=n}return{codec:t,description:e}}(e.slice(5));t.decoder.configure(o),t.hasInit=!0}},reset(){t.hasInit=!1,t.isEmitInfo=!1,t.offscreenCanvas=null,t.offscreenCanvasCtx=null}});var n={opt:{debug:c,useOffscreen:f,useWCS:d,videoBuffer:l,openWebglAlignment:p,videoBufferDelay:u},useOffscreen:function(){return n.opt.useOffscreen&&"undefined"!=typeof OffscreenCanvas},initAudioPlanar:function(e,t){postMessage({cmd:v,sampleRate:t,channels:e});var n=[],o=0;this.playAudioPlanar=function(t,a,i){for(var l=a,u=[],c=0,d=0;d<2;d++){var f=s.HEAPU32[(t>>2)+d]>>2;u[d]=s.HEAPF32.subarray(f,f+l)}if(o){if(!(l>=(a=1024-o)))return o+=l,r[0]=Float32Array.of(...r[0],...u[0]),void(2==e&&(r[1]=Float32Array.of(...r[1],...u[1])));n[0]=Float32Array.of(...r[0],...u[0].subarray(0,a)),2==e&&(n[1]=Float32Array.of(...r[1],...u[1].subarray(0,a))),postMessage({cmd:g,buffer:n,ts:i},n.map((e=>e.buffer))),c=a,l-=a}for(o=l;o>=1024;o-=1024)n[0]=u[0].slice(c,c+=1024),2==e&&(n[1]=u[1].slice(c-1024,c)),postMessage({cmd:g,buffer:n,ts:i},n.map((e=>e.buffer)));o&&(r[0]=u[0].slice(c),2==e&&(r[1]=u[1].slice(c)))}},setVideoCodec:function(e){postMessage({cmd:E,code:e})},setAudioCodec:function(e){postMessage({cmd:y,code:e})},setVideoSize:function(e,r){postMessage({cmd:m,w:e,h:r});var t=e*r,o=t>>2;n.useOffscreen()?(this.offscreenCanvas=new OffscreenCanvas(e,r),this.offscreenCanvasGL=this.offscreenCanvas.getContext("webgl"),this.webglObj=((e,r)=>{var t=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),n=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");r&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var o=e.createShader(e.VERTEX_SHADER);e.shaderSource(o,t),e.compileShader(o),e.getShaderParameter(o,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(o));var a=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(a,n),e.compileShader(a),e.getShaderParameter(a,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(a));var i=e.createProgram();e.attachShader(i,o),e.attachShader(i,a),e.linkProgram(i),e.getProgramParameter(i,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(i)),e.useProgram(i);var s=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,s),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var l=e.getAttribLocation(i,"vertexPos");e.enableVertexAttribArray(l),e.vertexAttribPointer(l,2,e.FLOAT,!1,0,0);var u=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,u),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(i,"texturePos");function d(r,t){var n=e.createTexture();return e.bindTexture(e.TEXTURE_2D,n),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(i,r),t),n}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var f=d("ySampler",0),p=d("uSampler",1),m=d("vSampler",2);return{render:function(r,t,n,o,a){e.viewport(0,0,r,t),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,f),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r,t,0,e.LUMINANCE,e.UNSIGNED_BYTE,n),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,m),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,r/2,t/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,a),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(i),e.deleteBuffer(s),e.deleteBuffer(u),e.deleteTexture(f),e.deleteTexture(p),e.deleteBuffer(m)}catch(e){}}}})(this.offscreenCanvasGL,n.opt.openWebglAlignment),this.draw=function(a,i,l,u){const c=s.HEAPU8.subarray(i,i+t),d=s.HEAPU8.subarray(l,l+o),f=s.HEAPU8.subarray(u,u+o);if(O(Uint8Array.from(c)))return void(n.opt.debug&&console.log("Jessibuca: [worker]: draw offscreenCanvas is green yuv"));this.webglObj.render(e,r,c,d,f);let p=this.offscreenCanvas.transferToImageBitmap();postMessage({cmd:h,buffer:p,delay:this.delay,ts:a},[p])}):this.draw=function(e,r,a,i){const l=Uint8Array.from(s.HEAPU8.subarray(r,r+t)),u=Uint8Array.from(s.HEAPU8.subarray(a,a+o)),c=Uint8Array.from(s.HEAPU8.subarray(i,i+o));if(O(l))return void(n.opt.debug&&console.log("Jessibuca: [worker]: draw is green yuv"));const d=[l,u,c];postMessage({cmd:h,output:d,delay:this.delay,ts:e},d.map((e=>e.buffer)))}},getDelay:function(e){if(!e)return-1;if(this.firstTimestamp){if(e){const r=Date.now()-this.startTimestamp,t=e-this.firstTimestamp;this.delay=r>=t?r-t:t-r}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay},resetDelay:function(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1},init:function(){n.opt.debug&&console.log("Jessibuca: [worker] init");const r=e=>{n.opt.useWCS&&n.useOffscreen()&&e.type===b&&t.decode?t.decode(e.payload,e.ts):e.decoder.decode(e.payload,e.ts)};this.stopId=setInterval((()=>{if(e.length)if(this.dropping){for((t=e.shift()).type===w&&0===t.payload[1]&&r(t);!t.isIFrame&&e.length;)(t=e.shift()).type===w&&0===t.payload[1]&&r(t);t.isIFrame&&(this.dropping=!1,r(t))}else{var t=e[0];if(-1===this.getDelay(t.ts))e.shift(),r(t);else if(this.delay>n.opt.videoBuffer+n.opt.videoBufferDelay)this.resetDelay(),this.dropping=!0;else for(;e.length&&(t=e[0],this.getDelay(t.ts)>n.opt.videoBuffer);)e.shift(),r(t)}}),10)},close:function(){n.opt.debug&&console.log("Jessibuca: [worker]: close"),clearInterval(this.stopId),this.stopId=null,o.clear&&o.clear(),a.clear&&a.clear(),t.reset&&t.reset(),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1,this.webglObj&&(this.webglObj.destroy(),this.offscreenCanvas=null,this.offscreenCanvasGL=null,this.offscreenCanvasCtx=null),e=[],r=[],delete this.playAudioPlanar,delete this.draw},pushBuffer:function(r,t){t.type===w?e.push({ts:t.ts,payload:r,decoder:o,type:w}):t.type===b&&e.push({ts:t.ts,payload:r,decoder:a,type:b,isIFrame:t.isIFrame})}},o=new s.AudioDecoder(n),a=new s.VideoDecoder(n);postMessage({cmd:_}),self.onmessage=function(e){var r=e.data;switch(r.cmd){case _:try{n.opt=Object.assign(n.opt,JSON.parse(r.opt))}catch(e){}o.sample_rate=r.sampleRate,n.init();break;case T:n.pushBuffer(r.buffer,r.options);break;case k:o.decode(r.buffer,r.ts);break;case S:a.decode(r.buffer,r.ts);break;case C:n.close();break;case P:n.opt[r.key]=r.value}}}})); | ... | ... |
web_src/static/js/jessibuca/decoder.wasm
0 → 100644
No preview for this file type
web_src/static/js/jessibuca/jessibuca.js
0 → 100644
| 1 | +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).jessibuca=t()}(this,(function(){"use strict";var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e,t){return e(t={exports:{}},t.exports),t.exports}var i,o=t((function(e){e.exports=function(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e},e.exports.__esModule=!0,e.exports.default=e.exports})),r=(i=o)&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i;const s=0,a=1,n="flv",A="m7s",d={videoBuffer:1e3,videoBufferDelay:1e3,isResize:!0,isFullResize:!1,isFlv:!1,debug:!1,hotKey:!1,loadingTimeout:10,heartTimeout:5,timeout:10,loadingTimeoutReplay:!0,heartTimeoutReplay:!1,loadingTimeoutReplayTimes:3,heartTimeoutReplayTimes:3,supportDblclickFullscreen:!1,showBandwidth:!1,keepScreenOn:!1,isNotMute:!1,hasAudio:!0,hasVideo:!0,operateBtns:{fullscreen:!1,screenshot:!1,play:!1,audio:!1,record:!1},controlAutoHide:!1,hasControl:!1,loadingText:"",background:"",decoder:"decoder.js",url:"",rotate:0,forceNoOffscreen:!0,hiddenAutoPause:!1,protocol:a,demuxType:n,useWCS:!1,wcsUseVideoRender:!0,useMSE:!1,useOffscreen:!1,autoWasm:!0,wasmDecodeErrorReplay:!0,openWebglAlignment:!1,wasmDecodeAudioSyncVideo:!1},c="init",l="initVideo",u="render",h="playAudio",p="initAudio",m="audioCode",g="videoCode",f="wasmError",b="Invalid NAL unit size",y=1,v=2,w=8,S=9,E="init",B="decode",C="audioDecode",R="close",k="updateConfig",T={fullscreen:"fullscreen$2",webFullscreen:"webFullscreen",decoderWorkerInit:"decoderWorkerInit",play:"play",playing:"playing",pause:"pause",mute:"mute",load:"load",loading:"loading",videoInfo:"videoInfo",timeUpdate:"timeUpdate",audioInfo:"audioInfo",log:"log",error:"error",kBps:"kBps",timeout:"timeout",delayTimeout:"delayTimeout",loadingTimeout:"loadingTimeout",stats:"stats",performance:"performance",record:"record",recording:"recording",recordingTimestamp:"recordingTimestamp",recordStart:"recordStart",recordEnd:"recordEnd",recordCreateError:"recordCreateError",buffer:"buffer",videoFrame:"videoFrame",start:"start",metadata:"metadata",resize:"resize",streamEnd:"streamEnd",streamSuccess:"streamSuccess",streamMessage:"streamMessage",streamError:"streamError",volumechange:"volumechange",destroy:"destroy",mseSourceOpen:"mseSourceOpen",mseSourceClose:"mseSourceClose",mseSourceBufferError:"mseSourceBufferError",mseSourceBufferBusy:"mseSourceBufferBusy",mseSourceBufferFull:"mseSourceBufferFull",videoWaiting:"videoWaiting",videoTimeUpdate:"videoTimeUpdate",videoSyncAudio:"videoSyncAudio",playToRenderTimes:"playToRenderTimes"},I={load:T.load,timeUpdate:T.timeUpdate,videoInfo:T.videoInfo,audioInfo:T.audioInfo,error:T.error,kBps:T.kBps,log:T.log,start:T.start,timeout:T.timeout,loadingTimeout:T.loadingTimeout,delayTimeout:T.delayTimeout,fullscreen:"fullscreen",play:T.play,pause:T.pause,mute:T.mute,stats:T.stats,performance:T.performance,recordingTimestamp:T.recordingTimestamp,recordStart:T.recordStart,recordEnd:T.recordEnd,playToRenderTimes:T.playToRenderTimes},x={playError:"playIsNotPauseOrUrlIsNull",fetchError:"fetchError",websocketError:"websocketError",webcodecsH265NotSupport:"webcodecsH265NotSupport",webcodecsDecodeError:"webcodecsDecodeError",webcodecsWidthOrHeightChange:"webcodecsWidthOrHeightChange",mediaSourceH265NotSupport:"mediaSourceH265NotSupport",mediaSourceFull:T.mseSourceBufferFull,mseSourceBufferError:T.mseSourceBufferError,mediaSourceAppendBufferError:"mediaSourceAppendBufferError",mediaSourceBufferListLarge:"mediaSourceBufferListLarge",mediaSourceAppendBufferEndTimeout:"mediaSourceAppendBufferEndTimeout",wasmDecodeError:"wasmDecodeError",webglAlignmentError:"webglAlignmentError"},D="notConnect",j="open",L="close",F="error",O={download:"download",base64:"base64",blob:"blob"},V={7:"H264(AVC)",12:"H265(HEVC)"},M=12,U={10:"AAC",7:"ALAW",8:"MULAW"},Q=38,W=0,J=1,G=2,P="mp4",N="webm",z="webcodecs",H="webgl",Y="offscreen",X="key",q="delta",Z='video/mp4; codecs="avc1.64002A"',K="ended",_="open",$="closed",ee=1e3,te=27,ie=38,oe=40,re="A key frame is required after configure() or flush()",se="The user aborted a request";class ae{constructor(e){this.log=function(t){if(e._opt.debug){for(var i=arguments.length,o=new Array(i>1?i-1:0),r=1;r<i;r++)o[r-1]=arguments[r];console.log(`Jessibuca: [${t}]`,...o)}},this.warn=function(t){if(e._opt.debug){for(var i=arguments.length,o=new Array(i>1?i-1:0),r=1;r<i;r++)o[r-1]=arguments[r];console.warn(`Jessibuca: [${t}]`,...o)}},this.error=function(e){for(var t=arguments.length,i=new Array(t>1?t-1:0),o=1;o<t;o++)i[o-1]=arguments[o];console.error(`Jessibuca: [${e}]`,...i)}}}class ne{constructor(e){this.destroys=[],this.proxy=this.proxy.bind(this),this.master=e}proxy(e,t,i){let o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};if(!e)return;if(Array.isArray(t))return t.map((t=>this.proxy(e,t,i,o)));e.addEventListener(t,i,o);const r=()=>e.removeEventListener(t,i,o);return this.destroys.push(r),r}destroy(){this.master.debug&&this.master.debug.log("Events","destroy"),this.destroys.forEach((e=>e()))}}var Ae=t((function(e){!function(){var t="undefined"!=typeof window&&void 0!==window.document?window.document:{},i=e.exports,o=function(){for(var e,i=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],o=0,r=i.length,s={};o<r;o++)if((e=i[o])&&e[1]in t){for(o=0;o<e.length;o++)s[i[0][o]]=e[o];return s}return!1}(),r={change:o.fullscreenchange,error:o.fullscreenerror},s={request:function(e,i){return new Promise(function(r,s){var a=function(){this.off("change",a),r()}.bind(this);this.on("change",a);var n=(e=e||t.documentElement)[o.requestFullscreen](i);n instanceof Promise&&n.then(a).catch(s)}.bind(this))},exit:function(){return new Promise(function(e,i){if(this.isFullscreen){var r=function(){this.off("change",r),e()}.bind(this);this.on("change",r);var s=t[o.exitFullscreen]();s instanceof Promise&&s.then(r).catch(i)}else e()}.bind(this))},toggle:function(e,t){return this.isFullscreen?this.exit():this.request(e,t)},onchange:function(e){this.on("change",e)},onerror:function(e){this.on("error",e)},on:function(e,i){var o=r[e];o&&t.addEventListener(o,i,!1)},off:function(e,i){var o=r[e];o&&t.removeEventListener(o,i,!1)},raw:o};o?(Object.defineProperties(s,{isFullscreen:{get:function(){return Boolean(t[o.fullscreenElement])}},element:{enumerable:!0,get:function(){return t[o.fullscreenElement]}},isEnabled:{enumerable:!0,get:function(){return Boolean(t[o.fullscreenEnabled])}}}),i?e.exports=s:window.screenfull=s):i?e.exports={isEnabled:!1}:window.screenfull={isEnabled:!1}}()}));function de(){}function ce(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";const t=e.split(","),i=atob(t[1]),o=t[0].replace("data:","").replace(";base64","");let r=i.length,s=new Uint8Array(r);for(;r--;)s[r]=i.charCodeAt(r);return new File([s],"file",{type:o})}function le(){return(new Date).getTime()}function ue(e,t,i){return Math.max(Math.min(e,Math.max(t,i)),Math.min(t,i))}function he(e,t,i){if(e)return"object"==typeof t&&Object.keys(t).forEach((i=>{he(e,i,t[i])})),e.style[t]=i,e}function pe(e,t){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(!e)return 0;const o=getComputedStyle(e,null).getPropertyValue(t);return i?parseFloat(o):o}function me(){return performance&&"function"==typeof performance.now?performance.now():Date.now()}function ge(e){let t=0,i=me();return o=>{t+=o;const r=me(),s=r-i;s>=1e3&&(e(t/s*1e3),i=r,t=0)}}function fe(){return/iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase())}function be(e){if(null==e||""===e||0===parseInt(e)||isNaN(parseInt(e)))return"0kb/s";let t=parseFloat(e);return t=t.toFixed(2),t+"kb/s"}function ye(e){return null==e}function ve(e){return!ye(e)}Ae.isEnabled,(()=>{try{if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate){const e=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));if(e instanceof WebAssembly.Module)return new WebAssembly.Instance(e)instanceof WebAssembly.Instance}}catch(e){}})();class we{on(e,t,i){const o=this.e||(this.e={});return(o[e]||(o[e]=[])).push({fn:t,ctx:i}),this}once(e,t,i){const o=this;function r(){o.off(e,r);for(var s=arguments.length,a=new Array(s),n=0;n<s;n++)a[n]=arguments[n];t.apply(i,a)}return r._=t,this.on(e,r,i)}emit(e){const t=((this.e||(this.e={}))[e]||[]).slice();for(var i=arguments.length,o=new Array(i>1?i-1:0),r=1;r<i;r++)o[r-1]=arguments[r];for(let e=0;e<t.length;e+=1)t[e].fn.apply(t[e].ctx,o);return this}off(e,t){const i=this.e||(this.e={});if(!e)return Object.keys(i).forEach((e=>{delete i[e]})),void delete this.e;const o=i[e],r=[];if(o&&t)for(let e=0,i=o.length;e<i;e+=1)o[e].fn!==t&&o[e].fn._!==t&&r.push(o[e]);return r.length?i[e]=r:delete i[e],this}}class Se extends we{constructor(){super(),this.init=!1}resetInit(){this.init=!1,this.videoInfo={width:"",height:"",encType:"",encTypeCode:""}}destroy(){this.resetInit(),this.player.$container.removeChild(this.$videoElement),this.off()}updateVideoInfo(e){e.encTypeCode&&(this.videoInfo.encType=V[e.encTypeCode]),e.width&&(this.videoInfo.width=e.width),e.height&&(this.videoInfo.height=e.height),this.videoInfo.encType&&this.videoInfo.height&&this.videoInfo.width&&!this.init&&(this.player.emit(T.videoInfo,this.videoInfo),this.init=!0)}play(){}pause(){}clearView(){}}var Ee="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0;function Be(e,t,i){var o=new XMLHttpRequest;o.open("GET",e),o.responseType="blob",o.onload=function(){Te(o.response,t,i)},o.onerror=function(){console.error("could not download file")},o.send()}function Ce(e){var t=new XMLHttpRequest;t.open("HEAD",e,!1);try{t.send()}catch(e){}return t.status>=200&&t.status<=299}function Re(e){try{e.dispatchEvent(new MouseEvent("click"))}catch(i){var t=document.createEvent("MouseEvents");t.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),e.dispatchEvent(t)}}var ke=Ee.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),Te="object"!=typeof window||window!==Ee?function(){}:"download"in HTMLAnchorElement.prototype&&!ke?function(e,t,i){var o=Ee.URL||Ee.webkitURL,r=document.createElementNS("http://www.w3.org/1999/xhtml","a");t=t||e.name||"download",r.download=t,r.rel="noopener","string"==typeof e?(r.href=e,r.origin!==location.origin?Ce(r.href)?Be(e,t,i):Re(r,r.target="_blank"):Re(r)):(r.href=o.createObjectURL(e),setTimeout((function(){o.revokeObjectURL(r.href)}),4e4),setTimeout((function(){Re(r)}),0))}:"msSaveOrOpenBlob"in navigator?function(e,t,i){if(t=t||e.name||"download","string"==typeof e)if(Ce(e))Be(e,t,i);else{var o=document.createElement("a");o.href=e,o.target="_blank",setTimeout((function(){Re(o)}))}else navigator.msSaveOrOpenBlob(function(e,t){return void 0===t?t={autoBom:!1}:"object"!=typeof t&&(console.warn("Deprecated: Expected third argument to be a object"),t={autoBom:!t}),t.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob([String.fromCharCode(65279),e],{type:e.type}):e}(e,i),t)}:function(e,t,i,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof e)return Be(e,t,i);var r="application/octet-stream"===e.type,s=/constructor/i.test(Ee.HTMLElement)||Ee.safari,a=/CriOS\/[\d]+/.test(navigator.userAgent);if((a||r&&s||ke)&&"undefined"!=typeof FileReader){var n=new FileReader;n.onloadend=function(){var e=n.result;e=a?e:e.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=e:location=e,o=null},n.readAsDataURL(e)}else{var A=Ee.URL||Ee.webkitURL,d=A.createObjectURL(e);o?o.location=d:location.href=d,o=null,setTimeout((function(){A.revokeObjectURL(d)}),4e4)}};class Ie extends Se{constructor(e){super(),this.player=e;const t=document.createElement("canvas");t.style.position="absolute",t.style.top=0,t.style.left=0,this.$videoElement=t,e.$container.appendChild(this.$videoElement),this.context2D=null,this.contextGl=null,this.contextGlRender=null,this.contextGlDestroy=null,this.bitmaprenderer=null,this.renderType=null,this.videoInfo={width:"",height:"",encType:""},this._initCanvasRender(),this.player.debug.log("CanvasVideo","init")}destroy(){super.destroy(),this.contextGl&&(this.contextGl=null),this.context2D&&(this.context2D=null),this.contextGlRender&&(this.contextGlDestroy&&this.contextGlDestroy(),this.contextGlDestroy=null,this.contextGlRender=null),this.bitmaprenderer&&(this.bitmaprenderer=null),this.renderType=null,this.player.debug.log("CanvasVideoLoader","destroy")}_initContextGl(){this.contextGl=function(e){let t=null;const i=["webgl","experimental-webgl","moz-webgl","webkit-3d"];let o=0;for(;!t&&o<i.length;){const r=i[o];try{let i={preserveDrawingBuffer:!0};t=e.getContext(r,i)}catch(e){t=null}t&&"function"==typeof t.getParameter||(t=null),++o}return t}(this.$videoElement);const e=((e,t)=>{var i=["attribute vec4 vertexPos;","attribute vec4 texturePos;","varying vec2 textureCoord;","void main()","{","gl_Position = vertexPos;","textureCoord = texturePos.xy;","}"].join("\n"),o=["precision highp float;","varying highp vec2 textureCoord;","uniform sampler2D ySampler;","uniform sampler2D uSampler;","uniform sampler2D vSampler;","const mat4 YUV2RGB = mat4","(","1.1643828125, 0, 1.59602734375, -.87078515625,","1.1643828125, -.39176171875, -.81296875, .52959375,","1.1643828125, 2.017234375, 0, -1.081390625,","0, 0, 0, 1",");","void main(void) {","highp float y = texture2D(ySampler, textureCoord).r;","highp float u = texture2D(uSampler, textureCoord).r;","highp float v = texture2D(vSampler, textureCoord).r;","gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;","}"].join("\n");t&&e.pixelStorei(e.UNPACK_ALIGNMENT,1);var r=e.createShader(e.VERTEX_SHADER);e.shaderSource(r,i),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)||console.log("Vertex shader failed to compile: "+e.getShaderInfoLog(r));var s=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(s,o),e.compileShader(s),e.getShaderParameter(s,e.COMPILE_STATUS)||console.log("Fragment shader failed to compile: "+e.getShaderInfoLog(s));var a=e.createProgram();e.attachShader(a,r),e.attachShader(a,s),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)||console.log("Program failed to compile: "+e.getProgramInfoLog(a)),e.useProgram(a);var n=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,n),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,1,-1,1,1,-1,-1,-1]),e.STATIC_DRAW);var A=e.getAttribLocation(a,"vertexPos");e.enableVertexAttribArray(A),e.vertexAttribPointer(A,2,e.FLOAT,!1,0,0);var d=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,d),e.bufferData(e.ARRAY_BUFFER,new Float32Array([1,0,0,0,1,1,0,1]),e.STATIC_DRAW);var c=e.getAttribLocation(a,"texturePos");function l(t,i){var o=e.createTexture();return e.bindTexture(e.TEXTURE_2D,o),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.bindTexture(e.TEXTURE_2D,null),e.uniform1i(e.getUniformLocation(a,t),i),o}e.enableVertexAttribArray(c),e.vertexAttribPointer(c,2,e.FLOAT,!1,0,0);var u=l("ySampler",0),h=l("uSampler",1),p=l("vSampler",2);return{render:function(t,i,o,r,s){e.viewport(0,0,t,i),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,u),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t,i,0,e.LUMINANCE,e.UNSIGNED_BYTE,o),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,h),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,r),e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.LUMINANCE,t/2,i/2,0,e.LUMINANCE,e.UNSIGNED_BYTE,s),e.drawArrays(e.TRIANGLE_STRIP,0,4)},destroy:function(){try{e.deleteProgram(a),e.deleteBuffer(n),e.deleteBuffer(d),e.deleteTexture(u),e.deleteTexture(h),e.deleteBuffer(p)}catch(e){}}}})(this.contextGl,this.player._opt.openWebglAlignment);this.contextGlRender=e.render,this.contextGlDestroy=e.destroy}_initContext2D(){this.context2D=this.$videoElement.getContext("2d")}_initCanvasRender(){this.player._opt.useWCS&&!this._supportOffscreen()?(this.renderType=z,this._initContext2D()):this._supportOffscreen()?(this.renderType=Y,this._bindOffscreen()):(this.renderType=H,this._initContextGl())}_supportOffscreen(){return"function"==typeof this.$videoElement.transferControlToOffscreen&&this.player._opt.useOffscreen}_bindOffscreen(){this.bitmaprenderer=this.$videoElement.getContext("bitmaprenderer")}initCanvasViewSize(){this.$videoElement.width=this.videoInfo.width,this.$videoElement.height=this.videoInfo.height,this.resize()}render(e){switch(this.player.videoTimestamp=e.ts,this.renderType){case Y:this.bitmaprenderer.transferFromImageBitmap(e.buffer);break;case H:this.contextGlRender(this.$videoElement.width,this.$videoElement.height,e.output[0],e.output[1],e.output[2]);break;case z:this.context2D.drawImage(e.videoFrame,0,0,this.$videoElement.width,this.$videoElement.height)}}screenshot(e,t,i,o){e=e||le(),o=o||O.download;const r={png:"image/png",jpeg:"image/jpeg",webp:"image/webp"};let s=.92;!r[t]&&O[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(s=Number(i));const a=this.$videoElement.toDataURL(r[t]||r.png,s),n=ce(a);return o===O.base64?a:o===O.blob?n:void(o===O.download&&Te(n,e))}clearView(){switch(this.renderType){case Y:(function(e,t){const i=document.createElement("canvas");return i.width=e,i.height=t,window.createImageBitmap(i,0,0,e,t)})(this.$videoElement.width,this.$videoElement.height).then((e=>{this.bitmaprenderer.transferFromImageBitmap(e)}));break;case H:this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT);break;case z:this.context2D.clearRect(0,0,this.$videoElement.width,this.$videoElement.height)}}resize(){this.player.debug.log("canvasVideo","resize");const e=this.player._opt;let t=this.player.width,i=this.player.height;e.hasControl&&!e.controlAutoHide&&(fe()&&this.player.fullscreen?t-=Q:i-=Q);let o=this.$videoElement.width,r=this.$videoElement.height;const s=e.rotate;let a=(t-o)/2,n=(i-r)/2;270!==s&&90!==s||(o=this.$videoElement.height,r=this.$videoElement.width);const A=t/o,d=i/r;let c=A>d?d:A;e.isResize||A!==d&&(c=A+","+d),e.isFullResize&&(c=A>d?A:d);let l="scale("+c+")";s&&(l+=" rotate("+s+"deg)"),this.$videoElement.style.transform=l,this.$videoElement.style.left=a+"px",this.$videoElement.style.top=n+"px"}}class xe extends Se{constructor(e){super(),this.player=e;const t=document.createElement("video");t.muted=!0,t.style.position="absolute",t.style.top=0,t.style.left=0,e.$container.appendChild(t),this.videoInfo={width:"",height:"",encType:""};const i=this.player._opt;i.useWCS&&i.wcsUseVideoRender&&(this.trackGenerator=new MediaStreamTrackGenerator({kind:"video"}),t.srcObject=new MediaStream([this.trackGenerator]),this.vwriter=this.trackGenerator.writable.getWriter()),this.$videoElement=t,this.resize();const{proxy:o}=this.player.events;o(this.$videoElement,"canplay",(()=>{this.player.debug.log("Video","canplay")})),o(this.$videoElement,"waiting",(()=>{this.player.emit(T.videoWaiting)})),o(this.$videoElement,"timeupdate",(e=>{const t=parseInt(e.timeStamp,10);this.player.emit(T.timeUpdate,t)})),this.player.debug.log("Video","init")}destroy(){super.destroy(),this.$videoElement&&(this.$videoElement.src="",this.$videoElement.removeAttribute("src"),this.$videoElement=null),this.trackGenerator&&(this.trackGenerator=null),this.vwriter&&(this.vwriter=null),this.player.debug.log("Video","destroy")}play(){setTimeout((()=>{this.$videoElement.play().then((()=>{this.player.debug.log("Video","play")})).catch((e=>{this.player.debug.warn("Video","play",e)}))}),100)}pause(e){e?this.$videoElement&&this.$videoElement.pause():setTimeout((()=>{this.$videoElement&&this.$videoElement.pause()}),100)}clearView(){}screenshot(e,t,i,o){e=e||le(),o=o||O.download;let r=.92;!{png:"image/png",jpeg:"image/jpeg",webp:"image/webp"}[t]&&O[t]&&(o=t,t="png",i=void 0),"string"==typeof i&&(o=i,i=void 0),void 0!==i&&(r=Number(i));const s=this.$videoElement;let a=document.createElement("canvas");a.width=s.videoWidth,a.height=s.videoHeight;a.getContext("2d").drawImage(s,0,0,a.width,a.height);const n=a.toDataURL(O[t]||O.png,r),A=ce(n);return o===O.base64?n:o===O.blob?A:void(o===O.download&&Te(A,e))}initCanvasViewSize(){this.resize()}render(e){this.vwriter&&this.vwriter.write(e.videoFrame)}resize(){let e=this.player.width,t=this.player.height;const i=this.player._opt,o=i.rotate;i.hasControl&&!i.controlAutoHide&&(fe()&&this.player.fullscreen?e-=Q:t-=Q),this.$videoElement.width=e,this.$videoElement.height=t,270!==o&&90!==o||(this.$videoElement.width=t,this.$videoElement.height=e);let r=(e-this.$videoElement.width)/2,s=(t-this.$videoElement.height)/2,a="contain";i.isResize||(a="fill"),i.isFullResize&&(a="none"),this.$videoElement.style.objectFit=a,this.$videoElement.style.transform="rotate("+o+"deg)",this.$videoElement.style.left=r+"px",this.$videoElement.style.top=s+"px"}}class De{constructor(e){return new(De.getLoaderFactory(e._opt))(e)}static getLoaderFactory(e){return e.useMSE||e.useWCS&&!e.useOffscreen&&e.wcsUseVideoRender?xe:Ie}}class je extends we{constructor(e){super(),this.bufferList=[],this.player=e,this.scriptNode=null,this.hasInitScriptNode=!1,this.audioContextChannel=null,this.audioContext=new(window.AudioContext||window.webkitAudioContext),this.gainNode=this.audioContext.createGain();const t=this.audioContext.createBufferSource();t.buffer=this.audioContext.createBuffer(1,1,22050),t.connect(this.audioContext.destination),t.noteOn?t.noteOn(0):t.start(0),this.audioBufferSourceNode=t,this.mediaStreamAudioDestinationNode=this.audioContext.createMediaStreamDestination(),this.audioEnabled(!0),this.gainNode.gain.value=0,this.playing=!1,this.audioSyncVideoOption={diff:null},this.audioInfo={encType:"",channels:"",sampleRate:""},this.init=!1,this.hasAudio=!1,this.on(T.videoSyncAudio,(e=>{this.audioSyncVideoOption=e})),this.player.debug.log("AudioContext","init")}resetInit(){this.init=!1,this.audioInfo={encType:"",channels:"",sampleRate:""}}destroy(){this.closeAudio(),this.resetInit(),this.audioContext.close(),this.audioContext=null,this.gainNode=null,this.hasAudio=!1,this.playing=!1,this.scriptNode&&(this.scriptNode.onaudioprocess=de,this.scriptNode=null),this.audioBufferSourceNode=null,this.mediaStreamAudioDestinationNode=null,this.hasInitScriptNode=!1,this.audioSyncVideoOption={diff:null},this.off(),this.player.debug.log("AudioContext","destroy")}updateAudioInfo(e){e.encTypeCode&&(this.audioInfo.encType=U[e.encTypeCode]),e.channels&&(this.audioInfo.channels=e.channels),e.sampleRate&&(this.audioInfo.sampleRate=e.sampleRate),this.audioInfo.sampleRate&&this.audioInfo.channels&&this.audioInfo.encType&&!this.init&&(this.player.emit(T.audioInfo,this.audioInfo),this.init=!0)}get isPlaying(){return this.playing}get isMute(){return 0===this.gainNode.gain.value||this.isStateSuspended()}get volume(){return this.gainNode.gain.value}get bufferSize(){return this.bufferList.length}initScriptNode(){if(this.playing=!0,this.hasInitScriptNode)return;const e=this.audioInfo.channels,t=this.audioContext.createScriptProcessor(1024,0,e);t.onaudioprocess=t=>{const i=t.outputBuffer;if(this.bufferList.length&&this.playing){if(!this.player._opt.useWCS&&!this.player._opt.useMSE&&this.player._opt.wasmDecodeAudioSyncVideo){if(this.audioSyncVideoOption.diff>ee)return void this.player.debug.warn("AudioContext",`audioSyncVideoOption more than diff :${this.audioSyncVideoOption.diff}, waiting`);if(this.audioSyncVideoOption.diff<-1e3){this.player.debug.warn("AudioContext",`audioSyncVideoOption less than diff :${this.audioSyncVideoOption.diff}, dropping`);let e=this.bufferList.shift();for(;e.ts-this.player.videoTimestamp<-1e3&&this.bufferList.length>0;)e=this.bufferList.shift();if(0===this.bufferList.length)return}}if(0===this.bufferList.length)return;const t=this.bufferList.shift();t&&t.ts&&(this.player.audioTimestamp=t.ts);for(let o=0;o<e;o++){const e=t.buffer[o],r=i.getChannelData(o);for(let t=0;t<1024;t++)r[t]=e[t]||0}}},t.connect(this.gainNode),this.scriptNode=t,this.gainNode.connect(this.audioContext.destination),this.gainNode.connect(this.mediaStreamAudioDestinationNode),this.hasInitScriptNode=!0}mute(e){e?(this.isMute||this.player.emit(T.mute,e),this.setVolume(0),this.audioEnabled(!1),this.clear()):(this.isMute&&this.player.emit(T.mute,e),this.setVolume(.5),this.audioEnabled(!0))}setVolume(e){e=parseFloat(e).toFixed(2),isNaN(e)||(this.audioEnabled(!0),e=ue(e,0,1),this.gainNode.gain.value=e,this.gainNode.gain.setValueAtTime(e,this.audioContext.currentTime),this.player.emit(T.volumechange,this.player.volume))}closeAudio(){this.hasInitScriptNode&&(this.scriptNode&&this.scriptNode.disconnect(this.gainNode),this.gainNode&&this.gainNode.disconnect(this.audioContext.destination),this.gainNode&&this.gainNode.disconnect(this.mediaStreamAudioDestinationNode)),this.clear()}audioEnabled(e){e?"suspended"===this.audioContext.state&&this.audioContext.resume():"running"===this.audioContext.state&&this.audioContext.suspend()}isStateRunning(){return"running"===this.audioContext.state}isStateSuspended(){return"suspended"===this.audioContext.state}clear(){this.bufferList=[]}play(e,t){this.isMute||(this.hasAudio=!0,this.bufferList.push({buffer:e,ts:t}),this.bufferList.length>20&&(this.player.debug.warn("AudioContext",`bufferList is large: ${this.bufferList.length}`),this.bufferList.length>50&&this.bufferList.shift()))}pause(){this.audioSyncVideoOption={diff:null},this.playing=!1,this.clear()}resume(){this.playing=!0}}class Le{constructor(e){return new(Le.getLoaderFactory())(e)}static getLoaderFactory(){return je}}class Fe extends we{constructor(e){super(),this.player=e,this.playing=!1,this.abortController=new AbortController,this.streamRate=ge((t=>{e.emit(T.kBps,(t/1024).toFixed(2))})),e.debug.log("FetchStream","init")}destroy(){this.abort(),this.off(),this.streamRate=null,this.player.debug.log("FetchStream","destroy")}fetchStream(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{demux:i}=this.player;this.player._times.streamStart=le();const o=Object.assign({signal:this.abortController.signal},{headers:t.headers||{}});fetch(e,o).then((e=>{const t=e.body.getReader();this.emit(T.streamSuccess);const o=()=>{t.read().then((e=>{let{done:t,value:r}=e;t?i.close():(this.streamRate&&this.streamRate(r.byteLength),i.dispatch(r),o())})).catch((e=>{i.close();const t=e.toString();this.abort(),-1===t.indexOf(se)&&(this.emit(x.fetchError,e),this.player.emit(T.error,x.fetchError))}))};o()})).catch((e=>{this.abort(),this.emit(x.fetchError,e),this.player.emit(T.error,x.fetchError)}))}abort(){this.abortController&&(this.abortController.abort(),this.abortController=null)}}class Oe extends we{constructor(e){super(),this.player=e,this.socket=null,this.socketStatus=D,this.wsUrl=null,this.streamRate=ge((t=>{e.emit(T.kBps,(t/1024).toFixed(2))})),e.debug.log("WebsocketLoader","init")}destroy(){this.socket&&(this.socket.close(),this.socket=null),this.socketStatus=D,this.streamRate=null,this.wsUrl=null,this.off(),this.player.debug.log("websocketLoader","destroy")}_createWebSocket(){const e=this.player,{debug:t,events:{proxy:i},demux:o}=e;this.socket=new WebSocket(this.wsUrl),this.socket.binaryType="arraybuffer",i(this.socket,"open",(()=>{this.emit(T.streamSuccess),t.log("websocketLoader","socket open"),this.socketStatus=j})),i(this.socket,"message",(e=>{this.streamRate&&this.streamRate(e.data.byteLength),this._handleMessage(e.data)})),i(this.socket,"close",(()=>{t.log("websocketLoader","socket close"),this.emit(T.streamEnd),this.socketStatus=L})),i(this.socket,"error",(e=>{t.log("websocketLoader","socket error"),this.emit(x.websocketError,e),this.player.emit(T.error,x.websocketError),this.socketStatus=F,o.close(),t.log("websocketLoader","socket error:",e)}))}_handleMessage(e){const{demux:t}=this.player;t?t.dispatch(e):this.player.debug.warn("websocketLoader","websocket handle message demux is null")}fetchStream(e,t){this.player._times.streamStart=le(),this.wsUrl=e,this._createWebSocket()}}class Ve{constructor(e){return new(Ve.getLoaderFactory(e._opt.protocol))(e)}static getLoaderFactory(e){return e===a?Fe:e===s?Oe:void 0}}var Me=t((function(t){function i(e,t){if(!e)throw"First parameter is required.";t=new o(e,t=t||{type:"video"});var s=this;function a(i){i&&(t.initCallback=function(){i(),i=t.initCallback=null});var o=new r(e,t);(h=new o(e,t)).record(),u("recording"),t.disableLogs||console.log("Initialized recorderType:",h.constructor.name,"for output-type:",t.type)}function n(e){if(e=e||function(){},h){if("paused"===s.state)return s.resumeRecording(),void setTimeout((function(){n(e)}),1);"recording"===s.state||t.disableLogs||console.warn('Recording state should be: "recording", however current state is: ',s.state),t.disableLogs||console.log("Stopped recording "+t.type+" stream."),"gif"!==t.type?h.stop(i):(h.stop(),i()),u("stopped")}else m();function i(i){if(h){Object.keys(h).forEach((function(e){"function"!=typeof h[e]&&(s[e]=h[e])}));var o=h.blob;if(!o){if(!i)throw"Recording failed.";h.blob=o=i}if(o&&!t.disableLogs&&console.log(o.type,"->",b(o.size)),e){var r;try{r=l.createObjectURL(o)}catch(e){}"function"==typeof e.call?e.call(s,r):e(r)}t.autoWriteToDisk&&d((function(e){var i={};i[t.type+"Blob"]=e,x.Store(i)}))}else"function"==typeof e.call?e.call(s,""):e("")}}function A(e){postMessage((new FileReaderSync).readAsDataURL(e))}function d(e,i){if(!e)throw"Pass a callback function over getDataURL.";var o=i?i.blob:(h||{}).blob;if(!o)return t.disableLogs||console.warn("Blob encoder did not finish its job yet."),void setTimeout((function(){d(e,i)}),1e3);if("undefined"==typeof Worker||navigator.mozGetUserMedia){var r=new FileReader;r.readAsDataURL(o),r.onload=function(t){e(t.target.result)}}else{var s=function(e){try{var t=l.createObjectURL(new Blob([e.toString(),"this.onmessage = function (eee) {"+e.name+"(eee.data);}"],{type:"application/javascript"})),i=new Worker(t);return l.revokeObjectURL(t),i}catch(e){}}(A);s.onmessage=function(t){e(t.data)},s.postMessage(o)}}function c(e){e=e||0,"paused"!==s.state?"stopped"!==s.state&&(e>=s.recordingDuration?n(s.onRecordingStopped):(e+=1e3,setTimeout((function(){c(e)}),1e3))):setTimeout((function(){c(e)}),1e3)}function u(e){s&&(s.state=e,"function"==typeof s.onStateChanged.call?s.onStateChanged.call(s,e):s.onStateChanged(e))}var h,p='It seems that recorder is destroyed or "startRecording" is not invoked for '+t.type+" recorder.";function m(){!0!==t.disableLogs&&console.warn(p)}var g={startRecording:function(i){return t.disableLogs||console.log("RecordRTC version: ",s.version),i&&(t=new o(e,i)),t.disableLogs||console.log("started recording "+t.type+" stream."),h?(h.clearRecordedData(),h.record(),u("recording"),s.recordingDuration&&c(),s):(a((function(){s.recordingDuration&&c()})),s)},stopRecording:n,pauseRecording:function(){h?"recording"===s.state?(u("paused"),h.pause(),t.disableLogs||console.log("Paused recording.")):t.disableLogs||console.warn("Unable to pause the recording. Recording state: ",s.state):m()},resumeRecording:function(){h?"paused"===s.state?(u("recording"),h.resume(),t.disableLogs||console.log("Resumed recording.")):t.disableLogs||console.warn("Unable to resume the recording. Recording state: ",s.state):m()},initRecorder:a,setRecordingDuration:function(e,t){if(void 0===e)throw"recordingDuration is required.";if("number"!=typeof e)throw"recordingDuration must be a number.";return s.recordingDuration=e,s.onRecordingStopped=t||function(){},{onRecordingStopped:function(e){s.onRecordingStopped=e}}},clearRecordedData:function(){h?(h.clearRecordedData(),t.disableLogs||console.log("Cleared old recorded data.")):m()},getBlob:function(){if(h)return h.blob;m()},getDataURL:d,toURL:function(){if(h)return l.createObjectURL(h.blob);m()},getInternalRecorder:function(){return h},save:function(e){h?y(h.blob,e):m()},getFromDisk:function(e){h?i.getFromDisk(t.type,e):m()},setAdvertisementArray:function(e){t.advertisement=[];for(var i=e.length,o=0;o<i;o++)t.advertisement.push({duration:o,image:e[o]})},blob:null,bufferSize:0,sampleRate:0,buffer:null,reset:function(){"recording"!==s.state||t.disableLogs||console.warn("Stop an active recorder."),h&&"function"==typeof h.clearRecordedData&&h.clearRecordedData(),h=null,u("inactive"),s.blob=null},onStateChanged:function(e){t.disableLogs||console.log("Recorder state changed:",e)},state:"inactive",getState:function(){return s.state},destroy:function(){var e=t.disableLogs;t={disableLogs:!0},s.reset(),u("destroyed"),g=s=null,E.AudioContextConstructor&&(E.AudioContextConstructor.close(),E.AudioContextConstructor=null),t.disableLogs=e,t.disableLogs||console.log("RecordRTC is destroyed.")},version:"5.6.2"};if(!this)return s=g,g;for(var f in g)this[f]=g[f];return s=this,g}function o(e,t){return t.recorderType||t.type||(t.audio&&t.video?t.type="video":t.audio&&!t.video&&(t.type="audio")),t.recorderType&&!t.type&&(t.recorderType===T||t.recorderType===k||t.recorderType===F?t.type="video":t.recorderType===D?t.type="gif":t.recorderType===R?t.type="audio":t.recorderType===C&&(w(e,"audio").length&&w(e,"video").length||!w(e,"audio").length&&w(e,"video").length?t.type="video":w(e,"audio").length&&!w(e,"video").length&&(t.type="audio"))),"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&(t.mimeType||(t.mimeType="video/webm"),t.type||(t.type=t.mimeType.split("/")[0]),t.bitsPerSecond),t.type||(t.mimeType&&(t.type=t.mimeType.split("/")[0]),t.type||(t.type="audio")),t}function r(e,t){var i;return(m||u||h)&&(i=R),"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&!m&&(i=C),"video"===t.type&&(m||h)&&(i=T,"undefined"!=typeof ReadableStream&&(i=F)),"gif"===t.type&&(i=D),"canvas"===t.type&&(i=k),B()&&i!==k&&i!==D&&"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&(w(e,"video").length||w(e,"audio").length)&&("audio"===t.type?"function"==typeof MediaRecorder.isTypeSupported&&MediaRecorder.isTypeSupported("audio/webm")&&(i=C):"function"==typeof MediaRecorder.isTypeSupported&&MediaRecorder.isTypeSupported("video/webm")&&(i=C)),e instanceof Array&&e.length&&(i=L),t.recorderType&&(i=t.recorderType),!t.disableLogs&&i&&i.name&&console.log("Using recorderType:",i.name||i.constructor.name),!i&&g&&(i=C),i}function s(e){this.addStream=function(t){t&&(e=t)},this.mediaType={audio:!0,video:!0},this.startRecording=function(){var t,o=this.mediaType,r=this.mimeType||{audio:null,video:null,gif:null};if("function"!=typeof o.audio&&B()&&!w(e,"audio").length&&(o.audio=!1),"function"!=typeof o.video&&B()&&!w(e,"video").length&&(o.video=!1),"function"!=typeof o.gif&&B()&&!w(e,"video").length&&(o.gif=!1),!o.audio&&!o.video&&!o.gif)throw"MediaStream must have either audio or video tracks.";if(o.audio&&(t=null,"function"==typeof o.audio&&(t=o.audio),this.audioRecorder=new i(e,{type:"audio",bufferSize:this.bufferSize,sampleRate:this.sampleRate,numberOfAudioChannels:this.numberOfAudioChannels||2,disableLogs:this.disableLogs,recorderType:t,mimeType:r.audio,timeSlice:this.timeSlice,onTimeStamp:this.onTimeStamp}),o.video||this.audioRecorder.startRecording()),o.video){t=null,"function"==typeof o.video&&(t=o.video);var s=e;if(B()&&o.audio&&"function"==typeof o.audio){var a=w(e,"video")[0];p?((s=new f).addTrack(a),t&&t===T&&(t=C)):(s=new f).addTrack(a)}this.videoRecorder=new i(s,{type:"video",video:this.video,canvas:this.canvas,frameInterval:this.frameInterval||10,disableLogs:this.disableLogs,recorderType:t,mimeType:r.video,timeSlice:this.timeSlice,onTimeStamp:this.onTimeStamp,workerPath:this.workerPath,webAssemblyPath:this.webAssemblyPath,frameRate:this.frameRate,bitrate:this.bitrate}),o.audio||this.videoRecorder.startRecording()}if(o.audio&&o.video){var n=this,A=!0===B();(o.audio instanceof R&&o.video||!0!==o.audio&&!0!==o.video&&o.audio!==o.video)&&(A=!1),!0===A?(n.audioRecorder=null,n.videoRecorder.startRecording()):n.videoRecorder.initRecorder((function(){n.audioRecorder.initRecorder((function(){n.videoRecorder.startRecording(),n.audioRecorder.startRecording()}))}))}o.gif&&(t=null,"function"==typeof o.gif&&(t=o.gif),this.gifRecorder=new i(e,{type:"gif",frameRate:this.frameRate||200,quality:this.quality||10,disableLogs:this.disableLogs,recorderType:t,mimeType:r.gif}),this.gifRecorder.startRecording())},this.stopRecording=function(e){e=e||function(){},this.audioRecorder&&this.audioRecorder.stopRecording((function(t){e(t,"audio")})),this.videoRecorder&&this.videoRecorder.stopRecording((function(t){e(t,"video")})),this.gifRecorder&&this.gifRecorder.stopRecording((function(t){e(t,"gif")}))},this.pauseRecording=function(){this.audioRecorder&&this.audioRecorder.pauseRecording(),this.videoRecorder&&this.videoRecorder.pauseRecording(),this.gifRecorder&&this.gifRecorder.pauseRecording()},this.resumeRecording=function(){this.audioRecorder&&this.audioRecorder.resumeRecording(),this.videoRecorder&&this.videoRecorder.resumeRecording(),this.gifRecorder&&this.gifRecorder.resumeRecording()},this.getBlob=function(e){var t={};return this.audioRecorder&&(t.audio=this.audioRecorder.getBlob()),this.videoRecorder&&(t.video=this.videoRecorder.getBlob()),this.gifRecorder&&(t.gif=this.gifRecorder.getBlob()),e&&e(t),t},this.destroy=function(){this.audioRecorder&&(this.audioRecorder.destroy(),this.audioRecorder=null),this.videoRecorder&&(this.videoRecorder.destroy(),this.videoRecorder=null),this.gifRecorder&&(this.gifRecorder.destroy(),this.gifRecorder=null)},this.getDataURL=function(e){function t(e,t){if("undefined"!=typeof Worker){var i=function(e){var t,i=l.createObjectURL(new Blob([e.toString(),"this.onmessage = function (eee) {"+e.name+"(eee.data);}"],{type:"application/javascript"})),o=new Worker(i);if(void 0!==l)t=l;else{if("undefined"==typeof webkitURL)throw"Neither URL nor webkitURL detected.";t=webkitURL}return t.revokeObjectURL(i),o}((function(e){postMessage((new FileReaderSync).readAsDataURL(e))}));i.onmessage=function(e){t(e.data)},i.postMessage(e)}else{var o=new FileReader;o.readAsDataURL(e),o.onload=function(e){t(e.target.result)}}}this.getBlob((function(i){i.audio&&i.video?t(i.audio,(function(o){t(i.video,(function(t){e({audio:o,video:t})}))})):i.audio?t(i.audio,(function(t){e({audio:t})})):i.video&&t(i.video,(function(t){e({video:t})}))}))},this.writeToDisk=function(){i.writeToDisk({audio:this.audioRecorder,video:this.videoRecorder,gif:this.gifRecorder})},this.save=function(e){(e=e||{audio:!0,video:!0,gif:!0}).audio&&this.audioRecorder&&this.audioRecorder.save("string"==typeof e.audio?e.audio:""),e.video&&this.videoRecorder&&this.videoRecorder.save("string"==typeof e.video?e.video:""),e.gif&&this.gifRecorder&&this.gifRecorder.save("string"==typeof e.gif?e.gif:"")}}i.version="5.6.2",t.exports=i,i.getFromDisk=function(e,t){if(!t)throw"callback is mandatory.";console.log("Getting recorded "+("all"===e?"blobs":e+" blob ")+" from disk!"),x.Fetch((function(i,o){"all"!==e&&o===e+"Blob"&&t&&t(i),"all"===e&&t&&t(i,o.replace("Blob",""))}))},i.writeToDisk=function(e){console.log("Writing recorded blob(s) to disk!"),(e=e||{}).audio&&e.video&&e.gif?e.audio.getDataURL((function(t){e.video.getDataURL((function(i){e.gif.getDataURL((function(e){x.Store({audioBlob:t,videoBlob:i,gifBlob:e})}))}))})):e.audio&&e.video?e.audio.getDataURL((function(t){e.video.getDataURL((function(e){x.Store({audioBlob:t,videoBlob:e})}))})):e.audio&&e.gif?e.audio.getDataURL((function(t){e.gif.getDataURL((function(e){x.Store({audioBlob:t,gifBlob:e})}))})):e.video&&e.gif?e.video.getDataURL((function(t){e.gif.getDataURL((function(e){x.Store({videoBlob:t,gifBlob:e})}))})):e.audio?e.audio.getDataURL((function(e){x.Store({audioBlob:e})})):e.video?e.video.getDataURL((function(e){x.Store({videoBlob:e})})):e.gif&&e.gif.getDataURL((function(e){x.Store({gifBlob:e})}))},s.getFromDisk=i.getFromDisk,s.writeToDisk=i.writeToDisk,i.MRecordRTC=s;var a;(a=void 0!==e?e:null)&&"undefined"==typeof window&&void 0!==e&&(e.navigator={userAgent:"Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",getUserMedia:function(){}},e.console||(e.console={}),void 0!==e.console.log&&void 0!==e.console.error||(e.console.error=e.console.log=e.console.log||function(){console.log(arguments)}),"undefined"==typeof document&&(a.document={documentElement:{appendChild:function(){return""}}},document.createElement=document.captureStream=document.mozCaptureStream=function(){var e={getContext:function(){return e},play:function(){},pause:function(){},drawImage:function(){},toDataURL:function(){return""},style:{}};return e},a.HTMLVideoElement=function(){}),"undefined"==typeof location&&(a.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(a.screen={width:0,height:0}),void 0===l&&(a.URL={createObjectURL:function(){return""},revokeObjectURL:function(){return""}}),a.window=e);var n=window.requestAnimationFrame;if(void 0===n)if("undefined"!=typeof webkitRequestAnimationFrame)n=webkitRequestAnimationFrame;else if("undefined"!=typeof mozRequestAnimationFrame)n=mozRequestAnimationFrame;else if("undefined"!=typeof msRequestAnimationFrame)n=msRequestAnimationFrame;else if(void 0===n){var A=0;n=function(e,t){var i=(new Date).getTime(),o=Math.max(0,16-(i-A)),r=setTimeout((function(){e(i+o)}),o);return A=i+o,r}}var d=window.cancelAnimationFrame;void 0===d&&("undefined"!=typeof webkitCancelAnimationFrame?d=webkitCancelAnimationFrame:"undefined"!=typeof mozCancelAnimationFrame?d=mozCancelAnimationFrame:"undefined"!=typeof msCancelAnimationFrame?d=msCancelAnimationFrame:void 0===d&&(d=function(e){clearTimeout(e)}));var c=window.AudioContext;void 0===c&&("undefined"!=typeof webkitAudioContext&&(c=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(c=mozAudioContext));var l=window.URL;void 0===l&&"undefined"!=typeof webkitURL&&(l=webkitURL),"undefined"!=typeof navigator&&void 0===navigator.getUserMedia&&(void 0!==navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),void 0!==navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var u=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),h=!!window.opera||-1!==navigator.userAgent.indexOf("OPR/"),p=navigator.userAgent.toLowerCase().indexOf("firefox")>-1&&"netscape"in window&&/ rv:/.test(navigator.userAgent),m=!h&&!u&&!!navigator.webkitGetUserMedia||v()||-1!==navigator.userAgent.toLowerCase().indexOf("chrome/"),g=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);g&&!m&&-1!==navigator.userAgent.indexOf("CriOS")&&(g=!1,m=!0);var f=window.MediaStream;function b(e){if(0===e)return"0 Bytes";var t=parseInt(Math.floor(Math.log(e)/Math.log(1e3)),10);return(e/Math.pow(1e3,t)).toPrecision(3)+" "+["Bytes","KB","MB","GB","TB"][t]}function y(e,t){if(!e)throw"Blob object is required.";if(!e.type)try{e.type="video/webm"}catch(e){}var i=(e.type||"video/webm").split("/")[1];if(-1!==i.indexOf(";")&&(i=i.split(";")[0]),t&&-1!==t.indexOf(".")){var o=t.split(".");t=o[0],i=o[1]}var r=(t||Math.round(9999999999*Math.random())+888888888)+"."+i;if(void 0!==navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(e,r);if(void 0!==navigator.msSaveBlob)return navigator.msSaveBlob(e,r);var s=document.createElement("a");s.href=l.createObjectURL(e),s.download=r,s.style="display:none;opacity:0;color:transparent;",(document.body||document.documentElement).appendChild(s),"function"==typeof s.click?s.click():(s.target="_blank",s.dispatchEvent(new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0}))),l.revokeObjectURL(s.href)}function v(){return"undefined"!=typeof window&&"object"==typeof window.process&&"renderer"===window.process.type||(!("undefined"==typeof process||"object"!=typeof process.versions||!process.versions.electron)||"object"==typeof navigator&&"string"==typeof navigator.userAgent&&navigator.userAgent.indexOf("Electron")>=0)}function w(e,t){return e&&e.getTracks?e.getTracks().filter((function(e){return e.kind===(t||"audio")})):[]}function S(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}void 0===f&&"undefined"!=typeof webkitMediaStream&&(f=webkitMediaStream),void 0!==f&&void 0===f.prototype.stop&&(f.prototype.stop=function(){this.getTracks().forEach((function(e){e.stop()}))}),i.invokeSaveAsDialog=y,i.getTracks=w,i.getSeekableBlob=function(e,t){if("undefined"==typeof EBML)throw new Error("Please link: https://www.webrtc-experiment.com/EBML.js");var i=new EBML.Reader,o=new EBML.Decoder,r=EBML.tools,s=new FileReader;s.onload=function(e){o.decode(this.result).forEach((function(e){i.read(e)})),i.stop();var s=r.makeMetadataSeekable(i.metadatas,i.duration,i.cues),a=this.result.slice(i.metadataSize),n=new Blob([s,a],{type:"video/webm"});t(n)},s.readAsArrayBuffer(e)},i.bytesToSize=b,i.isElectron=v;var E={};function B(){if(p||g||u)return!0;var e,t,i=navigator.userAgent,o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10);return(m||h)&&(e=i.indexOf("Chrome"),o=i.substring(e+7)),-1!==(t=o.indexOf(";"))&&(o=o.substring(0,t)),-1!==(t=o.indexOf(" "))&&(o=o.substring(0,t)),r=parseInt(""+o,10),isNaN(r)&&(o=""+parseFloat(navigator.appVersion),r=parseInt(navigator.appVersion,10)),r>=49}function C(e,t){var i=this;if(void 0===e)throw'First argument "MediaStream" is required.';if("undefined"==typeof MediaRecorder)throw"Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.";if("audio"===(t=t||{mimeType:"video/webm"}).type){var o;if(w(e,"video").length&&w(e,"audio").length)navigator.mozGetUserMedia?(o=new f).addTrack(w(e,"audio")[0]):o=new f(w(e,"audio")),e=o;t.mimeType&&-1!==t.mimeType.toString().toLowerCase().indexOf("audio")||(t.mimeType=m?"audio/webm":"audio/ogg"),t.mimeType&&"audio/ogg"!==t.mimeType.toString().toLowerCase()&&navigator.mozGetUserMedia&&(t.mimeType="audio/ogg")}var r,s=[];function a(){i.timestamps.push((new Date).getTime()),"function"==typeof t.onTimeStamp&&t.onTimeStamp(i.timestamps[i.timestamps.length-1],i.timestamps)}function n(e){return r&&r.mimeType?r.mimeType:e.mimeType||"video/webm"}function A(){s=[],r=null,i.timestamps=[]}this.getArrayOfBlobs=function(){return s},this.record=function(){i.blob=null,i.clearRecordedData(),i.timestamps=[],d=[],s=[];var o=t;t.disableLogs||console.log("Passing following config over MediaRecorder API.",o),r&&(r=null),m&&!B()&&(o="video/vp8"),"function"==typeof MediaRecorder.isTypeSupported&&o.mimeType&&(MediaRecorder.isTypeSupported(o.mimeType)||(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType),o.mimeType="audio"===t.type?"audio/webm":"video/webm"));try{r=new MediaRecorder(e,o),t.mimeType=o.mimeType}catch(t){r=new MediaRecorder(e)}o.mimeType&&!MediaRecorder.isTypeSupported&&"canRecordMimeType"in r&&!1===r.canRecordMimeType(o.mimeType)&&(t.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",o.mimeType)),r.ondataavailable=function(e){if(e.data&&d.push("ondataavailable: "+b(e.data.size)),"number"!=typeof t.timeSlice)!e.data||!e.data.size||e.data.size<100||i.blob?i.recordingCallback&&(i.recordingCallback(new Blob([],{type:n(o)})),i.recordingCallback=null):(i.blob=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)}),i.recordingCallback&&(i.recordingCallback(i.blob),i.recordingCallback=null));else if(e.data&&e.data.size&&(s.push(e.data),a(),"function"==typeof t.ondataavailable)){var r=t.getNativeBlob?e.data:new Blob([e.data],{type:n(o)});t.ondataavailable(r)}},r.onstart=function(){d.push("started")},r.onpause=function(){d.push("paused")},r.onresume=function(){d.push("resumed")},r.onstop=function(){d.push("stopped")},r.onerror=function(e){e&&(e.name||(e.name="UnknownError"),d.push("error: "+e),t.disableLogs||(-1!==e.name.toString().toLowerCase().indexOf("invalidstate")?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.",e):-1!==e.name.toString().toLowerCase().indexOf("notsupported")?console.error("MIME type (",o.mimeType,") is not supported.",e):-1!==e.name.toString().toLowerCase().indexOf("security")?console.error("MediaRecorder security error",e):"OutOfMemory"===e.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"IllegalStreamModification"===e.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"OtherRecordingError"===e.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.",e):"GenericError"===e.name?console.error("The UA cannot provide the codec or recording option that has been requested.",e):console.error("MediaRecorder Error",e)),function(e){if(!i.manuallyStopped&&r&&"inactive"===r.state)return delete t.timeslice,void r.start(6e5);setTimeout(void 0,1e3)}(),"inactive"!==r.state&&"stopped"!==r.state&&r.stop())},"number"==typeof t.timeSlice?(a(),r.start(t.timeSlice)):r.start(36e5),t.initCallback&&t.initCallback()},this.timestamps=[],this.stop=function(e){e=e||function(){},i.manuallyStopped=!0,r&&(this.recordingCallback=e,"recording"===r.state&&r.stop(),"number"==typeof t.timeSlice&&setTimeout((function(){i.blob=new Blob(s,{type:n(t)}),i.recordingCallback(i.blob)}),100))},this.pause=function(){r&&"recording"===r.state&&r.pause()},this.resume=function(){r&&"paused"===r.state&&r.resume()},this.clearRecordedData=function(){r&&"recording"===r.state&&i.stop(A),A()},this.getInternalRecorder=function(){return r},this.blob=null,this.getState=function(){return r&&r.state||"inactive"};var d=[];this.getAllStates=function(){return d},void 0===t.checkForInactiveTracks&&(t.checkForInactiveTracks=!1);i=this;!function o(){if(r&&!1!==t.checkForInactiveTracks)return!1===function(){if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}()?(t.disableLogs||console.log("MediaStream seems stopped."),void i.stop()):void setTimeout(o,1e3)}(),this.name="MediaStreamRecorder",this.toString=function(){return this.name}}function R(e,t){if(!w(e,"audio").length)throw"Your stream has no audio tracks.";var o,r=this,s=[],a=[],n=!1,A=0,d=2,c=(t=t||{}).desiredSampRate;function u(){if(!1===t.checkForInactiveTracks)return!0;if("active"in e){if(!e.active)return!1}else if("ended"in e&&e.ended)return!1;return!0}function h(e,t){function i(e,t){var i,o=e.numberOfAudioChannels,r=e.leftBuffers.slice(0),s=e.rightBuffers.slice(0),a=e.sampleRate,n=e.internalInterleavedLength,A=e.desiredSampRate;function d(e,t,i){var o=Math.round(e.length*(t/i)),r=[],s=Number((e.length-1)/(o-1));r[0]=e[0];for(var a=1;a<o-1;a++){var n=a*s,A=Number(Math.floor(n)).toFixed(),d=Number(Math.ceil(n)).toFixed(),l=n-A;r[a]=c(e[A],e[d],l)}return r[o-1]=e[e.length-1],r}function c(e,t,i){return e+(t-e)*i}function l(e,t){for(var i=new Float64Array(t),o=0,r=e.length,s=0;s<r;s++){var a=e[s];i.set(a,o),o+=a.length}return i}function u(e,t,i){for(var o=i.length,r=0;r<o;r++)e.setUint8(t+r,i.charCodeAt(r))}2===o&&(r=l(r,n),s=l(s,n),A&&(r=d(r,A,a),s=d(s,A,a))),1===o&&(r=l(r,n),A&&(r=d(r,A,a))),A&&(a=A),2===o&&(i=function(e,t){for(var i=e.length+t.length,o=new Float64Array(i),r=0,s=0;s<i;)o[s++]=e[r],o[s++]=t[r],r++;return o}(r,s)),1===o&&(i=r);var h=i.length,p=new ArrayBuffer(44+2*h),m=new DataView(p);u(m,0,"RIFF"),m.setUint32(4,36+2*h,!0),u(m,8,"WAVE"),u(m,12,"fmt "),m.setUint32(16,16,!0),m.setUint16(20,1,!0),m.setUint16(22,o,!0),m.setUint32(24,a,!0),m.setUint32(28,a*o*2,!0),m.setUint16(32,2*o,!0),m.setUint16(34,16,!0),u(m,36,"data"),m.setUint32(40,2*h,!0);for(var g=h,f=44,b=0;b<g;b++)m.setInt16(f,32767*i[b],!0),f+=2;if(t)return t({buffer:p,view:m});postMessage({buffer:p,view:m})}if(e.noWorker)i(e,(function(e){t(e.buffer,e.view)}));else{var o,r,s,a=(o=i,r=l.createObjectURL(new Blob([o.toString(),";this.onmessage = function (eee) {"+o.name+"(eee.data);}"],{type:"application/javascript"})),(s=new Worker(r)).workerURL=r,s);a.onmessage=function(e){t(e.data.buffer,e.data.view),l.revokeObjectURL(a.workerURL),a.terminate()},a.postMessage(e)}}!0===t.leftChannel&&(d=1),1===t.numberOfAudioChannels&&(d=1),(!d||d<1)&&(d=2),t.disableLogs||console.log("StereoAudioRecorder is set to record number of channels: "+d),void 0===t.checkForInactiveTracks&&(t.checkForInactiveTracks=!0),this.record=function(){if(!1===u())throw"Please make sure MediaStream is active.";v(),E=y=!1,n=!0,void 0!==t.timeSlice&&C()},this.stop=function(e){e=e||function(){},n=!1,h({desiredSampRate:c,sampleRate:b,numberOfAudioChannels:d,internalInterleavedLength:A,leftBuffers:s,rightBuffers:1===d?[]:a,noWorker:t.noWorker},(function(t,i){r.blob=new Blob([i],{type:"audio/wav"}),r.buffer=new ArrayBuffer(i.buffer.byteLength),r.view=i,r.sampleRate=c||b,r.bufferSize=f,r.length=A,E=!1,e&&e(r.blob)}))},void 0===i.Storage&&(i.Storage={AudioContextConstructor:null,AudioContext:window.AudioContext||window.webkitAudioContext}),i.Storage.AudioContextConstructor&&"closed"!==i.Storage.AudioContextConstructor.state||(i.Storage.AudioContextConstructor=new i.Storage.AudioContext);var p=i.Storage.AudioContextConstructor,m=p.createMediaStreamSource(e),g=[0,256,512,1024,2048,4096,8192,16384],f=void 0===t.bufferSize?4096:t.bufferSize;if(-1===g.indexOf(f)&&(t.disableLogs||console.log("Legal values for buffer-size are "+JSON.stringify(g,null,"\t"))),p.createJavaScriptNode)o=p.createJavaScriptNode(f,d,d);else{if(!p.createScriptProcessor)throw"WebAudio API has no support on this browser.";o=p.createScriptProcessor(f,d,d)}m.connect(o),t.bufferSize||(f=o.bufferSize);var b=void 0!==t.sampleRate?t.sampleRate:p.sampleRate||44100;(b<22050||b>96e3)&&(t.disableLogs||console.log("sample-rate must be under range 22050 and 96000.")),t.disableLogs||t.desiredSampRate&&console.log("Desired sample-rate: "+t.desiredSampRate);var y=!1;function v(){s=[],a=[],A=0,E=!1,n=!1,y=!1,p=null,r.leftchannel=s,r.rightchannel=a,r.numberOfAudioChannels=d,r.desiredSampRate=c,r.sampleRate=b,r.recordingLength=A,B={left:[],right:[],recordingLength:0}}function S(){o&&(o.onaudioprocess=null,o.disconnect(),o=null),m&&(m.disconnect(),m=null),v()}this.pause=function(){y=!0},this.resume=function(){if(!1===u())throw"Please make sure MediaStream is active.";if(!n)return t.disableLogs||console.log("Seems recording has been restarted."),void this.record();y=!1},this.clearRecordedData=function(){t.checkForInactiveTracks=!1,n&&this.stop(S),S()},this.name="StereoAudioRecorder",this.toString=function(){return this.name};var E=!1;o.onaudioprocess=function(e){if(!y)if(!1===u()&&(t.disableLogs||console.log("MediaStream seems stopped."),o.disconnect(),n=!1),n){E||(E=!0,t.onAudioProcessStarted&&t.onAudioProcessStarted(),t.initCallback&&t.initCallback());var i=e.inputBuffer.getChannelData(0),c=new Float32Array(i);if(s.push(c),2===d){var l=e.inputBuffer.getChannelData(1),h=new Float32Array(l);a.push(h)}A+=f,r.recordingLength=A,void 0!==t.timeSlice&&(B.recordingLength+=f,B.left.push(c),2===d&&B.right.push(h))}else m&&(m.disconnect(),m=null)},p.createMediaStreamDestination?o.connect(p.createMediaStreamDestination()):o.connect(p.destination),this.leftchannel=s,this.rightchannel=a,this.numberOfAudioChannels=d,this.desiredSampRate=c,this.sampleRate=b,r.recordingLength=A;var B={left:[],right:[],recordingLength:0};function C(){n&&"function"==typeof t.ondataavailable&&void 0!==t.timeSlice&&(B.left.length?(h({desiredSampRate:c,sampleRate:b,numberOfAudioChannels:d,internalInterleavedLength:B.recordingLength,leftBuffers:B.left,rightBuffers:1===d?[]:B.right},(function(e,i){var o=new Blob([i],{type:"audio/wav"});t.ondataavailable(o),setTimeout(C,t.timeSlice)})),B={left:[],right:[],recordingLength:0}):setTimeout(C,t.timeSlice))}}function k(e,t){if("undefined"==typeof html2canvas)throw"Please link: https://www.webrtc-experiment.com/screenshot.js";(t=t||{}).frameInterval||(t.frameInterval=10);var i=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach((function(e){e in document.createElement("canvas")&&(i=!0)}));var o,r,s,a=!(!window.webkitRTCPeerConnection&&!window.webkitGetUserMedia||!window.chrome),n=50,A=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);if(a&&A&&A[2]&&(n=parseInt(A[2],10)),a&&n<52&&(i=!1),t.useWhammyRecorder&&(i=!1),i)if(t.disableLogs||console.log("Your browser supports both MediRecorder API and canvas.captureStream!"),e instanceof HTMLCanvasElement)o=e;else{if(!(e instanceof CanvasRenderingContext2D))throw"Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";o=e.canvas}else navigator.mozGetUserMedia&&(t.disableLogs||console.error("Canvas recording is NOT supported in Firefox."));this.record=function(){if(s=!0,i&&!t.useWhammyRecorder){var e;"captureStream"in o?e=o.captureStream(25):"mozCaptureStream"in o?e=o.mozCaptureStream(25):"webkitCaptureStream"in o&&(e=o.webkitCaptureStream(25));try{var a=new f;a.addTrack(w(e,"video")[0]),e=a}catch(e){}if(!e)throw"captureStream API are NOT available.";(r=new C(e,{mimeType:t.mimeType||"video/webm"})).record()}else h.frames=[],u=(new Date).getTime(),l();t.initCallback&&t.initCallback()},this.getWebPImages=function(i){if("canvas"===e.nodeName.toLowerCase()){var o=h.frames.length;h.frames.forEach((function(e,i){var r=o-i;t.disableLogs||console.log(r+"/"+o+" frames remaining"),t.onEncodingCallback&&t.onEncodingCallback(r,o);var s=e.image.toDataURL("image/webp",1);h.frames[i].image=s})),t.disableLogs||console.log("Generating WebM"),i()}else i()},this.stop=function(e){s=!1;var o=this;i&&r?r.stop(e):this.getWebPImages((function(){h.compile((function(i){t.disableLogs||console.log("Recording finished!"),o.blob=i,o.blob.forEach&&(o.blob=new Blob([],{type:"video/webm"})),e&&e(o.blob),h.frames=[]}))}))};var d=!1;function c(){h.frames=[],s=!1,d=!1}function l(){if(d)return u=(new Date).getTime(),setTimeout(l,500);if("canvas"===e.nodeName.toLowerCase()){var i=(new Date).getTime()-u;return u=(new Date).getTime(),h.frames.push({image:(o=document.createElement("canvas"),r=o.getContext("2d"),o.width=e.width,o.height=e.height,r.drawImage(e,0,0),o),duration:i}),void(s&&setTimeout(l,t.frameInterval))}var o,r;html2canvas(e,{grabMouse:void 0===t.showMousePointer||t.showMousePointer,onrendered:function(e){var i=(new Date).getTime()-u;if(!i)return setTimeout(l,t.frameInterval);u=(new Date).getTime(),h.frames.push({image:e.toDataURL("image/webp",1),duration:i}),s&&setTimeout(l,t.frameInterval)}})}this.pause=function(){d=!0,r instanceof C&&r.pause()},this.resume=function(){d=!1,r instanceof C?r.resume():s||this.record()},this.clearRecordedData=function(){s&&this.stop(c),c()},this.name="CanvasRecorder",this.toString=function(){return this.name};var u=(new Date).getTime(),h=new I.Video(100)}function T(e,t){function i(e){e=void 0!==e?e:10;var t=(new Date).getTime()-A;return t?s?(A=(new Date).getTime(),setTimeout(i,100)):(A=(new Date).getTime(),n.paused&&n.play(),l.drawImage(n,0,0,c.width,c.height),d.frames.push({duration:t,image:c.toDataURL("image/webp")}),void(r||setTimeout(i,e,e))):setTimeout(i,e,e)}function o(e,t,i,o,r){var s=document.createElement("canvas");s.width=c.width,s.height=c.height;var a,n,A,d=s.getContext("2d"),l=[],u=-1===t,h=t&&t>0&&t<=e.length?t:e.length,p=0,m=0,g=0,f=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),b=i&&i>=0&&i<=1?i:0,y=o&&o>=0&&o<=1?o:0,v=!1;n=-1,A=(a={length:h,functionToLoop:function(t,i){var o,r,s,a=function(){!v&&s-o<=s*y||(u&&(v=!0),l.push(e[i])),t()};if(v)a();else{var n=new Image;n.onload=function(){d.drawImage(n,0,0,c.width,c.height);var e=d.getImageData(0,0,c.width,c.height);o=0,r=e.data.length,s=e.data.length/4;for(var t=0;t<r;t+=4){var i={r:e.data[t],g:e.data[t+1],b:e.data[t+2]};Math.sqrt(Math.pow(i.r-p,2)+Math.pow(i.g-m,2)+Math.pow(i.b-g,2))<=f*b&&o++}a()},n.src=e[i].image}},callback:function(){(l=l.concat(e.slice(h))).length<=0&&l.push(e[e.length-1]),r(l)}}).length,function e(){++n!==A?setTimeout((function(){a.functionToLoop(e,n)}),1):a.callback()}()}(t=t||{}).frameInterval||(t.frameInterval=10),t.disableLogs||console.log("Using frames-interval:",t.frameInterval),this.record=function(){t.width||(t.width=320),t.height||(t.height=240),t.video||(t.video={width:t.width,height:t.height}),t.canvas||(t.canvas={width:t.width,height:t.height}),c.width=t.canvas.width||320,c.height=t.canvas.height||240,l=c.getContext("2d"),t.video&&t.video instanceof HTMLVideoElement?(n=t.video.cloneNode(),t.initCallback&&t.initCallback()):(n=document.createElement("video"),S(e,n),n.onloadedmetadata=function(){t.initCallback&&t.initCallback()},n.width=t.video.width,n.height=t.video.height),n.muted=!0,n.play(),A=(new Date).getTime(),d=new I.Video,t.disableLogs||(console.log("canvas resolutions",c.width,"*",c.height),console.log("video width/height",n.width||c.width,"*",n.height||c.height)),i(t.frameInterval)};var r=!1;this.stop=function(e){e=e||function(){},r=!0;var i=this;setTimeout((function(){o(d.frames,-1,null,null,(function(o){d.frames=o,t.advertisement&&t.advertisement.length&&(d.frames=t.advertisement.concat(d.frames)),d.compile((function(t){i.blob=t,i.blob.forEach&&(i.blob=new Blob([],{type:"video/webm"})),e&&e(i.blob)}))}))}),10)};var s=!1;function a(){d.frames=[],r=!0,s=!1}this.pause=function(){s=!0},this.resume=function(){s=!1,r&&this.record()},this.clearRecordedData=function(){r||this.stop(a),a()},this.name="WhammyRecorder",this.toString=function(){return this.name};var n,A,d,c=document.createElement("canvas"),l=c.getContext("2d")}void 0!==c?E.AudioContext=c:"undefined"!=typeof webkitAudioContext&&(E.AudioContext=webkitAudioContext),i.Storage=E,i.MediaStreamRecorder=C,i.StereoAudioRecorder=R,i.CanvasRecorder=k,i.WhammyRecorder=T;var I=function(){function e(e){this.frames=[],this.duration=e||1,this.quality=.8}function t(e){function t(e,t,i){return[{data:e,id:231}].concat(i.map((function(e){var i=function(e){var t=0;e.keyframe&&(t|=128);e.invisible&&(t|=8);e.lacing&&(t|=e.lacing<<1);e.discardable&&(t|=1);if(e.trackNum>127)throw"TrackNumber > 127 not supported";return[128|e.trackNum,e.timecode>>8,255&e.timecode,t].map((function(e){return String.fromCharCode(e)})).join("")+e.frame}({discardable:0,frame:e.data.slice(4),invisible:0,keyframe:1,lacing:0,trackNum:1,timecode:Math.round(t)});return t+=e.duration,{data:i,id:163}})))}function i(e){for(var t=[];e>0;)t.push(255&e),e>>=8;return new Uint8Array(t.reverse())}function o(e){var t=[];e=(e.length%8?new Array(9-e.length%8).join("0"):"")+e;for(var i=0;i<e.length;i+=8)t.push(parseInt(e.substr(i,8),2));return new Uint8Array(t)}function r(e){for(var t=[],s=0;s<e.length;s++){var a=e[s].data;"object"==typeof a&&(a=r(a)),"number"==typeof a&&(a=o(a.toString(2))),"string"==typeof a&&(a=new Uint8Array(a.split("").map((function(e){return e.charCodeAt(0)}))));var n=a.size||a.byteLength||a.length,A=Math.ceil(Math.ceil(Math.log(n)/Math.log(2))/8),d=n.toString(2),c=new Array(7*A+7+1-d.length).join("0")+d,l=new Array(A).join("0")+"1"+c;t.push(i(e[s].id)),t.push(o(l)),t.push(a)}return new Blob(t,{type:"video/webm"})}function s(e,t){return parseInt(e.substr(t+4,4).split("").map((function(e){var t=e.charCodeAt(0).toString(2);return new Array(8-t.length+1).join("0")+t})).join(""),2)}function a(e){for(var t=0,i={};t<e.length;){var o=e.substr(t,4),r=s(e,t),n=e.substr(t+4+4,r);t+=8+r,i[o]=i[o]||[],"RIFF"===o||"LIST"===o?i[o].push(a(n)):i[o].push(n)}return i}var n=new function(e){var i=function(e){if(!e[0])return void postMessage({error:"Something went wrong. Maybe WebP format is not supported in the current browser."});for(var t=e[0].width,i=e[0].height,o=e[0].duration,r=1;r<e.length;r++)o+=e[r].duration;return{duration:o,width:t,height:i}}(e);if(!i)return[];for(var o,s=[{id:440786851,data:[{data:1,id:17030},{data:1,id:17143},{data:4,id:17138},{data:8,id:17139},{data:"webm",id:17026},{data:2,id:17031},{data:2,id:17029}]},{id:408125543,data:[{id:357149030,data:[{data:1e6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:(o=i.duration,[].slice.call(new Uint8Array(new Float64Array([o]).buffer),0).map((function(e){return String.fromCharCode(e)})).reverse().join("")),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:29637},{data:0,id:156},{data:"und",id:2274716},{data:"V_VP8",id:134},{data:"VP8",id:2459272},{data:1,id:131},{id:224,data:[{data:i.width,id:176},{data:i.height,id:186}]}]}]}]}],a=0,n=0;a<e.length;){var A=[],d=0;do{A.push(e[a]),d+=e[a].duration,a++}while(a<e.length&&d<3e4);var c={id:524531317,data:t(n,0,A)};s[1].data.push(c),n+=d}return r(s)}(e.map((function(e){var t=function(e){for(var t=e.RIFF[0].WEBP[0],i=t.indexOf("*"),o=0,r=[];o<4;o++)r[o]=t.charCodeAt(i+3+o);return{width:16383&(r[1]<<8|r[0]),height:16383&(r[3]<<8|r[2]),data:t,riff:e}}(a(atob(e.image.slice(23))));return t.duration=e.duration,t})));postMessage(n)}return e.prototype.add=function(e,t){if("canvas"in e&&(e=e.canvas),"toDataURL"in e&&(e=e.toDataURL("image/webp",this.quality)),!/^data:image\/webp;base64,/gi.test(e))throw"Input must be formatted properly as a base64 encoded DataURI of type image/webp";this.frames.push({image:e,duration:t||this.duration})},e.prototype.compile=function(e){var i,o,r,s=(i=t,o=l.createObjectURL(new Blob([i.toString(),"this.onmessage = function (eee) {"+i.name+"(eee.data);}"],{type:"application/javascript"})),r=new Worker(o),l.revokeObjectURL(o),r);s.onmessage=function(t){t.data.error?console.error(t.data.error):e(t.data)},s.postMessage(this.frames)},{Video:e}}();i.Whammy=I;var x={init:function(){var e=this;if("undefined"!=typeof indexedDB&&void 0!==indexedDB.open){var t,i=this.dbName||location.href.replace(/\/|:|#|%|\.|\[|\]/g,""),o=indexedDB.open(i,1);o.onerror=e.onError,o.onsuccess=function(){((t=o.result).onerror=e.onError,t.setVersion)?1!==t.version?t.setVersion(1).onsuccess=function(){r(t),s()}:s():s()},o.onupgradeneeded=function(e){r(e.target.result)}}else console.error("IndexedDB API are not available in this browser.");function r(t){t.createObjectStore(e.dataStoreName)}function s(){var i=t.transaction([e.dataStoreName],"readwrite");function o(t){i.objectStore(e.dataStoreName).get(t).onsuccess=function(i){e.callback&&e.callback(i.target.result,t)}}e.videoBlob&&i.objectStore(e.dataStoreName).put(e.videoBlob,"videoBlob"),e.gifBlob&&i.objectStore(e.dataStoreName).put(e.gifBlob,"gifBlob"),e.audioBlob&&i.objectStore(e.dataStoreName).put(e.audioBlob,"audioBlob"),o("audioBlob"),o("videoBlob"),o("gifBlob")}},Fetch:function(e){return this.callback=e,this.init(),this},Store:function(e){return this.audioBlob=e.audioBlob,this.videoBlob=e.videoBlob,this.gifBlob=e.gifBlob,this.init(),this},onError:function(e){console.error(JSON.stringify(e,null,"\t"))},dataStoreName:"recordRTC",dbName:null};function D(e,t){if("undefined"==typeof GIFEncoder){var i=document.createElement("script");i.src="https://www.webrtc-experiment.com/gif-recorder.js",(document.body||document.documentElement).appendChild(i)}t=t||{};var o=e instanceof CanvasRenderingContext2D||e instanceof HTMLCanvasElement;this.record=function(){"undefined"!=typeof GIFEncoder&&A?(o||(t.width||(t.width=c.offsetWidth||320),t.height||(t.height=c.offsetHeight||240),t.video||(t.video={width:t.width,height:t.height}),t.canvas||(t.canvas={width:t.width,height:t.height}),s.width=t.canvas.width||320,s.height=t.canvas.height||240,c.width=t.video.width||320,c.height=t.video.height||240),(u=new GIFEncoder).setRepeat(0),u.setDelay(t.frameRate||200),u.setQuality(t.quality||10),u.start(),"function"==typeof t.onGifRecordingStarted&&t.onGifRecordingStarted(),h=n((function e(i){if(!0!==p.clearedRecordedData){if(r)return setTimeout((function(){e(i)}),100);h=n(e),void 0===typeof l&&(l=i),i-l<90||(!o&&c.paused&&c.play(),o||a.drawImage(c,0,0,s.width,s.height),t.onGifPreview&&t.onGifPreview(s.toDataURL("image/png")),u.addFrame(a),l=i)}})),t.initCallback&&t.initCallback()):setTimeout(p.record,1e3)},this.stop=function(e){e=e||function(){},h&&d(h),this.blob=new Blob([new Uint8Array(u.stream().bin)],{type:"image/gif"}),e(this.blob),u.stream().bin=[]};var r=!1;this.pause=function(){r=!0},this.resume=function(){r=!1},this.clearRecordedData=function(){p.clearedRecordedData=!0,u&&(u.stream().bin=[])},this.name="GifRecorder",this.toString=function(){return this.name};var s=document.createElement("canvas"),a=s.getContext("2d");o&&(e instanceof CanvasRenderingContext2D?s=(a=e).canvas:e instanceof HTMLCanvasElement&&(a=e.getContext("2d"),s=e));var A=!0;if(!o){var c=document.createElement("video");c.muted=!0,c.autoplay=!0,c.playsInline=!0,A=!1,c.onloadedmetadata=function(){A=!0},S(e,c),c.play()}var l,u,h=null,p=this}function j(e,t){t=t||"multi-streams-mixer";var i=[],o=!1,r=document.createElement("canvas"),s=r.getContext("2d");r.style.opacity=0,r.style.position="absolute",r.style.zIndex=-1,r.style.top="-1000em",r.style.left="-1000em",r.className=t,(document.body||document.documentElement).appendChild(r),this.disableLogs=!1,this.frameInterval=10,this.width=360,this.height=240,this.useGainNode=!0;var a=this,n=window.AudioContext;void 0===n&&("undefined"!=typeof webkitAudioContext&&(n=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(n=mozAudioContext));var A=window.URL;void 0===A&&"undefined"!=typeof webkitURL&&(A=webkitURL),"undefined"!=typeof navigator&&void 0===navigator.getUserMedia&&(void 0!==navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),void 0!==navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var d=window.MediaStream;void 0===d&&"undefined"!=typeof webkitMediaStream&&(d=webkitMediaStream),void 0!==d&&void 0===d.prototype.stop&&(d.prototype.stop=function(){this.getTracks().forEach((function(e){e.stop()}))});var c={};function l(){if(!o){var e=i.length,t=!1,s=[];if(i.forEach((function(e){e.stream||(e.stream={}),e.stream.fullcanvas?t=e:s.push(e)})),t)r.width=t.stream.width,r.height=t.stream.height;else if(s.length){r.width=e>1?2*s[0].width:s[0].width;var n=1;3!==e&&4!==e||(n=2),5!==e&&6!==e||(n=3),7!==e&&8!==e||(n=4),9!==e&&10!==e||(n=5),r.height=s[0].height*n}else r.width=a.width||360,r.height=a.height||240;t&&t instanceof HTMLVideoElement&&u(t),s.forEach((function(e,t){u(e,t)})),setTimeout(l,a.frameInterval)}}function u(e,t){if(!o){var i=0,r=0,a=e.width,n=e.height;1===t&&(i=e.width),2===t&&(r=e.height),3===t&&(i=e.width,r=e.height),4===t&&(r=2*e.height),5===t&&(i=e.width,r=2*e.height),6===t&&(r=3*e.height),7===t&&(i=e.width,r=3*e.height),void 0!==e.stream.left&&(i=e.stream.left),void 0!==e.stream.top&&(r=e.stream.top),void 0!==e.stream.width&&(a=e.stream.width),void 0!==e.stream.height&&(n=e.stream.height),s.drawImage(e,i,r,a,n),"function"==typeof e.stream.onRender&&e.stream.onRender(s,i,r,a,n,t)}}function h(e){var i=document.createElement("video");return function(e,t){"srcObject"in t?t.srcObject=e:"mozSrcObject"in t?t.mozSrcObject=e:t.srcObject=e}(e,i),i.className=t,i.muted=!0,i.volume=0,i.width=e.width||a.width||360,i.height=e.height||a.height||240,i.play(),i}function p(t){i=[],(t=t||e).forEach((function(e){if(e.getTracks().filter((function(e){return"video"===e.kind})).length){var t=h(e);t.stream=e,i.push(t)}}))}void 0!==n?c.AudioContext=n:"undefined"!=typeof webkitAudioContext&&(c.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){l()},this.appendStreams=function(t){if(!t)throw"First parameter is required.";t instanceof Array||(t=[t]),t.forEach((function(t){var o=new d;if(t.getTracks().filter((function(e){return"video"===e.kind})).length){var r=h(t);r.stream=t,i.push(r),o.addTrack(t.getTracks().filter((function(e){return"video"===e.kind}))[0])}if(t.getTracks().filter((function(e){return"audio"===e.kind})).length){var s=a.audioContext.createMediaStreamSource(t);a.audioDestination=a.audioContext.createMediaStreamDestination(),s.connect(a.audioDestination),o.addTrack(a.audioDestination.stream.getTracks().filter((function(e){return"audio"===e.kind}))[0])}e.push(o)}))},this.releaseStreams=function(){i=[],o=!0,a.gainNode&&(a.gainNode.disconnect(),a.gainNode=null),a.audioSources.length&&(a.audioSources.forEach((function(e){e.disconnect()})),a.audioSources=[]),a.audioDestination&&(a.audioDestination.disconnect(),a.audioDestination=null),a.audioContext&&a.audioContext.close(),a.audioContext=null,s.clearRect(0,0,r.width,r.height),r.stream&&(r.stream.stop(),r.stream=null)},this.resetVideoStreams=function(e){!e||e instanceof Array||(e=[e]),p(e)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=function(){o=!1;var t=function(){var e;p(),"captureStream"in r?e=r.captureStream():"mozCaptureStream"in r?e=r.mozCaptureStream():a.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var t=new d;return e.getTracks().filter((function(e){return"video"===e.kind})).forEach((function(e){t.addTrack(e)})),r.stream=t,t}(),i=function(){c.AudioContextConstructor||(c.AudioContextConstructor=new c.AudioContext);a.audioContext=c.AudioContextConstructor,a.audioSources=[],!0===a.useGainNode&&(a.gainNode=a.audioContext.createGain(),a.gainNode.connect(a.audioContext.destination),a.gainNode.gain.value=0);var t=0;if(e.forEach((function(e){if(e.getTracks().filter((function(e){return"audio"===e.kind})).length){t++;var i=a.audioContext.createMediaStreamSource(e);!0===a.useGainNode&&i.connect(a.gainNode),a.audioSources.push(i)}})),!t)return;return a.audioDestination=a.audioContext.createMediaStreamDestination(),a.audioSources.forEach((function(e){e.connect(a.audioDestination)})),a.audioDestination.stream}();return i&&i.getTracks().filter((function(e){return"audio"===e.kind})).forEach((function(e){t.addTrack(e)})),e.forEach((function(e){e.fullcanvas})),t}}function L(e,t){e=e||[];var i,o,r=this;(t=t||{elementClass:"multi-streams-mixer",mimeType:"video/webm",video:{width:360,height:240}}).frameInterval||(t.frameInterval=10),t.video||(t.video={}),t.video.width||(t.video.width=360),t.video.height||(t.video.height=240),this.record=function(){var r;i=new j(e,t.elementClass||"multi-streams-mixer"),(r=[],e.forEach((function(e){w(e,"video").forEach((function(e){r.push(e)}))})),r).length&&(i.frameInterval=t.frameInterval||10,i.width=t.video.width||360,i.height=t.video.height||240,i.startDrawingFrames()),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()),(o=new C(i.getMixedStream(),t)).record()},this.stop=function(e){o&&o.stop((function(t){r.blob=t,e(t),r.clearRecordedData()}))},this.pause=function(){o&&o.pause()},this.resume=function(){o&&o.resume()},this.clearRecordedData=function(){o&&(o.clearRecordedData(),o=null),i&&(i.releaseStreams(),i=null)},this.addStreams=function(r){if(!r)throw"First parameter is required.";r instanceof Array||(r=[r]),e.concat(r),o&&i&&(i.appendStreams(r),t.previewStream&&"function"==typeof t.previewStream&&t.previewStream(i.getMixedStream()))},this.resetVideoStreams=function(e){i&&(!e||e instanceof Array||(e=[e]),i.resetVideoStreams(e))},this.getMixer=function(){return i},this.name="MultiStreamRecorder",this.toString=function(){return this.name}}function F(e,t){var i,o,r;function s(){return new ReadableStream({start:function(o){var r=document.createElement("canvas"),s=document.createElement("video"),a=!0;s.srcObject=e,s.muted=!0,s.height=t.height,s.width=t.width,s.volume=0,s.onplaying=function(){r.width=t.width,r.height=t.height;var e=r.getContext("2d"),n=1e3/t.frameRate,A=setInterval((function(){if(i&&(clearInterval(A),o.close()),a&&(a=!1,t.onVideoProcessStarted&&t.onVideoProcessStarted()),e.drawImage(s,0,0),"closed"!==o._controlledReadableStream.state)try{o.enqueue(e.getImageData(0,0,t.width,t.height))}catch(e){}}),n)},s.play()}})}function a(e,A){if(!t.workerPath&&!A)return i=!1,void fetch("https://unpkg.com/webm-wasm@latest/dist/webm-worker.js").then((function(t){t.arrayBuffer().then((function(t){a(e,t)}))}));if(!t.workerPath&&A instanceof ArrayBuffer){var d=new Blob([A],{type:"text/javascript"});t.workerPath=l.createObjectURL(d)}t.workerPath||console.error("workerPath parameter is missing."),(o=new Worker(t.workerPath)).postMessage(t.webAssemblyPath||"https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm"),o.addEventListener("message",(function(e){"READY"===e.data?(o.postMessage({width:t.width,height:t.height,bitrate:t.bitrate||1200,timebaseDen:t.frameRate||30,realtime:t.realtime}),s().pipeTo(new WritableStream({write:function(e){i?console.error("Got image, but recorder is finished!"):o.postMessage(e.data.buffer,[e.data.buffer])}}))):e.data&&(r||n.push(e.data))}))}"undefined"!=typeof ReadableStream&&"undefined"!=typeof WritableStream||console.error("Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js"),(t=t||{}).width=t.width||640,t.height=t.height||480,t.frameRate=t.frameRate||30,t.bitrate=t.bitrate||1200,t.realtime=t.realtime||!0,this.record=function(){n=[],r=!1,this.blob=null,a(e),"function"==typeof t.initCallback&&t.initCallback()},this.pause=function(){r=!0},this.resume=function(){r=!1};var n=[];this.stop=function(e){i=!0;var t=this;!function(e){o?(o.addEventListener("message",(function(t){null===t.data&&(o.terminate(),o=null,e&&e())})),o.postMessage(null)):e&&e()}((function(){t.blob=new Blob(n,{type:"video/webm"}),e(t.blob)}))},this.name="WebAssemblyRecorder",this.toString=function(){return this.name},this.clearRecordedData=function(){n=[],r=!1,this.blob=null},this.blob=null}i.DiskStorage=x,i.GifRecorder=D,i.MultiStreamRecorder=L,i.RecordRTCPromisesHandler=function(e,t){if(!this)throw'Use "new RecordRTCPromisesHandler()"';if(void 0===e)throw'First argument "MediaStream" is required.';var o=this;o.recordRTC=new i(e,t),this.startRecording=function(){return new Promise((function(e,t){try{o.recordRTC.startRecording(),e()}catch(e){t(e)}}))},this.stopRecording=function(){return new Promise((function(e,t){try{o.recordRTC.stopRecording((function(i){o.blob=o.recordRTC.getBlob(),o.blob&&o.blob.size?e(i):t("Empty blob.",o.blob)}))}catch(e){t(e)}}))},this.pauseRecording=function(){return new Promise((function(e,t){try{o.recordRTC.pauseRecording(),e()}catch(e){t(e)}}))},this.resumeRecording=function(){return new Promise((function(e,t){try{o.recordRTC.resumeRecording(),e()}catch(e){t(e)}}))},this.getDataURL=function(e){return new Promise((function(e,t){try{o.recordRTC.getDataURL((function(t){e(t)}))}catch(e){t(e)}}))},this.getBlob=function(){return new Promise((function(e,t){try{e(o.recordRTC.getBlob())}catch(e){t(e)}}))},this.getInternalRecorder=function(){return new Promise((function(e,t){try{e(o.recordRTC.getInternalRecorder())}catch(e){t(e)}}))},this.reset=function(){return new Promise((function(e,t){try{e(o.recordRTC.reset())}catch(e){t(e)}}))},this.destroy=function(){return new Promise((function(e,t){try{e(o.recordRTC.destroy())}catch(e){t(e)}}))},this.getState=function(){return new Promise((function(e,t){try{e(o.recordRTC.getState())}catch(e){t(e)}}))},this.blob=null,this.version="5.6.2"},i.WebAssemblyRecorder=F}));class Ue extends we{constructor(e){super(),this.player=e,this.fileName="",this.fileType=N,this.isRecording=!1,this.recordingTimestamp=0,this.recordingInterval=null,e.debug.log("Recorder","init")}destroy(){this._reset(),this.player.debug.log("Recorder","destroy")}setFileName(e,t){this.fileName=e,P!==t&&N!==t||(this.fileType=t)}get recording(){return this.isRecording}get recordTime(){return this.recordingTimestamp}startRecord(){const e=this.player.debug,t={type:"video",mimeType:"video/webm;codecs=h264",onTimeStamp:t=>{e.log("Recorder","record timestamp :"+t)},disableLogs:!this.player._opt.debug};try{const e=this.player.video.$videoElement.captureStream(25);if(this.player.audio&&this.player.audio.mediaStreamAudioDestinationNode&&this.player.audio.mediaStreamAudioDestinationNode.stream&&!this.player.audio.isStateSuspended()&&this.player.audio.hasAudio&&this.player._opt.hasAudio){const t=this.player.audio.mediaStreamAudioDestinationNode.stream;if(t.getAudioTracks().length>0){const i=t.getAudioTracks()[0];i&&i.enabled&&e.addTrack(i)}}this.recorder=Me(e,t)}catch(t){e.error("Recorder","startRecord error",t),this.emit(T.recordCreateError)}this.recorder&&(this.isRecording=!0,this.player.emit(T.recording,!0),this.recorder.startRecording(),e.log("Recorder","start recording"),this.player.emit(T.recordStart),this.recordingInterval=window.setInterval((()=>{this.recordingTimestamp+=1,this.player.emit(T.recordingTimestamp,this.recordingTimestamp)}),1e3))}stopRecordAndSave(){this.recorder&&this.isRecording&&this.recorder.stopRecording((()=>{this.player.debug.log("Recorder","stop recording"),this.player.emit(T.recordEnd);const e=(this.fileName||le())+"."+(this.fileType||N);Te(this.recorder.getBlob(),e),this._reset(),this.player.emit(T.recording,!1)}))}_reset(){this.isRecording=!1,this.recordingTimestamp=0,this.recorder&&(this.recorder.destroy(),this.recorder=null),this.fileName=null,this.recordingInterval&&clearInterval(this.recordingInterval),this.recordingInterval=null}}class Qe{constructor(e){return new(Qe.getLoaderFactory())(e)}static getLoaderFactory(){return Ue}}class We{constructor(e){this.player=e,this.decoderWorker=new Worker(e._opt.decoder),this._initDecoderWorker(),e.debug.log("decoderWorker","init")}destroy(){this.decoderWorker.postMessage({cmd:R}),this.decoderWorker.terminate(),this.decoderWorker=null,this.player.debug.log("decoderWorker","destroy")}_initDecoderWorker(){const{debug:e,events:{proxy:t}}=this.player;this.decoderWorker.onmessage=t=>{const i=t.data;switch(i.cmd){case c:e.log("decoderWorker","onmessage:",c),this.player.loaded||this.player.emit(T.load),this.player.emit(T.decoderWorkerInit),this._initWork();break;case g:e.log("decoderWorker","onmessage:",g,i.code),this.player._times.decodeStart||(this.player._times.decodeStart=le()),this.player.video.updateVideoInfo({encTypeCode:i.code});break;case m:e.log("decoderWorker","onmessage:",m,i.code),this.player.audio&&this.player.audio.updateAudioInfo({encTypeCode:i.code});break;case l:if(e.log("decoderWorker","onmessage:",l,`width:${i.w},height:${i.h}`),this.player.video.updateVideoInfo({width:i.w,height:i.h}),!this.player._opt.openWebglAlignment&&i.w/2%4!=0)return void this.player.emit(x.webglAlignmentError);this.player.video.initCanvasViewSize();break;case p:e.log("decoderWorker","onmessage:",p,`channels:${i.channels},sampleRate:${i.sampleRate}`),this.player.audio&&(this.player.audio.updateAudioInfo(i),this.player.audio.initScriptNode(i));break;case u:this.player.handleRender(),this.player.video.render(i),this.player.emit(T.timeUpdate,i.ts),this.player.updateStats({fps:!0,ts:i.ts,buf:i.delay}),this.player._times.videoStart||(this.player._times.videoStart=le(),this.player.handlePlayToRenderTimes());break;case h:this.player.playing&&this.player.audio&&this.player.audio.play(i.buffer,i.ts);break;case f:i.message&&-1!==i.message.indexOf(b)&&(this.player.emit(T.error,x.wasmDecodeError),this.player.emit(x.wasmDecodeError));break;default:this.player[i.cmd]&&this.player[i.cmd](i)}}}_initWork(){const e={debug:this.player._opt.debug,useOffscreen:this.player._opt.useOffscreen,useWCS:this.player._opt.useWCS,videoBuffer:this.player._opt.videoBuffer,videoBufferDelay:this.player._opt.videoBufferDelay,openWebglAlignment:this.player._opt.openWebglAlignment};this.decoderWorker.postMessage({cmd:E,opt:JSON.stringify(e),sampleRate:this.player.audio&&this.player.audio.audioContext.sampleRate||0})}decodeVideo(e,t,i){const o={type:v,ts:Math.max(t,0),isIFrame:i};this.decoderWorker.postMessage({cmd:B,buffer:e,options:o},[e.buffer])}decodeAudio(e,t){this.player._opt.useWCS&&!this.player._opt.useOffscreen||this.player._opt.useMSE?this._decodeAudioNoDelay(e,t):this._decodeAudio(e,t)}_decodeAudio(e,t){const i={type:y,ts:Math.max(t,0)};this.decoderWorker.postMessage({cmd:B,buffer:e,options:i},[e.buffer])}_decodeAudioNoDelay(e,t){this.decoderWorker.postMessage({cmd:C,buffer:e,ts:Math.max(t,0)},[e.buffer])}updateWorkConfig(e){this.decoderWorker.postMessage({cmd:k,key:e.key,value:e.value})}}class Je extends we{constructor(e){super(),this.player=e,this.stopId=null,this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.initInterval()}destroy(){this.stopId&&(clearInterval(this.stopId),this.stopId=null),this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.bufferList=[],this.dropping=!1,this.off(),this.player.debug.log("CommonDemux","destroy")}getDelay(e){if(!e)return-1;if(this.firstTimestamp){if(e){const t=Date.now()-this.startTimestamp,i=e-this.firstTimestamp;this.delay=t>=i?t-i:i-t}}else this.firstTimestamp=e,this.startTimestamp=Date.now(),this.delay=-1;return this.delay}resetDelay(){this.firstTimestamp=null,this.startTimestamp=null,this.delay=-1,this.dropping=!1}initInterval(){this.player.debug.log("common dumex","init Interval");let e=()=>{let e;const t=this.player._opt.videoBuffer,i=this.player._opt.videoBufferDelay;if(this.bufferList.length)if(this.dropping){for(e=this.bufferList.shift(),e.type===y&&0===e.payload[1]&&this._doDecoderDecode(e);!e.isIFrame&&this.bufferList.length;)e=this.bufferList.shift(),e.type===y&&0===e.payload[1]&&this._doDecoderDecode(e);e.isIFrame&&this.getDelay(e.ts)<=Math.min(t,200)&&(this.dropping=!1,this._doDecoderDecode(e))}else e=this.bufferList[0],-1===this.getDelay(e.ts)?(this.bufferList.shift(),this._doDecoderDecode(e)):this.delay>t+i?(this.resetDelay(),this.dropping=!0):(e=this.bufferList[0],this.getDelay(e.ts)>t&&(this.bufferList.shift(),this._doDecoderDecode(e)))};e(),this.stopId=setInterval(e,10)}_doDecode(e,t,i,o,r){const s=this.player;let a={ts:i,cts:r,type:t,isIFrame:!1};s._opt.useWCS&&!s._opt.useOffscreen||s._opt.useMSE?(t===v&&(a.isIFrame=o),this.pushBuffer(e,a)):t===v?s.decoderWorker&&s.decoderWorker.decodeVideo(e,i,o):t===y&&s._opt.hasAudio&&s.decoderWorker&&s.decoderWorker.decodeAudio(e,i)}_doDecoderDecode(e){const t=this.player,{webcodecsDecoder:i,mseDecoder:o}=t;e.type===y?t._opt.hasAudio&&t.decoderWorker&&t.decoderWorker.decodeAudio(e.payload,e.ts):e.type===v&&(t._opt.useWCS&&!t._opt.useOffscreen?i.decodeVideo(e.payload,e.ts,e.isIFrame):t._opt.useMSE&&o.decodeVideo(e.payload,e.ts,e.isIFrame,e.cts))}pushBuffer(e,t){t.type===y?this.bufferList.push({ts:t.ts,payload:e,type:y}):t.type===v&&this.bufferList.push({ts:t.ts,cts:t.cts,payload:e,type:v,isIFrame:t.isIFrame})}close(){}}class Ge extends Je{constructor(e){super(e),this.input=this._inputFlv(),this.flvDemux=this.dispatchFlvData(this.input),e.debug.log("FlvDemux","init")}destroy(){super.destroy(),this.input=null,this.flvDemux=null,this.player.debug.log("FlvDemux","destroy")}dispatch(e){this.flvDemux(e)}*_inputFlv(){yield 9;const e=new ArrayBuffer(4),t=new Uint8Array(e),i=new Uint32Array(e),o=this.player;for(;;){t[3]=0;const e=yield 15,r=e[4];t[0]=e[7],t[1]=e[6],t[2]=e[5];const s=i[0];t[0]=e[10],t[1]=e[9],t[2]=e[8];let a=i[0];16777215===a&&(t[3]=e[11],a=i[0]);const n=yield s;switch(r){case w:o._opt.hasAudio&&(o.updateStats({abps:n.byteLength}),n.byteLength>0&&this._doDecode(n,y,a));break;case S:if(o._times.demuxStart||(o._times.demuxStart=le()),o._opt.hasVideo){o.updateStats({vbps:n.byteLength});const e=n[0]>>4==1;if(n.byteLength>0){i[0]=n[4],i[1]=n[3],i[2]=n[2],i[3]=0;let t=i[0];this._doDecode(n,v,a,e,t)}}}}}dispatchFlvData(e){let t=e.next(),i=null;return o=>{let r=new Uint8Array(o);if(i){let e=new Uint8Array(i.length+r.length);e.set(i),e.set(r,i.length),r=e,i=null}for(;r.length>=t.value;){let i=r.slice(t.value);t=e.next(r.slice(0,t.value)),r=i}r.length>0&&(i=r)}}close(){this.input&&this.input.return(null)}}class Pe extends Je{constructor(e){super(e),e.debug.log("M7sDemux","init")}destroy(){super.destroy(),this.player.debug.log("M7sDemux","destroy"),this.player=null}dispatch(e){const t=this.player,i=new DataView(e),o=i.getUint8(0),r=i.getUint32(1,!1);switch(o){case y:if(t._opt.hasAudio){const i=new Uint8Array(e,5);t.updateStats({abps:i.byteLength}),i.byteLength>0&&this._doDecode(i,o,r)}break;case v:if(t._opt.hasVideo)if(t._times.demuxStart||(t._times.demuxStart=le()),i.byteLength>5){const s=new Uint8Array(e,5),a=i.getUint8(5)>>4==1;t.updateStats({vbps:s.byteLength}),s.byteLength>0&&this._doDecode(s,o,r,a)}else this.player.debug.warn("M7sDemux","dispatch","dv byteLength is",i.byteLength)}}}class Ne{constructor(e){return new(Ne.getLoaderFactory(e._opt.demuxType))(e)}static getLoaderFactory(e){return e===A?Pe:e===n?Ge:void 0}}class ze{constructor(e){this.TAG="ExpGolomb",this._buffer=e,this._buffer_index=0,this._total_bytes=e.byteLength,this._total_bits=8*e.byteLength,this._current_word=0,this._current_word_bits_left=0}destroy(){this._buffer=null}_fillCurrentWord(){let e=this._total_bytes-this._buffer_index,t=Math.min(4,e),i=new Uint8Array(4);i.set(this._buffer.subarray(this._buffer_index,this._buffer_index+t)),this._current_word=new DataView(i.buffer).getUint32(0,!1),this._buffer_index+=t,this._current_word_bits_left=8*t}readBits(e){if(e<=this._current_word_bits_left){let t=this._current_word>>>32-e;return this._current_word<<=e,this._current_word_bits_left-=e,t}let t=this._current_word_bits_left?this._current_word:0;t>>>=32-this._current_word_bits_left;let i=e-this._current_word_bits_left;this._fillCurrentWord();let o=Math.min(i,this._current_word_bits_left),r=this._current_word>>>32-o;return this._current_word<<=o,this._current_word_bits_left-=o,t=t<<o|r,t}readBool(){return 1===this.readBits(1)}readByte(){return this.readBits(8)}_skipLeadingZero(){let e;for(e=0;e<this._current_word_bits_left;e++)if(0!=(this._current_word&2147483648>>>e))return this._current_word<<=e,this._current_word_bits_left-=e,e;return this._fillCurrentWord(),e+this._skipLeadingZero()}readUEG(){let e=this._skipLeadingZero();return this.readBits(e+1)-1}readSEG(){let e=this.readUEG();return 1&e?e+1>>>1:-1*(e>>>1)}}class He{static _ebsp2rbsp(e){let t=e,i=t.byteLength,o=new Uint8Array(i),r=0;for(let e=0;e<i;e++)e>=2&&3===t[e]&&0===t[e-1]&&0===t[e-2]||(o[r]=t[e],r++);return new Uint8Array(o.buffer,0,r)}static parseSPS(e){let t=He._ebsp2rbsp(e),i=new ze(t);i.readByte();let o=i.readByte();i.readByte();let r=i.readByte();i.readUEG();let s=He.getProfileString(o),a=He.getLevelString(r),n=1,A=420,d=[0,420,422,444],c=8;if((100===o||110===o||122===o||244===o||44===o||83===o||86===o||118===o||128===o||138===o||144===o)&&(n=i.readUEG(),3===n&&i.readBits(1),n<=3&&(A=d[n]),c=i.readUEG()+8,i.readUEG(),i.readBits(1),i.readBool())){let e=3!==n?8:12;for(let t=0;t<e;t++)i.readBool()&&(t<6?He._skipScalingList(i,16):He._skipScalingList(i,64))}i.readUEG();let l=i.readUEG();if(0===l)i.readUEG();else if(1===l){i.readBits(1),i.readSEG(),i.readSEG();let e=i.readUEG();for(let t=0;t<e;t++)i.readSEG()}let u=i.readUEG();i.readBits(1);let h=i.readUEG(),p=i.readUEG(),m=i.readBits(1);0===m&&i.readBits(1),i.readBits(1);let g=0,f=0,b=0,y=0;i.readBool()&&(g=i.readUEG(),f=i.readUEG(),b=i.readUEG(),y=i.readUEG());let v=1,w=1,S=0,E=!0,B=0,C=0;if(i.readBool()){if(i.readBool()){let e=i.readByte(),t=[1,12,10,16,40,24,20,32,80,18,15,64,160,4,3,2],o=[1,11,11,11,33,11,11,11,33,11,11,33,99,3,2,1];e>0&&e<16?(v=t[e-1],w=o[e-1]):255===e&&(v=i.readByte()<<8|i.readByte(),w=i.readByte()<<8|i.readByte())}if(i.readBool()&&i.readBool(),i.readBool()&&(i.readBits(4),i.readBool()&&i.readBits(24)),i.readBool()&&(i.readUEG(),i.readUEG()),i.readBool()){let e=i.readBits(32),t=i.readBits(32);E=i.readBool(),B=t,C=2*e,S=B/C}}let R=1;1===v&&1===w||(R=v/w);let k=0,T=0;if(0===n)k=1,T=2-m;else{k=3===n?1:2,T=(1===n?2:1)*(2-m)}let I=16*(h+1),x=16*(p+1)*(2-m);I-=(g+f)*k,x-=(b+y)*T;let D=Math.ceil(I*R);return i.destroy(),i=null,{profile_string:s,level_string:a,bit_depth:c,ref_frames:u,chroma_format:A,chroma_format_string:He.getChromaFormatString(A),frame_rate:{fixed:E,fps:S,fps_den:C,fps_num:B},sar_ratio:{width:v,height:w},codec_size:{width:I,height:x},present_size:{width:D,height:x}}}static _skipScalingList(e,t){let i=8,o=8,r=0;for(let s=0;s<t;s++)0!==o&&(r=e.readSEG(),o=(i+r+256)%256),i=0===o?i:o}static getProfileString(e){switch(e){case 66:return"Baseline";case 77:return"Main";case 88:return"Extended";case 100:return"High";case 110:return"High10";case 122:return"High422";case 244:return"High444";default:return"Unknown"}}static getLevelString(e){return(e/10).toFixed(1)}static getChromaFormatString(e){switch(e){case 420:return"4:2:0";case 422:return"4:2:2";case 444:return"4:4:4";default:return"Unknown"}}}function Ye(e){const t={},i=new DataView(e.buffer);let o=i.getUint8(0),r=i.getUint8(1);if(i.getUint8(2),i.getUint8(3),1!==o||0===r)return;const s=1+(3&i.getUint8(4));if(3!==s&&4!==s)return;let a=31&i.getUint8(5);if(0===a)return;let n=6;for(let o=0;o<a;o++){let r=i.getUint16(n,!1);if(n+=2,0===r)continue;let s=new Uint8Array(e.buffer,n,r);n+=r;let a=He.parseSPS(s);if(0!==o)continue;t.codecWidth=a.codec_size.width,t.codecHeight=a.codec_size.height,t.presentWidth=a.present_size.width,t.presentHeight=a.present_size.height,t.profile=a.profile_string,t.level=a.level_string,t.bitDepth=a.bit_depth,t.chromaFormat=a.chroma_format,t.sarRatio=a.sar_ratio,t.frameRate=a.frame_rate,!1!==a.frame_rate.fixed&&0!==a.frame_rate.fps_num&&0!==a.frame_rate.fps_den||(t.frameRate={});let A=t.frameRate.fps_den,d=t.frameRate.fps_num;t.refSampleDuration=t.timescale*(A/d);let c=s.subarray(1,4),l="avc1.";for(let e=0;e<3;e++){let t=c[e].toString(16);t.length<2&&(t="0"+t),l+=t}t.codec=l}let A=i.getUint8(n);if(0!==A){n++;for(let t=0;t<A;t++){let t=i.getUint16(n,!1);n+=2,0!==t&&(new Uint8Array(e.buffer,n,t),n+=t)}return t.videoType="avc",t}}class Xe extends we{constructor(e){super(),this.player=e,this.hasInit=!1,this.isDecodeFirstIIframe=!1,this.isInitInfo=!1,this.decoder=null,this.initDecoder(),e.debug.log("Webcodecs","init")}destroy(){this.decoder&&(this.decoder.close(),this.decoder=null),this.hasInit=!1,this.isInitInfo=!1,this.isDecodeFirstIIframe=!1,this.off(),this.player.debug.log("Webcodecs","destroy")}initDecoder(){const e=this;this.decoder=new VideoDecoder({output(t){e.handleDecode(t)},error(t){e.handleError(t)}})}handleDecode(e){this.isInitInfo||(this.player.video.updateVideoInfo({width:e.codedWidth,height:e.codedHeight}),this.player.video.initCanvasViewSize(),this.isInitInfo=!0),this.player._times.videoStart||(this.player._times.videoStart=le(),this.player.handlePlayToRenderTimes()),this.player.handleRender(),this.player.video.render({videoFrame:e}),this.player.updateStats({fps:!0,ts:0,buf:this.player.demux.delay}),setTimeout((function(){e.close?e.close():e.destroy()}),100)}handleError(e){this.player.debug.error("Webcodecs","VideoDecoder handleError",e)}decodeVideo(e,t,i){if(this.hasInit){if(i&&0===e[1]){const t=Ye(e.slice(5)),i=this.player.video.videoInfo;if(t.codecWidth!==i.width||t.codecHeight!==i.height)return this.player.debug.log("Webcodecs",`width or height is update, width ${i.width}-> ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),void this.player.emit(x.webcodecsWidthOrHeightChange)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){const o=new EncodedVideoChunk({data:e.slice(5),timestamp:t,type:i?X:q});this.player.emit(T.timeUpdate,t);try{this.decoder.decode(o)}catch(e){this.player.debug.error("Webcodecs","VideoDecoder",e),-1!==e.toString().indexOf(re)&&this.player.emit(x.webcodecsDecodeError)}}else this.player.debug.warn("Webcodecs","VideoDecoder isDecodeFirstIIframe false")}else if(i&&0===e[1]){const t=15&e[0];if(this.player.video.updateVideoInfo({encTypeCode:t}),t===M)return void this.emit(x.webcodecsH265NotSupport);this.player._times.decodeStart||(this.player._times.decodeStart=le());const i=function(e){let t=e.subarray(1,4),i="avc1.";for(let e=0;e<3;e++){let o=t[e].toString(16);o.length<2&&(o="0"+o),i+=o}return{codec:i,description:e}}(e.slice(5));this.decoder.configure(i),this.hasInit=!0}}}const qe={play:"播放",pause:"暂停",audio:"",mute:"",screenshot:"截图",loading:"加载",fullscreen:"全屏",fullscreenExit:"退出全屏",record:"录制",recordStop:"停止录制"};var Ze=Object.keys(qe).reduce(((e,t)=>(e[t]=`\n <i class="jessibuca-icon jessibuca-icon-${t}"></i>\n ${qe[t]?`<span class="icon-title-tips"><span class="icon-title">${qe[t]}</span></span>`:""}\n`,e)),{}),Ke=(e,t)=>{const{events:{proxy:i}}=e,o=document.createElement("object");o.setAttribute("aria-hidden","true"),o.setAttribute("tabindex",-1),o.type="text/html",o.data="about:blank",he(o,{display:"block",position:"absolute",top:"0",left:"0",height:"100%",width:"100%",overflow:"hidden",pointerEvents:"none",zIndex:"-1"});let r=e.width,s=e.height;i(o,"load",(()=>{i(o.contentDocument.defaultView,"resize",(()=>{e.width===r&&e.height===s||(r=e.width,s=e.height,e.emit(T.resize),n())}))})),e.$container.appendChild(o),e.on(T.destroy,(()=>{e.$container.removeChild(o)})),e.on(T.volumechange,(()=>{!function(e){if(0===e)he(t.$volumeOn,"display","none"),he(t.$volumeOff,"display","flex"),he(t.$volumeHandle,"top","48px");else if(t.$volumeHandle&&t.$volumePanel){const i=pe(t.$volumePanel,"height")||60,o=pe(t.$volumeHandle,"height"),r=i-(i-o)*e-o;he(t.$volumeHandle,"top",`${r}px`),he(t.$volumeOn,"display","flex"),he(t.$volumeOff,"display","none")}t.$volumePanelText&&(t.$volumePanelText.innerHTML=parseInt(100*e))}(e.volume)})),e.on(T.loading,(e=>{he(t.$loading,"display",e?"flex":"none"),he(t.$poster,"display","none"),e&&he(t.$playBig,"display","none")}));const a=i=>{let o=!0===(r=i)||!1===r?i:e.fullscreen;var r;he(t.$fullscreenExit,"display",o?"flex":"none"),he(t.$fullscreen,"display",o?"none":"flex")},n=()=>{fe()&&t.$controls&&setTimeout((()=>{if(e.fullscreen){let i=e.height/2-e.width+19,o=e.height/2-19;t.$controls.style.transform=`translateX(${-i}px) translateY(-${o}px) rotate(-90deg)`}else t.$controls.style.transform="translateX(0) translateY(0) rotate(0)"}),10)};try{Ae.on("change",a),e.events.destroys.push((()=>{Ae.off("change",a)}))}catch(e){}e.on(T.webFullscreen,(e=>{a(e),n()})),e.on(T.recording,(()=>{he(t.$record,"display",e.recording?"none":"flex"),he(t.$recordStop,"display",e.recording?"flex":"none"),he(t.$recording,"display",e.recording?"flex":"none")})),e.on(T.recordingTimestamp,(e=>{t.$recordingTime&&(t.$recordingTime.innerHTML=function(e){var t;if(e>-1){var i=Math.floor(e/3600),o=Math.floor(e/60)%60,r=e%60;t=i<10?"0"+i+":":i+":",o<10&&(t+="0"),t+=o+":",(r=Math.round(r))<10&&(t+="0"),t+=r.toFixed(0)}return t}(e))})),e.on(T.playing,(e=>{he(t.$play,"display",e?"none":"flex"),he(t.$playBig,"display",e?"none":"block"),he(t.$pause,"display",e?"flex":"none"),he(t.$screenshot,"display",e?"flex":"none"),he(t.$record,"display",e?"flex":"none"),he(t.$fullscreen,"display",e?"flex":"none"),e||t.$speed&&(t.$speed.innerHTML=be(""))})),e.on(T.kBps,(e=>{const i=be(e);t.$speed&&(t.$speed.innerHTML=i)}))};function _e(e,t){void 0===t&&(t={});var i=t.insertAt;if(e&&"undefined"!=typeof document){var o=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css","top"===i&&o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}}_e('@keyframes rotation{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes magentaPulse{0%{background-color:#630030;-webkit-box-shadow:0 0 9px #333}50%{background-color:#a9014b;-webkit-box-shadow:0 0 18px #a9014b}to{background-color:#630030;-webkit-box-shadow:0 0 9px #333}}.jessibuca-container .jessibuca-icon{cursor:pointer;width:16px;height:16px}.jessibuca-container .jessibuca-poster{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:100%;background-position:50%;background-repeat:no-repeat;background-size:contain;pointer-events:none}.jessibuca-container .jessibuca-play-big{position:absolute;display:none;height:100%;width:100%;background:rgba(0,0,0,.4)}.jessibuca-container .jessibuca-play-big:after{cursor:pointer;content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;width:48px;height:48px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-position:50%}.jessibuca-container .jessibuca-play-big:hover:after{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC")}.jessibuca-container .jessibuca-recording{display:none;position:absolute;left:50%;top:0;padding:0 3px;transform:translateX(-50%);justify-content:space-around;align-items:center;width:95px;height:20px;background:#000;opacity:1;border-radius:0 0 8px 8px;z-index:1}.jessibuca-container .jessibuca-recording .jessibuca-recording-red-point{width:8px;height:8px;background:#ff1f1f;border-radius:50%;animation:magentaPulse 1s linear infinite}.jessibuca-container .jessibuca-recording .jessibuca-recording-time{font-size:14px;font-weight:500;color:#ddd}.jessibuca-container .jessibuca-recording .jessibuca-icon-recordStop{width:16px;height:16px;cursor:pointer}.jessibuca-container .jessibuca-loading{display:none;flex-direction:column;justify-content:center;align-items:center;position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.jessibuca-container .jessibuca-loading-text{line-height:20px;font-size:13px;color:#fff;margin-top:10px}.jessibuca-container .jessibuca-controls{background-color:#161616;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;position:absolute;z-index:40;left:0;right:0;bottom:0;height:38px;width:100%;padding-left:13px;padding-right:13px;font-size:14px;color:#fff;opacity:0;visibility:hidden;transition:all .2s ease-in-out;-webkit-user-select:none;user-select:none;transition:width .5s ease-in}.jessibuca-container .jessibuca-controls .jessibuca-controls-item{position:relative;display:flex;justify-content:center;padding:0 8px}.jessibuca-container .jessibuca-controls .jessibuca-controls-item:hover .icon-title-tips{visibility:visible;opacity:1}.jessibuca-container .jessibuca-controls .jessibuca-fullscreen,.jessibuca-container .jessibuca-controls .jessibuca-fullscreen-exit,.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-microphone-close,.jessibuca-container .jessibuca-controls .jessibuca-pause,.jessibuca-container .jessibuca-controls .jessibuca-play,.jessibuca-container .jessibuca-controls .jessibuca-record,.jessibuca-container .jessibuca-controls .jessibuca-record-stop,.jessibuca-container .jessibuca-controls .jessibuca-screenshot{display:none}.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-icon-mute{z-index:1}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom{display:flex;justify-content:space-between;height:100%}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-left,.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-right{display:flex;align-items:center}.jessibuca-container.jessibuca-controls-show .jessibuca-controls{opacity:1;visibility:visible}.jessibuca-container.jessibuca-controls-show-auto-hide .jessibuca-controls{opacity:.8;visibility:visible;display:none}.jessibuca-container.jessibuca-hide-cursor *{cursor:none!important}.jessibuca-container .jessibuca-icon-loading{width:50px;height:50px;background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%;animation:rotation 1s linear infinite}.jessibuca-container .jessibuca-icon-screenshot{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE5UlEQVRoQ+1YW2sdVRT+1s7JxbsoVkEUrIIX0ouz15zYNA+N1RdtQfCltlUfvLbqL/BCwZ8grbHtizQqPojgBSr0JkiMmT2nxgapqBURtPVCq7HxJCeZJVPmxDlzZubMmXOSEsnAvOy917fXt9e39tp7E5b4R0vcfywTuNgRbBgBx3HuJqLVzPzmYjprjHkcwAlmLqXNm4XAISLaSESPaq2HF4OE67rbRGRYRA7btn1fbgLGmKsA/Azg0gBkGzO/vZAkHMd5hIiqc5wHcCMz/5k0Z2oExsfHV1QqldPAf8lORNu11m8tBAljzFYAYWxRSl1vWdZvuQj4RsYYF4AVBlgIOVVlE55HRIxt23ZuCfmGjuOsJ6LPoiAistW27XfaEYmIbOYhPc9bXywWR1oiEJDYQkR1zrYjEjGyqfqbKd8a7kJVtLgQ+30i8pht2wfyRKIdmJkJBPkQTbILfudJ7CTZNBvVpggEcgpvc/ML38zESbLJsxBNE/A9biX0rdjGyTQXgbxyapdsarb0PMlXtWnGoXbKpm0Essqp3bJpK4E0OXmed3+hUBDP8w5FI91M0rdcyLLILElOCbaZilSWeXMncRx4klTCY1spfG3dhZJWx3GcDUR0EEB3ZMw0ET2gtT6SZWWzjmlrBIJCl0hAKfWgZVmHszqXZVxbCSxpCS2JJA6umIhe8ZKKVLPbaBJ+S9toqVRa53nedgAbAKwIwH4FcAzAa0R0l4i8F7PPz189k6RFRA+LyNcAXojDV0oNW5b1eW4Cxpg9AHZkSaaa6hhzb065uDSCH2LmRB8Sk9gY4293g43Qo/1pV80m8yQMfZSZ781cB1zXHRKRZ2IMpgD8A+DamL4ZItqitX4/jbQx5iEA7wLoihn3V/ACckWMJN/QWj9b1x5tGBsbW6uUOh5pPy0iL3Z2dn6ilJqanp5ep5TaJSLhF4NppdRNaU8gPmapVLrO87yfIoXuWyJ6uVKp+HmFjo6OQSJ6FcBtYT+UUmstyxqvkWuUgDFmP4AnQu2/e563qlgs+u9DNZ8xZhRAX7VRRPbath0XuXk7Y8xeAE+FgL6fnJzsHRwcLIfBR0ZGLunq6poAsDLUvp+Zw7b1r9PGmJMAbg8Z7WDmoThZuK67WkS+DD18fcPMdzSQUBR/EzN/nIC/SUQ+DPXV4dclsTHmHAD/SfHCNzc3t7Kvr++HJKeMMacA3BL0nyuXyzcPDAxMxo0fHR29slAo/Ajg6qD/fE9Pzw29vb1/x42fmJi4vFwu+5G/LOg/y8zXNJLQ2dAES5JANMQ7mfn1jBI6ycx3NiMhItqstf4oAX+ziHwQ6qvDj5NQNIn/ALCKmX+JSeIvABRD7fuY+ekGBPYBeDI05tTMzExvf3+/vz2Hk91/ET8RSeI6/DoCpVJpjed5fmKGvzMAXpqdnT3oed5Ud3d3v4jsAqBr9Ei0Rmv9VRqBBPzvROQVETnq2xJRdRu9tRF+bCVOKWT+Kvl/TSIFk6SW/LAjKfjV5K8rZABi8dOOEv7FI7Z8x6zwEWbemLbyMfJr5qiSiJ96oclymBOR3bZtP9+M89WxxpjdAHY2sN3DzM8ljWl4I3Nd9x7/OE1ENcdpETnmH3e11n41zv0l4J8RkU+J6AAz+xtF4teQQG7PFslwmcAiLfSyhC72Qv9/I/Avns2OT7QJskoAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-screenshot:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAED0lEQVRoQ+2ZycsdRRTFf2ejqHFAMQqiYBTUoElUHLNx3GgCgpuYRF2o0UT9CxwQ/BMkMSbZSKLiQgQHUDCJgjiAxiEiESdEcJbEedgcKaj3UV+/6q7u/jovPPkK3qbr1ql76p5bt6qemPKmKfefeQKHOoLFCNg+H1gi6fFJOmv7VmCvpD1N87Yh8ApwNXCzpB2TIGF7DRDm2inpmt4EbB8LfAMcGUHWSHryYJKwfRMwmuMP4BRJv9TN2RgB2wuB72BWsq+V9MTBIGF7NZBiGzhJ0o+9CIRBtt8FLqgADC6nRDbpVO9Iuqi3hCKB5cDrGZDVkp4aIhIV2aSQyyW9MScCkcQqIOfsnCORkc3I31b5VtyFRmg1IQ7dt0ja3icSQ2C2JhAjUU2ykd+dE7tBNp2i2olAJJFuc+nCt564QTadF6IzgUhiVGiqyinKaQjZpJP2ItBXTkPJZhACXeU0pGwGI9BWTkPLZlACBTldG4o5EA6E1dY66edcyNrs8Q36zg1vVaTazNs7iXPgDVJJzYs7VRvHRzaDEohyugJ4CTi84sg/wHWSdnVxsGQ7aQLXS9pZcqpL/6AEplpCU5HE8YpJ9YrXUKQ6baN1+HPaRm1fBqwFQnKGK2ZoPwCvAo8Ai4FnMpPMHMwapHUj8DFwbw3+Dklv9iZgexOwvktSRduxU2VDlErwmyXV+lCbxLbDdndlCT3TX3vV7JgnKfRuSVflfMkSsL0ZuDMz4E/gL+CETN+/wCpJzzaRtn0D8DRwWMbu1/gCcnSm7zFJd1W/jxGwvQx4r2IYnlbuA14GAomQFw8B6YtBKFSnNj2BxEJ3IvB1pdB9CjwQ8yqYhcg/DJxZ8WOZpA/SbzkC24DbEqOfgPMkBRKzmu23gEuSj1sk5SI3Y2J7C3BHMuZz4FxJf6fgto8APgIWJd+3SUrHjr9O294HnJUMWi8pSGqs2V4CvJ88fH0i6eyChKr4KyS9WIO/Ang+6RvDz0XgABCeFEdtkaQv65yy/QVweuwPY0+T9FuNQ8cAXwHHxf7wdHiypN9r7BfEl8GjYv9+SceXJLQ/mSDYTh2Baog3SHq0pYT2STqno4RWSnqhBn8l8FzSN4bfJol/jkn8bXUS228DFyfft0paVyCwFbg9sQkSDEkctueZZju8iO+tJPEYfo7A0piYKd73wP3xnB+20cvjNnphxdmlkj4sEMjhfwY8COyOY0fb6Bkl/K6FLKxS+M1KpDhJY8mvrG5doRwlf66QZfGbjhLh4pEt35kV3iUp/IvTunU8qtTil/7gaHOY2yjpntaez9b5RmBDYewmSXfX2RRvZLYvbThOh+NuqMa9Ww1+yLnXgO2SwkZR24oEens2oYHzBCa00PMSOtQL/f+NwH+Hg8hAnbrYgQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABA0lEQVRoQ+1YwQqCUBAcfWXXsLr2AXWTPXno8yVB8AP6Aa3oHI+kCDqYaawJljSe133uzO44bx0M/HEG/v1gAd9mkAyQgY4I/F8LJUlyrQFtD2AtIkcNoFEU+Z7n7QD4DfFHEVlocrVmgAUAIAOl3mILPcDgEFcUhyrUKMGUUcroc3NQRimj9XJBGaWMvvPydKN0o6/9QTdKN6rZANxj6EbpRulGuZnjYqs8BbyR8Ub2Izeys+u6yyAIDpo/ehzHM2NMDsA0xFsRmWhyfTIDWSXxCEBmrd2EYXjSHJqm6bQoii2AOYBL5Z0xgFxEVppcrQvQJO0zhgX0iXbdWWSADHRE4AZQ731AhEUeNwAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA7klEQVRoQ+2YSwrCQBBEX6HiVvxsPYDewfN7By/gD9ciQkvERQwJdBSiYs0mEDo96aruombEjy/9+P/jAj7NoBkwA28i8H8tFBFRA9oeWEo6ZgCNiDGwAYpn3TpKmmVytWbABQBmoNRbbqEHGB7iiuJYhRol2DJqGX1uDsuoZdRmLuNZSzGWUcuoZdRHSp/IylNgK2ErYSthK3FHwLcSvpXIjoLt9Jfa6TMwl3TIMBkRE2AH9BriL5KGmVyvWIltJXEfKN6tJJ0ym0bECFgDU+Ba+WZQFCdpkcnVuoBM0i5jXECXaNftZQbMwJsI3AAPN3dAQflHegAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC+UlEQVRoQ+1ZS2sTURT+zlDJYE3XSq219QHVuEjnJDT+Bff9Abqw2voAEfGxqygUqWhVFHGl/yMLu9BwByxk5SNI66ML6U7axjhHbmhgWiftncxoOiV3FcI53z3f/e65594zhIQPSnj86BBot4IdBToKRFyBnbeFlFIScVEiuYvIWC6Xe2YK8pcC7SYA4CMzH4mDQBXAqilQBDsLQLfPf9FxnF4i8kwwmypARI+Wl5dvmIBEsUmlUkNE9NaHsVCpVAZGR0d/m+A2JSAid3K53E0TkCg2pVKpz7KseR/GfKVSGYxMAMA0M1+JEpyJb6lUOm5ZVnkrAsVisaunp+esiByr1Wp3R0ZGvmifzZK4XQQWHMc52MgBpdQuAOcAXABwuB400ZTjONdaIjA7O5u2bVsnWU1EujzP+5nP5xdMVjvIJkCBD8x8VCm1G8AYgAkAAxt8Z5j5YmgCSqlTAJ4D2OcD/AXgATNfbYVEAIFPIvKKiE4D6GuCea8xX6gtpJT6DmBvECgRFRzHeROWRAABE4iWCbwHEFhkPM/L5vP5dyaz+23+KwHXdR3P854S0YG1ILSCuthNMfNM2OC1/RYENLY+ygcBnPfht6ZAA6BYLNr6dyqVokKhsGpaNQ2TWJstreXaE2aed133sojcj41AKyvdzCdAgSXLsk4MDw9/a/i4rntbRPxFNZoC/5jAV2be759DKTUJ4FZSFFi0bbs/k8noy2R9dAjEuWU2YgXkQOK3kD6BMsysi2Z9JC2Jdcw/ALzwPO+xvmcl7Rj177JVEbkO4BARjSflFDJJuW1dBxJPoCIiL4noDIB1BS0pW6j+oJmbm+uuVqvjRKQfLr0bZHnIzJf0f6HeAybahrUJqAPruhLlcnnPysqKfpXp11n/Gv62zoHAroS+AafT6QkiGrIsazKbzX7eVIHEt1US39gCkOzWYthkjNE+tuZujDGZQ8XRXn8N4KT5lLFZ6uaYPt+nwyDuvC80YdhvB9uOAu1WoaNAR4GIK/AHvdr+QAexB7EAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACfUlEQVRoQ+2ZSYsUQRCFvycK4nJXXEbHBdwO4kn/gv9CD467ICIutxEFkREdFUU86T/xojcPntyQcT2INw+uISFVkD1Wd2dWlU7nUHlqisiX+fJFZGREi8yHMt8/HYG5VrBToFOg4QnMPxcyM2t4KE2nT0i6EwvylwIjQOCFpE1tEPgGfI0FamC3AFgazP8IrJL0KwZzkAI3gLMxIA1ttgCPA4w3wHpJP2NwBxG4KOlcDEgTGzNbA8wEGP57vA0CU5JONtlczFwz2wY8HUbAzBYCB4CtwCVJb33OIAXmioC70LoyBsxsEXAQOApsLIhelnS6FgEzW+5BBvwA/FS+SPJFa40KBZ5L2mxmS4AJ4IjHxCzwaUnHkgmY2V7gLrAyAPwOXJN0qg6DCgIvgQfAPsDjo2pcKddLciEz+wCs6AO6W9KjVBIVBGIgahN4BvRLMjslPYlZPbT53wR2AbeBtcUmXEFPdh5U06mbd/shBBzbr/Jx4FCAX0+BEsDMFocEYrNmFcE+BD4XsXZL0oyZnQCutkagzkn3m1NBwDe/Q9L74MAuFEqUn5op8I8JvJO0elacTALnc1HAH3Njkvwx+WeYWUegTa/pwaqIgexdyIN4uyRPmqULZRXEvulPwD3gpr+zcrtGQxfzRHYG2AAczuUWiom3kc4D2RN4BdwH9gM9CS0XFyoLGu9UuN974eIFVDiuSzruH5LqgRhtU20q8kBPV8LMlhVVmVdnYwX+SMdAZVeieAF7eeltmElJr4cpkH1bJfvGVvatxdR4bMu+teZuWxtKxWncXn8I7EldtQV7vz79fp9KwZp//9CksB8F206BuVahU6BToOEJ/Ab7+KdABdTt8AAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAFvklEQVRoQ+2ZaaiVVRSGn9fS0iabCNO0eSaosAmplKJRxMiygSQCixQipBKMoDRBon5EI/0pQ8JuRQTVj4omo+FH04/muVum2GCDWVYr3ss+8t3vfud8+3guXi6cBYc7nD2sd6+11/BuMcxFw1x/ugCG2oL9LBAR44HeFkr9B/wMbAOMBT4B9gC+BiZL+rfZ3Ijw+PuB6cA6YFdgAzAy/V41NQB/rpL0QNWAAS4UEVbQm+XKj8B4SX/VTYiIicC9wMnAjnXjC9/fKemaWgARsSfwEbBbxeDPgAOBL4AdgF2AD4ETJP2dq0xEHArcA4yGvjv4D/Br2vOo9P/ycosl3ZQD4IDkFiMqBl8LPASMkfRdREwFVknalKt8Y1xETJDUGxFea0NE2CX9aWbF+ZLuzgEwBlgPbNtEqYuAlZLsl4MmEWGL/t5iwQWS7sgB4Iv1TcE//yyZ1Ke9AOiR9MNgIGihvAOCrWJZKGlZDoCjgTdTZLDy1wGLS1HCkehF4DxJ9t0tlhbKXwbcAByRFp8taWUOgN2B94G9AZ/A9sD5wIPAdqUFngAuBTZuiUu1UH4O8DjwVQrR3nZuVhiNCEcFT3S4swX2k7QmImYDs3zqJRCOzfOBTe2AaKW8pOUR4cPy/tbH9+0cSc/mWMATfkp5wAtMlLQuAXNo7QEcfYqyBLjZFssBUad8IVI5bDsqWs7OAuCREeHselCaeLgkx/o+iQi71lPAsSUQyyQtrLsM6SB8h8oyxydf2Meu/CrgnGGZJcluNUDKpYRN9zEwCVgLjJPUb8OIODiBOKSw2lhJDr8tJSIc5ZzE7JIN6ad8OijrNQ9w8nJynSrppRwAjXhs5e0+lYklIo4DHgP2AUa1k8wiwjnmGeB0YIDyBSv4MB2yHQnPkvRGDgAjfxs4vq48iIhpwCuSXAq0JRHh6HZB0W2qFnCmBu4CludaYCen8zrl29K2w8Hp0o+U9EutBTrca0imdzuyITn2wqZdC3Qt0OEJDAsXcnHXLKmWSwn/PUmSK9JaiYgR5VKjdlKbAyJiL+DU3H7AtIpLhMslublvKinBXAg83E4pkWodZ2J3WO60XPVWSlLend9MSU9mJbKI+DxxPzPcvDdJ8Y2a6TfgCjcguZaIiFHA94ArTnd7S6oyf0TsC3yZ+oFLJD1SCyAVWp8Cnvxy6oRcXm+Winp+DXClK9S6fiAiXKrYPYu0jYu128tzI6LRD7gzPFPS8zkAXAGaHXDF6InTi41Ei2akablbAm8XfQ44rKSMmTezdn2SgLpinQK4nJ8i6fVaAGmyS2nX4JbNnVBuJ1V3RyPCzZD7abetDdmYXNFsRx/PFBEeMzMNmCbJRMIAqWpoDGDnNNIlb89gKV844VMSiKIrmdL8ILEdayPCljotMXeOQq/lADDdZ17IhK1daAbgTqiKdGrajNRZIZ2wSV732GW2w9HGbMcL7kvSJb5a0n05AEzqOnw69hqAT2pVxcSOlE8AbP2LgVvMfiQGorGVm5hjgJPSP26TdH0OADft3wJV3GhjfsfKF1zJILzX08AZLSy3SNLSHACOPnaXslkHXfmiMqnZd5xvBuJWSTfmAHCC8h2ootfdYJshnpASkX+eCKxo9bBRtWkKk3OBt5KrmgO1JUwf2n3LslTSohwAjs/vmmmoGGyGYnW64Da9SwBfdlOBLieyGOtCeeAt/K7gvbyWyQEnuiqZJ8l0zAAph9FxgMuHdqpUx23XTivqoo/fBdIdqxta/r5foit+WQZgF/IlNgFlxfx+VaS57V5O8eaD/Jbmu2Lqw+H3XEn+rlLS6887iTz285ILOruL1zwyrWFrFHWyVXwv+/JRjgVM5Vnp/ZN7GIyTmgsvb/iopNVObJL+8IIpyfnOrK+j2yNidKP6jAiD8CF5Xc+fnA7PXtB4o3Od1SvpvWYH046rtGv2rTK+C2CrHHOLTboW6FqgwxP4Hz4mJ0+J869tAAAAAElFTkSuQmCC") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADd0lEQVRoQ+2Zz2sdVRTHv+fJBDW6anDVXen6wZszYxYBiYgtFGst3VSDunKjpS0GpUlqfjVpsVVs6aaL0or4YxMVFCJZ2ZLdPUP+gq5bQnTxtNAkfTnlhnnlkmQy9yV9780rudt77tzv5/y4v4bQ4Y06XD/2ANodwec/AiJygJnvtdvTWfPnRkBEJAiCN8rl8kMfiPn5+Ve7u7v3rays0Orq6lJfX99/PuN2auMDoAD+BvA2M6/mTWSMOUtE48D6AjHGzN/kjdlNvy+AnWOOmQ/lTSYiEwDOWzsimgrDcCRvzG76GwGw8/zJzO9sN6GInAMwbW1UdSSKoqndCMwb6wNwGsB39Q+p6h/M/C4R2dTa1AoHYBWKyCkA1+pqiWi2Wq0e7e/vf7yRoJAAKcQggMtuJKIoOtoxACnE0/xOi/SXMAxPuhCFjUBdpIjYVWXSEf0TM3/g9BeriDMKdSPEz8z8vrU1xgwT0YXCrEJZy1iSJKOqOub0/8jMA0mSfKKqNwoPkHp7ioiGHIhRIvpHVa93BEBa2JcAfOlALAHo6RgAKzRJkk9V1S6xL7kpV4idOM31taxaIKJHqmpPnMMA9hcOQES2PDJkAT1XAAC+ZebPfWB3auNzmLObVsNRUNUXVHUujuM7OxXnMy4XwOcj29mIyOuq+lapVGrYCelKpkEQ3CyXy4tbzdN0AGPMxr2iYZ+sra3FcRybtgCIiK2BKw2rdgaUSqWoUqlIkQAepFDdAF7cBq5ERI9rtdr1OI7tmE2t6SmUEYFHAEaexYW/1QC2EF+ru5GIvg7D0D2GNJxprQY4o6qv1I/b6SpzOYqiLxpWng5oOQAzXxWRWwA+dkRfYOb1p5hGW6sBJpn5KytSRG4D+KguWFXHoyhy7xdeLC0F2ChSRL4H8OFuINoKYIUbY34gogHH3eeZef1K6tPaDpCm068A3nMEDzHzxY4BUNWSiPxORO6z5aDPPlGICNQ9bYyZIaLjjudzIQoFkKbTbwCO+UI0HcB9J/LdeY0xs0R02IGYYObRrWqiFQCfEZEtSHsfmGZm+4qxbbM/hQD8BeBNa0hEM2EYnmgLgP3lFARBT1dXly4vL//b29tbzQNIU+llAHeJaLFSqRzJes5vegR8xGbZLCwsHKzVav8z8/0sm0ID+MDvAfh4qZk2exFopnd9vv0ELrXBQO7fD10AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC/ElEQVRoQ+2Zy49NQRCHvx+ReK6IlZ34E7CUiCAR4xEbTLCyQRATYswwb2IQZDYWgojHZpCQECts+ResiQwLj0RClNSkb9Lu3HtPz7mZc8+V6eXt6tP1VVV3VdcVbT7U5vozC9BqD/7/HjCzlZLet9rS9fbP9ICZvQPWSfqRAmFmS4ClMHm+JiR9S1mXVyYFwIBXwEZJv7I2MrPjQH8A6JN0OWtNM/OpAL7HS0mbsjYzswGgN8gNS+rJWtPM/HQAfJ9nkrY22tDMTgMjQaZH0nAzCmatTQE4ClyNPvQU2CbJQ2vKKB2Aa2hmR4DrkbbPgQ5Jv6sJSgkQILqA0dgTkjraBiBAxPHtPz2UtDuGKK0HKkqamd8qg5HS9yXtjebLdYjrHNRqiAeS9gQvnQGGSnML1bvGzOwc0BfN35PUaWYHgRulBwjW9ju+O4JwqM/AWFsABIgLwKkIYgJY1jYAAeJQuGIXVIVcKTKxh8WfBin9J+AVpx/eFWUEqFkyNACKp0rhgWYArkg6kQibSyylmPOklQdibijBX+fSLHFRJkDid+qKmdlaYENOI0zeEcBNSZ9qbVIEQHWuyGOTNZLetgrAz8ClPFpHa1ZL8rf5lFGEB2oBfAxQi4D5DeDmAP7mGJPka0oD4LnDr9imH/xFe8AP4vLIjBclxWXItCOtaIBjwOKo3HaFRyWdnLbmYUHhAJKumdkt4ECk9JCkSitmWixFAwxKOjt5uZvdBvZH2vZLit8XSSBFA/yjpJndAfY1A9FSgOCJu0BnBNErqfIkzfRCywECxCNgR6Rtt6TzmdqHBmyKXG4ZM4sTWc04NzNPWE+AuG3ZlZInSuGBinXMbBzYGVkrE6JUACGcHgPbUyGKAIj7REmZ18y897o5ghiQ5E/bltRChwE/kF7Xj0jyLkbDYWbzgBfA+iA4LmlXqwD8LydvszjAF0lfswBCKC0E3gBeP22p186f8RBKUbaejJmtAr5L+lBPptQAKfCzAClWmkmZWQ/MpHVTvv0X9iFAQGQyevIAAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrUlEQVRoQ+2ZPYgTURCAZzbBXJnCeL2Cnb87b9MEtPBUrrMQFAtrtT5/ClGs9LBWWz0RtbBUFCF4oJDsbO68wsLA2YqQSmLlvpEHu7IuMdlLcus+yUKKhJfZ+ebnvZl5CJY/aLn+MAP41x7M1QPMfFtr/crzvHfTAs8FoNPp1LTWzwHgqIg0lFLvrQHwfX8BER8DwC6jNCIecF13wwoA3/dvIuKNpLJa60Oe560XGoCZd4rICiKeTCtaeABmPg4AJmRqg6xcaABmvg4At4aFRyEBhoVM4UMoCplHADCfJTEL5YEsIVNID5iQAYCHALCYxeq5b6PMfF5EBAAEESthGK7W6/XPRpFWq7W3VCqtZg2ZcT3g+/6i4zjzIlLSWn/yPO/DIGMNLCWY2Sj/+xGRK0qpZfNDEASnROTFVi0fr8+aA8z8Ld6KEfGt67oLYwMAwEUium8EREn7OgeAjwCwPyo/nrque3YSgAtE9GDaAM1mc65arc4Zuf1+P2w0Gt9jJZl5DQAORt+fENG5wgEw8zUAMB/zbBBRwyqAIAjuiMjlSOlNItpjFUCqWl0josMzgChR/9hGAWBbknjmAdPhDdqa0gfZzAMJKyVP4v8hhJYRcSni+0JEu63ahZj5anyQici6UuqIVQDdbrfS6/UqRulyufyTiH5sF8AlIro37VpoWEHIzGZ2tM+sEZFnSqkzk9RCS0R01wjIsZz+mug53hDRia0AnI4bGgDYISItz/M2jYC8Gpp2u30MEWuO4zha665Sqp0ZYFStX/iWchRAItFGzoHSsrJ2ZFl1mHg6bfVYJeGJv85CC++BpIJZ5kSFC6G0ha0e7mYJqcJ7IOkRay84UhD2XjHFIFZf8iW9YcYoYRi+tO6aNeupOs66iU/icV46zf/MAKZpzXFk/QL+JG1PUPhRiQAAAABJRU5ErkJggg==") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio:hover{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACSElEQVRoQ+2Zu4sUQRCHf5+C+gf4yBXMfMYHGvjCzEBQDIzV+HwEohipGKupD0QNDE8UEwUFTe68wEDhTMVUMFJ+0tArzbjs9u3Ojt0wBR0M9MzUV1XdXVWNKhcq1189wP/2YKcesH1d0nPgdVvgnQDY3iTpqaT9kuaAt9UA2D4o6aGkzVHpXcByFQC2r0q60lB2D7BUNIDtjZIeSDoyRNGyAWwfiiET4n6YlAtg+7Kka2PCozyAMSHT5CkLIIbMfUlbMhdmOQCZIVOeB2LI3JN0NNPq6bTZe8D2aUmOY72kN8DnoIXt7eF5FSEzkQdsB+OEsFwr6RPwbpixhqYStoPyqVwAbkaAY5KeTWD5wStZHrD9XdJgK34FhBP9H8kFOAvciQBhn3/RAcBHSTvjfx4DJ6cBOAPcbRvA9gZJYQT5DfwYKGl7UdLu+PwIOFUiwCVJYQRZBuZqA7gh6XxUegXYVhtAmq0uAnt7gLhQm9vorBZx74Hcc6D3QLKH/z2JGyVnlYs4pCfzEe4rsLW2XehicpAtAftqAwiZbhhBfgE/ZwVwDrjddi40KiG0HXpHO+KcJ8CJaXKheeBWBOgqnf6W1BwvgcOrATieFDTrJL0HViJAVwXNgVgPrJH0BfiQDTDKtREiNK7KLSnHASQLLacP1PxcVkWWq8PU3emq2yqJJ0b1Qsv2QKpdZp+orBBqmrfq5m5mSJXtgUZI1XnB0YCo94opCal6L/ka3ghtlIXqrllzT9VJ5k19Ek/y0zbf6QHatOYk3/oDujC8QMWgjf4AAAAASUVORK5CYII=") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute:hover{background:url("data:image/png;base64,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") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-text{font-size:14px;width:30px}.jessibuca-container .jessibuca-speed{font-size:14px;color:#fff}.jessibuca-container .jessibuca-quality-menu-list{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .jessibuca-quality-menu-list.jessibuca-quality-menu-shown{visibility:visible;opacity:1}.jessibuca-container .icon-title-tips{pointer-events:none;position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s ease 0s,opacity .3s ease 0s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .icon-title{display:inline-block;padding:5px 10px;font-size:12px;white-space:nowrap;color:#fff}.jessibuca-container .jessibuca-quality-menu{padding:8px 0}.jessibuca-container .jessibuca-quality-menu-item{display:block;height:25px;margin:0;padding:0 10px;cursor:pointer;font-size:14px;text-align:center;width:50px;color:hsla(0,0%,100%,.5);transition:color .3s,background-color .3s}.jessibuca-container .jessibuca-quality-menu-item:hover{background-color:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-quality-menu-item:focus{outline:none}.jessibuca-container .jessibuca-quality-menu-item.jessibuca-quality-menu-item-active{color:#2298fc}.jessibuca-container .jessibuca-volume-panel-wrap{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%) translateY(22%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px;height:120px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-wrap.jessibuca-volume-panel-wrap-show{visibility:visible;opacity:1}.jessibuca-container .jessibuca-volume-panel{cursor:pointer;position:absolute;top:21px;height:60px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-text{position:absolute;left:0;top:0;width:50px;height:20px;line-height:20px;text-align:center;color:#fff;font-size:12px}.jessibuca-container .jessibuca-volume-panel-handle{position:absolute;top:48px;left:50%;width:12px;height:12px;border-radius:12px;margin-left:-6px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:before{bottom:-54px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:after{bottom:6px;background:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-volume-panel-handle:after,.jessibuca-container .jessibuca-volume-panel-handle:before{content:"";position:absolute;display:block;left:50%;width:3px;margin-left:-1px;height:60px}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-controls{width:100vh}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-play-big:after{transform:translate(-50%,-50%) rotate(270deg)}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading{flex-direction:row}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading-text{transform:rotate(270deg)}');class $e{constructor(e){var t;this.player=e,((e,t)=>{e._opt.hasControl&&e._opt.controlAutoHide?e.$container.classList.add("jessibuca-controls-show-auto-hide"):e.$container.classList.add("jessibuca-controls-show");const i=e._opt,o=i.operateBtns;e.$container.insertAdjacentHTML("beforeend",`\n ${i.background?`<div class="jessibuca-poster" style="background-image: url(${i.background})"></div>`:""}\n <div class="jessibuca-loading">\n ${Ze.loading}\n ${i.loadingText?`<div class="jessibuca-loading-text">${i.loadingText}</div>`:""}\n </div>\n ${i.hasControl&&o.play?'<div class="jessibuca-play-big"></div>':""}\n ${i.hasControl?`\n <div class="jessibuca-recording">\n <div class="jessibuca-recording-red-point"></div>\n <div class="jessibuca-recording-time">00:00:01</div>\n <div class="jessibuca-icon-recordStop jessibuca-recording-stop">${Ze.recordStop}</div>\n </div>\n `:""}\n ${i.hasControl?`\n <div class="jessibuca-controls">\n <div class="jessibuca-controls-bottom">\n <div class="jessibuca-controls-left">\n ${i.showBandwidth?'<div class="jessibuca-controls-item jessibuca-speed"></div>':""}\n </div>\n <div class="jessibuca-controls-right">\n ${o.audio?`\n <div class="jessibuca-controls-item jessibuca-volume">\n ${Ze.audio}\n ${Ze.mute}\n <div class="jessibuca-volume-panel-wrap">\n <div class="jessibuca-volume-panel">\n <div class="jessibuca-volume-panel-handle"></div>\n </div>\n <div class="jessibuca-volume-panel-text"></div>\n </div>\n </div>\n `:""}\n ${o.play?`<div class="jessibuca-controls-item jessibuca-play">${Ze.play}</div><div class="jessibuca-controls-item jessibuca-pause">${Ze.pause}</div>`:""}\n ${o.screenshot?`<div class="jessibuca-controls-item jessibuca-screenshot">${Ze.screenshot}</div>`:""}\n ${o.record?` <div class="jessibuca-controls-item jessibuca-record">${Ze.record}</div><div class="jessibuca-controls-item jessibuca-record-stop">${Ze.recordStop}</div>`:""}\n ${o.fullscreen?`<div class="jessibuca-controls-item jessibuca-fullscreen">${Ze.fullscreen}</div><div class="jessibuca-controls-item jessibuca-fullscreen-exit">${Ze.fullscreenExit}</div>`:""}\n </div>\n </div>\n </div>\n `:""}\n\n `),Object.defineProperty(t,"$poster",{value:e.$container.querySelector(".jessibuca-poster")}),Object.defineProperty(t,"$loading",{value:e.$container.querySelector(".jessibuca-loading")}),Object.defineProperty(t,"$play",{value:e.$container.querySelector(".jessibuca-play")}),Object.defineProperty(t,"$playBig",{value:e.$container.querySelector(".jessibuca-play-big")}),Object.defineProperty(t,"$recording",{value:e.$container.querySelector(".jessibuca-recording")}),Object.defineProperty(t,"$recordingTime",{value:e.$container.querySelector(".jessibuca-recording-time")}),Object.defineProperty(t,"$recordingStop",{value:e.$container.querySelector(".jessibuca-recording-stop")}),Object.defineProperty(t,"$pause",{value:e.$container.querySelector(".jessibuca-pause")}),Object.defineProperty(t,"$controls",{value:e.$container.querySelector(".jessibuca-controls")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$volume",{value:e.$container.querySelector(".jessibuca-volume")}),Object.defineProperty(t,"$volumePanelWrap",{value:e.$container.querySelector(".jessibuca-volume-panel-wrap")}),Object.defineProperty(t,"$volumePanelText",{value:e.$container.querySelector(".jessibuca-volume-panel-text")}),Object.defineProperty(t,"$volumePanel",{value:e.$container.querySelector(".jessibuca-volume-panel")}),Object.defineProperty(t,"$volumeHandle",{value:e.$container.querySelector(".jessibuca-volume-panel-handle")}),Object.defineProperty(t,"$volumeOn",{value:e.$container.querySelector(".jessibuca-icon-audio")}),Object.defineProperty(t,"$volumeOff",{value:e.$container.querySelector(".jessibuca-icon-mute")}),Object.defineProperty(t,"$fullscreen",{value:e.$container.querySelector(".jessibuca-fullscreen")}),Object.defineProperty(t,"$fullscreenExit",{value:e.$container.querySelector(".jessibuca-fullscreen-exit")}),Object.defineProperty(t,"$record",{value:e.$container.querySelector(".jessibuca-record")}),Object.defineProperty(t,"$recordStop",{value:e.$container.querySelector(".jessibuca-record-stop")}),Object.defineProperty(t,"$screenshot",{value:e.$container.querySelector(".jessibuca-screenshot")}),Object.defineProperty(t,"$speed",{value:e.$container.querySelector(".jessibuca-speed")})})(e,this),t=this,Object.defineProperty(t,"controlsRect",{get:()=>t.$controls.getBoundingClientRect()}),Ke(e,this),((e,t)=>{const{events:{proxy:i},debug:o}=e;function r(e){const{bottom:i,height:o}=t.$volumePanel.getBoundingClientRect(),{height:r}=t.$volumeHandle.getBoundingClientRect();return ue(i-e.y-r/2,0,o-r/2)/(o-r)}i(window,["click","contextmenu"],(i=>{i.composedPath().indexOf(e.$container)>-1?t.isFocus=!0:t.isFocus=!1})),i(window,"orientationchange",(()=>{setTimeout((()=>{e.resize()}),300)})),i(t.$controls,"click",(e=>{e.stopPropagation()})),i(t.$pause,"click",(t=>{e.pause()})),i(t.$play,"click",(t=>{e.play(),e.resumeAudioAfterPause()})),i(t.$playBig,"click",(t=>{e.play(),e.resumeAudioAfterPause()})),i(t.$volume,"mouseover",(()=>{t.$volumePanelWrap.classList.add("jessibuca-volume-panel-wrap-show")})),i(t.$volume,"mouseout",(()=>{t.$volumePanelWrap.classList.remove("jessibuca-volume-panel-wrap-show")})),i(t.$volumeOn,"click",(i=>{i.stopPropagation(),he(t.$volumeOn,"display","none"),he(t.$volumeOff,"display","block");const o=e.volume;e.volume=0,e._lastVolume=o})),i(t.$volumeOff,"click",(i=>{i.stopPropagation(),he(t.$volumeOn,"display","block"),he(t.$volumeOff,"display","none"),e.volume=e.lastVolume||.5})),i(t.$screenshot,"click",(t=>{t.stopPropagation(),e.video.screenshot()})),i(t.$volumePanel,"click",(t=>{t.stopPropagation(),e.volume=r(t)})),i(t.$volumeHandle,"mousedown",(()=>{t.isVolumeDroging=!0})),i(t.$volumeHandle,"mousemove",(i=>{t.isVolumeDroging&&(e.volume=r(i))})),i(document,"mouseup",(()=>{t.isVolumeDroging&&(t.isVolumeDroging=!1)})),i(t.$record,"click",(t=>{t.stopPropagation(),e.recording=!0})),i(t.$recordStop,"click",(t=>{t.stopPropagation(),e.recording=!1})),i(t.$recordingStop,"click",(t=>{t.stopPropagation(),e.recording=!1})),i(t.$fullscreen,"click",(t=>{t.stopPropagation(),e.fullscreen=!0})),i(t.$fullscreenExit,"click",(t=>{t.stopPropagation(),e.fullscreen=!1})),e._opt.hasControl&&e._opt.controlAutoHide&&(i(e.$container,"mouseover",(()=>{e.fullscreen||he(t.$controls,"display","block")})),i(e.$container,"mouseout",(()=>{he(t.$controls,"display","none")})))})(e,this),e._opt.hotKey&&((e,t)=>{const{events:{proxy:i}}=e,o={};function r(e,t){o[e]?o[e].push(t):o[e]=[t]}r(te,(()=>{e.fullscreen&&(e.fullscreen=!1)})),r(ie,(()=>{e.volume+=.05})),r(oe,(()=>{e.volume-=.05})),i(window,"keydown",(e=>{if(t.isFocus){const t=document.activeElement.tagName.toUpperCase(),i=document.activeElement.getAttribute("contenteditable");if("INPUT"!==t&&"TEXTAREA"!==t&&""!==i&&"true"!==i){const t=o[e.keyCode];t&&(e.preventDefault(),t.forEach((e=>e())))}}}))})(e,this),this.player.debug.log("Control","init")}destroy(){this.$poster&&this.player.$container.removeChild(this.$poster),this.$loading&&this.player.$container.removeChild(this.$loading),this.$controls&&this.player.$container.removeChild(this.$controls),this.$playBig&&this.player.$container.removeChild(this.$playBig),this.player.debug.log("control","destroy")}autoSize(){const e=this.player;e.$container.style.padding="0 0";const t=e.width,i=e.height,o=t/i,r=e.video.$videoElement.width/e.video.$videoElement.height;if(o>r){const o=(t-i*r)/2;e.$container.style.padding=`0 ${o}px`}else{const o=(i-t/r)/2;e.$container.style.padding=`${o}px 0`}}}_e(".jessibuca-container{position:relative;display:block;width:100%;height:100%;overflow:hidden}.jessibuca-container.jessibuca-fullscreen-web{position:fixed;z-index:9999;left:0;top:0;right:0;bottom:0;width:100vw!important;height:100vh!important;background:#000}");var et=e=>{const{_opt:t,debug:i,events:{proxy:o}}=e;t.supportDblclickFullscreen&&o(e.$container,"dblclick",(t=>{const i=function(e){const t=e||window.event;return t.target||t.srcElement}(t),o=i.nodeName.toLowerCase();"canvas"!==o&&"video"!==o||(e.fullscreen=!e.fullscreen)})),o(document,"visibilitychange",(()=>{t.hiddenAutoPause&&(i.log("visibilitychange",document.visibilityState,e._isPlayingBeforePageHidden),"visible"===document.visibilityState?e._isPlayingBeforePageHidden&&e.play():(e._isPlayingBeforePageHidden=e.playing,e.playing&&e.pause()))})),o(window,"fullscreenchange",(()=>{null!==e.keepScreenOn&&"visible"===document.visibilityState&&e.enableWakeLock()}))};class tt{static init(){tt.types={avc1:[],avcC:[],hvc1:[],hvcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]};for(let e in tt.types)tt.types.hasOwnProperty(e)&&(tt.types[e]=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]);let e=tt.constants={};e.FTYP=new Uint8Array([105,115,111,109,0,0,0,1,105,115,111,109,97,118,99,49]),e.STSD_PREFIX=new Uint8Array([0,0,0,0,0,0,0,1]),e.STTS=new Uint8Array([0,0,0,0,0,0,0,0]),e.STSC=e.STCO=e.STTS,e.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),e.HDLR_VIDEO=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),e.HDLR_AUDIO=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]),e.DREF=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),e.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),e.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0])}static box(e){let t=8,i=null,o=Array.prototype.slice.call(arguments,1),r=o.length;for(let e=0;e<r;e++)t+=o[e].byteLength;i=new Uint8Array(t),i[0]=t>>>24&255,i[1]=t>>>16&255,i[2]=t>>>8&255,i[3]=255&t,i.set(e,4);let s=8;for(let e=0;e<r;e++)i.set(o[e],s),s+=o[e].byteLength;return i}static generateInitSegment(e){let t=tt.box(tt.types.ftyp,tt.constants.FTYP),i=tt.moov(e),o=new Uint8Array(t.byteLength+i.byteLength);return o.set(t,0),o.set(i,t.byteLength),o}static moov(e){let t=tt.mvhd(e.timescale,e.duration),i=tt.trak(e),o=tt.mvex(e);return tt.box(tt.types.moov,t,i,o)}static mvhd(e,t){return tt.box(tt.types.mvhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,e>>>24&255,e>>>16&255,e>>>8&255,255&e,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]))}static trak(e){return tt.box(tt.types.trak,tt.tkhd(e),tt.mdia(e))}static tkhd(e){let t=e.id,i=e.duration,o=e.presentWidth,r=e.presentHeight;return tt.box(tt.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,o>>>8&255,255&o,0,0,r>>>8&255,255&r,0,0]))}static mdia(e){return tt.box(tt.types.mdia,tt.mdhd(e),tt.hdlr(e),tt.minf(e))}static mdhd(e){let t=e.timescale,i=e.duration;return tt.box(tt.types.mdhd,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,i>>>24&255,i>>>16&255,i>>>8&255,255&i,85,196,0,0]))}static hdlr(e){let t=null;return t="audio"===e.type?tt.constants.HDLR_AUDIO:tt.constants.HDLR_VIDEO,tt.box(tt.types.hdlr,t)}static minf(e){let t=null;return t="audio"===e.type?tt.box(tt.types.smhd,tt.constants.SMHD):tt.box(tt.types.vmhd,tt.constants.VMHD),tt.box(tt.types.minf,t,tt.dinf(),tt.stbl(e))}static dinf(){return tt.box(tt.types.dinf,tt.box(tt.types.dref,tt.constants.DREF))}static stbl(e){return tt.box(tt.types.stbl,tt.stsd(e),tt.box(tt.types.stts,tt.constants.STTS),tt.box(tt.types.stsc,tt.constants.STSC),tt.box(tt.types.stsz,tt.constants.STSZ),tt.box(tt.types.stco,tt.constants.STCO))}static stsd(e){return"audio"===e.type?tt.box(tt.types.stsd,tt.constants.STSD_PREFIX,tt.mp4a(e)):"avc"===e.videoType?tt.box(tt.types.stsd,tt.constants.STSD_PREFIX,tt.avc1(e)):tt.box(tt.types.stsd,tt.constants.STSD_PREFIX,tt.hvc1(e))}static mp4a(e){let t=e.channelCount,i=e.audioSampleRate,o=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t,0,16,0,0,0,0,i>>>8&255,255&i,0,0]);return tt.box(tt.types.mp4a,o,tt.esds(e))}static esds(e){let t=e.config||[],i=t.length,o=new Uint8Array([0,0,0,0,3,23+i,0,1,0,4,15+i,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([i]).concat(t).concat([6,1,2]));return tt.box(tt.types.esds,o)}static avc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return tt.box(tt.types.avc1,r,tt.box(tt.types.avcC,t))}static hvc1(e){let t=e.avcc;const i=e.codecWidth,o=e.codecHeight;let r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,i>>>8&255,255&i,o>>>8&255,255&o,0,72,0,0,0,72,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255]);return tt.box(tt.types.hvc1,r,tt.box(tt.types.hvcC,t))}static mvex(e){return tt.box(tt.types.mvex,tt.trex(e))}static trex(e){let t=e.id,i=new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]);return tt.box(tt.types.trex,i)}static moof(e,t){return tt.box(tt.types.moof,tt.mfhd(e.sequenceNumber),tt.traf(e,t))}static mfhd(e){let t=new Uint8Array([0,0,0,0,e>>>24&255,e>>>16&255,e>>>8&255,255&e]);return tt.box(tt.types.mfhd,t)}static traf(e,t){let i=e.id,o=tt.box(tt.types.tfhd,new Uint8Array([0,0,0,0,i>>>24&255,i>>>16&255,i>>>8&255,255&i])),r=tt.box(tt.types.tfdt,new Uint8Array([0,0,0,0,t>>>24&255,t>>>16&255,t>>>8&255,255&t])),s=tt.sdtp(e),a=tt.trun(e,s.byteLength+16+16+8+16+8+8);return tt.box(tt.types.traf,o,r,a,s)}static sdtp(e){let t=new Uint8Array(5),i=e.flags;return t[4]=i.isLeading<<6|i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy,tt.box(tt.types.sdtp,t)}static trun(e,t){let i=new Uint8Array(28);t+=36,i.set([0,0,15,1,0,0,0,1,t>>>24&255,t>>>16&255,t>>>8&255,255&t],0);let o=e.duration,r=e.size,s=e.flags,a=e.cts;return i.set([o>>>24&255,o>>>16&255,o>>>8&255,255&o,r>>>24&255,r>>>16&255,r>>>8&255,255&r,s.isLeading<<2|s.dependsOn,s.isDependedOn<<6|s.hasRedundancy<<4|s.isNonSync,0,0,a>>>24&255,a>>>16&255,a>>>8&255,255&a],12),tt.box(tt.types.trun,i)}static mdat(e){return tt.box(tt.types.mdat,e)}}tt.init();class it extends we{constructor(e){super(),this.player=e,this.isAvc=!0,this.mediaSource=new window.MediaSource,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.cacheTrack={},this.timeInit=!1,this.sequenceNumber=0,this.mediaSourceOpen=!1,this.dropping=!1,this.firstRenderTime=null,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.player.video.$videoElement.src=window.URL.createObjectURL(this.mediaSource);const{debug:t,events:{proxy:i}}=e;i(this.mediaSource,"sourceopen",(()=>{this.mediaSourceOpen=!0,this.player.emit(T.mseSourceOpen)})),i(this.mediaSource,"sourceclose",(()=>{this.player.emit(T.mseSourceClose)})),e.debug.log("MediaSource","init")}destroy(){this.stop(),this.mediaSource=null,this.mediaSourceOpen=!1,this.sourceBuffer=null,this.hasInit=!1,this.isInitInfo=!1,this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1,this.mediaSourceAppendBufferError=!1,this.mediaSourceAppendBufferFull=!1,this.isDecodeFirstIIframe=!1,this.off(),this.player.debug.log("MediaSource","destroy")}get state(){return this.mediaSource&&this.mediaSource.readyState}get isStateOpen(){return this.state===_}get isStateClosed(){return this.state===$}get isStateEnded(){return this.state===K}get duration(){return this.mediaSource&&this.mediaSource.duration}set duration(e){this.mediaSource.duration=e}decodeVideo(e,t,i,o){const r=this.player;if(r)if(this.hasInit){if(i&&0===e[1]){e[0];let t={};t=Ye(e);const i=this.player.video.videoInfo;t.codecWidth===i.width&&t.codecHeight===i.height||(this.player.debug.warn("MediaSource",`width or height is update, width ${i.width}-> ${t.codecWidth}, height ${i.height}-> ${t.codecHeight}`),this.isInitInfo=!1,this.player.video.init=!1)}if(!this.isDecodeFirstIIframe&&i&&(this.isDecodeFirstIIframe=!0),this.isDecodeFirstIIframe){null===this.firstRenderTime&&(this.firstRenderTime=t);const r=t-this.firstRenderTime;this._decodeVideo(e,r,i,o)}else this.player.debug.warn("MediaSource","decodeVideo isDecodeFirstIIframe false")}else if(i&&0===e[1]){const o=15&e[0];if(r.video.updateVideoInfo({encTypeCode:o}),o===M)return void this.emit(x.mediaSourceH265NotSupport);r._times.decodeStart||(r._times.decodeStart=le()),this._decodeConfigurationRecord(e,t,i,o),this.hasInit=!0}}_decodeConfigurationRecord(e,t,i,o){let r=e.slice(5),s={};s=Ye(r);const a={id:1,type:"video",timescale:1e3,duration:0,avcc:r,codecWidth:s.codecWidth,codecHeight:s.codecHeight,videoType:s.videoType},n=tt.generateInitSegment(a);this.isAvc=!0,this.appendBuffer(n.buffer),this.sequenceNumber=0,this.cacheTrack=null,this.timeInit=!1}_decodeVideo(e,t,i,o){const r=this.player;let s=e.slice(5),a=s.byteLength;const n=r.video.$videoElement,A=r._opt.videoBufferDelay;if(n.buffered.length>1&&(this.removeBuffer(n.buffered.start(0),n.buffered.end(0)),this.timeInit=!1),this.dropping&&t-this.cacheTrack.dts>A)this.dropping=!1,this.cacheTrack={};else if(this.cacheTrack&&t>=this.cacheTrack.dts){let e=8+this.cacheTrack.size,i=new Uint8Array(e);i[0]=e>>>24&255,i[1]=e>>>16&255,i[2]=e>>>8&255,i[3]=255&e,i.set(tt.types.mdat,4),i.set(this.cacheTrack.data,8),this.cacheTrack.duration=t-this.cacheTrack.dts;let o=tt.moof(this.cacheTrack,this.cacheTrack.dts),s=new Uint8Array(o.byteLength+i.byteLength);s.set(o,0),s.set(i,o.byteLength),this.appendBuffer(s.buffer),r.handleRender(),r.updateStats({fps:!0,ts:t,buf:r.demux&&r.demux.delay||0}),r._times.videoStart||(r._times.videoStart=le(),r.handlePlayToRenderTimes())}else r.debug.log("MediaSource","timeInit set false , cacheTrack = {}"),this.timeInit=!1,this.cacheTrack={};this.cacheTrack||(this.cacheTrack={}),this.cacheTrack.id=1,this.cacheTrack.sequenceNumber=++this.sequenceNumber,this.cacheTrack.size=a,this.cacheTrack.dts=t,this.cacheTrack.cts=o,this.cacheTrack.isKeyframe=i,this.cacheTrack.data=s,this.cacheTrack.flags={isLeading:0,dependsOn:i?2:1,isDependedOn:i?1:0,hasRedundancy:0,isNonSync:i?0:1},this.timeInit||1!==n.buffered.length||(r.debug.log("MediaSource","timeInit set true"),this.timeInit=!0,n.currentTime=n.buffered.end(0)),!this.isInitInfo&&n.videoWidth>0&&n.videoHeight>0&&(r.debug.log("MediaSource",`updateVideoInfo: ${n.videoWidth},${n.videoHeight}`),r.video.updateVideoInfo({width:n.videoWidth,height:n.videoHeight}),r.video.initCanvasViewSize(),this.isInitInfo=!0)}appendBuffer(e){const{debug:t,events:{proxy:i}}=this.player;if(null===this.sourceBuffer&&(this.sourceBuffer=this.mediaSource.addSourceBuffer(Z),i(this.sourceBuffer,"error",(e=>{this.player.emit(T.mseSourceBufferError,e)}))),this.mediaSourceAppendBufferError)t.error("MediaSource","this.mediaSourceAppendBufferError is true");else if(this.mediaSourceAppendBufferFull)t.error("MediaSource","this.mediaSourceAppendBufferFull is true");else if(!1===this.sourceBuffer.updating&&this.isStateOpen)try{this.sourceBuffer.appendBuffer(e)}catch(e){t.warn("MediaSource","this.sourceBuffer.appendBuffer()",e.code,e),22===e.code?(this.stop(),this.mediaSourceAppendBufferFull=!0,this.emit(x.mediaSourceFull)):11===e.code?(this.stop(),this.mediaSourceAppendBufferError=!0,this.emit(x.mediaSourceAppendBufferError)):(t.error("MediaSource","appendBuffer error",e),this.player.emit(T.mseSourceBufferError,e))}else this.isStateClosed?this.player.emit(x.mseSourceBufferError,"mediaSource is not attached to video or mediaSource is closed"):this.isStateEnded?this.player.emit(x.mseSourceBufferError,"mediaSource is closed"):!0===this.sourceBuffer.updating&&this.player.emit(T.mseSourceBufferBusy)}stop(){this.abortSourceBuffer(),this.removeSourceBuffer(),this.endOfStream()}dropSourceBuffer(e){const t=this.player.video.$videoElement;this.dropping=e,t.buffered.length>0&&t.buffered.end(0)-t.currentTime>1&&(this.player.debug.warn("MediaSource","dropSourceBuffer",`$video.buffered.end(0) is ${t.buffered.end(0)} - $video.currentTime ${t.currentTime}`),t.currentTime=t.buffered.end(0))}removeBuffer(e,t){if(this.isStateOpen&&!1===this.sourceBuffer.updating)try{this.sourceBuffer.remove(e,t)}catch(e){this.player.debug.warn("MediaSource","removeBuffer() error",e)}else this.player.debug.warn("MediaSource","removeBuffer() this.isStateOpen is",this.isStateOpen,"this.sourceBuffer.updating",this.sourceBuffer.updating)}endOfStream(){if(this.isStateOpen)try{this.mediaSource.endOfStream()}catch(e){this.player.debug.warn("MediaSource","endOfStream() error",e)}}abortSourceBuffer(){this.isStateOpen&&this.sourceBuffer&&(this.sourceBuffer.abort(),this.sourceBuffer=null)}removeSourceBuffer(){if(!this.isStateClosed&&this.mediaSource&&this.sourceBuffer)try{this.mediaSource.removeSourceBuffer(this.sourceBuffer)}catch(e){this.player.debug.warn("MediaSource","removeSourceBuffer() error",e)}}}const ot=()=>"undefined"!=typeof navigator&&parseFloat((""+(/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))<10&&!window.MSStream,rt=()=>"wakeLock"in navigator;class st{constructor(e){if(this.player=e,this.enabled=!1,rt()){this._wakeLock=null;const e=()=>{null!==this._wakeLock&&"visible"===document.visibilityState&&this.enable()};document.addEventListener("visibilitychange",e),document.addEventListener("fullscreenchange",e)}else ot()?this.noSleepTimer=null:(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("title","No Sleep"),this.noSleepVideo.setAttribute("playsinline",""),this._addSourceToVideo(this.noSleepVideo,"webm","data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBCEKChHdlYm1Ch4EEQoWBAhhTgGcBAAAAAAAVkhFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsghV17AEAAAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUq17GDD0JATYCNTGF2ZjU1LjMzLjEwMFdBjUxhdmY1NS4zMy4xMDBzpJBlrrXf3DCDVB8KcgbMpcr+RImIQJBgAAAAAAAWVK5rAQAAAAAAD++uAQAAAAAAADLXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDiDgQEj44OEAmJaAOABAAAAAAAABrCBsLqBkK4BAAAAAAAPq9eBAnPFgQKcgQAitZyDdW5khohBX1ZPUkJJU4OBAuEBAAAAAAAAEZ+BArWIQOdwAAAAAABiZIEgY6JPbwIeVgF2b3JiaXMAAAAAAoC7AAAAAAAAgLUBAAAAAAC4AQN2b3JiaXMtAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAxMDExMDEgKFNjaGF1ZmVudWdnZXQpAQAAABUAAABlbmNvZGVyPUxhdmM1NS41Mi4xMDIBBXZvcmJpcyVCQ1YBAEAAACRzGCpGpXMWhBAaQlAZ4xxCzmvsGUJMEYIcMkxbyyVzkCGkoEKIWyiB0JBVAABAAACHQXgUhIpBCCGEJT1YkoMnPQghhIg5eBSEaUEIIYQQQgghhBBCCCGERTlokoMnQQgdhOMwOAyD5Tj4HIRFOVgQgydB6CCED0K4moOsOQghhCQ1SFCDBjnoHITCLCiKgsQwuBaEBDUojILkMMjUgwtCiJqDSTX4GoRnQXgWhGlBCCGEJEFIkIMGQcgYhEZBWJKDBjm4FITLQagahCo5CB+EIDRkFQCQAACgoiiKoigKEBqyCgDIAAAQQFEUx3EcyZEcybEcCwgNWQUAAAEACAAAoEiKpEiO5EiSJFmSJVmSJVmS5omqLMuyLMuyLMsyEBqyCgBIAABQUQxFcRQHCA1ZBQBkAAAIoDiKpViKpWiK54iOCISGrAIAgAAABAAAEDRDUzxHlETPVFXXtm3btm3btm3btm3btm1blmUZCA1ZBQBAAAAQ0mlmqQaIMAMZBkJDVgEACAAAgBGKMMSA0JBVAABAAACAGEoOogmtOd+c46BZDppKsTkdnEi1eZKbirk555xzzsnmnDHOOeecopxZDJoJrTnnnMSgWQqaCa0555wnsXnQmiqtOeeccc7pYJwRxjnnnCateZCajbU555wFrWmOmkuxOeecSLl5UptLtTnnnHPOOeecc84555zqxekcnBPOOeecqL25lpvQxTnnnE/G6d6cEM4555xzzjnnnHPOOeecIDRkFQAABABAEIaNYdwpCNLnaCBGEWIaMulB9+gwCRqDnELq0ehopJQ6CCWVcVJKJwgNWQUAAAIAQAghhRRSSCGFFFJIIYUUYoghhhhyyimnoIJKKqmooowyyyyzzDLLLLPMOuyssw47DDHEEEMrrcRSU2011lhr7jnnmoO0VlprrbVSSimllFIKQkNWAQAgAAAEQgYZZJBRSCGFFGKIKaeccgoqqIDQkFUAACAAgAAAAABP8hzRER3RER3RER3RER3R8RzPESVREiVREi3TMjXTU0VVdWXXlnVZt31b2IVd933d933d+HVhWJZlWZZlWZZlWZZlWZZlWZYgNGQVAAACAAAghBBCSCGFFFJIKcYYc8w56CSUEAgNWQUAAAIACAAAAHAUR3EcyZEcSbIkS9IkzdIsT/M0TxM9URRF0zRV0RVdUTdtUTZl0zVdUzZdVVZtV5ZtW7Z125dl2/d93/d93/d93/d93/d9XQdCQ1YBABIAADqSIymSIimS4ziOJElAaMgqAEAGAEAAAIriKI7jOJIkSZIlaZJneZaomZrpmZ4qqkBoyCoAABAAQAAAAAAAAIqmeIqpeIqoeI7oiJJomZaoqZoryqbsuq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq4LhIasAgAkAAB0JEdyJEdSJEVSJEdygNCQVQCADACAAAAcwzEkRXIsy9I0T/M0TxM90RM901NFV3SB0JBVAAAgAIAAAAAAAAAMybAUy9EcTRIl1VItVVMt1VJF1VNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVN0zRNEwgNWQkAkAEAkBBTLS3GmgmLJGLSaqugYwxS7KWxSCpntbfKMYUYtV4ah5RREHupJGOKQcwtpNApJq3WVEKFFKSYYyoVUg5SIDRkhQAQmgHgcBxAsixAsiwAAAAAAAAAkDQN0DwPsDQPAAAAAAAAACRNAyxPAzTPAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAA0DwP8DwR8EQRAAAAAAAAACzPAzTRAzxRBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA0jRA8zxA8zwAAAAAAAAAsDwP8EQR0DwRAAAAAAAAACzPAzxRBDzRAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEOAAABBgIRQasiIAiBMAcEgSJAmSBM0DSJYFTYOmwTQBkmVB06BpME0AAAAAAAAAAAAAJE2DpkHTIIoASdOgadA0iCIAAAAAAAAAAAAAkqZB06BpEEWApGnQNGgaRBEAAAAAAAAAAAAAzzQhihBFmCbAM02IIkQRpgkAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAGHAAAAgwoQwUGrIiAIgTAHA4imUBAIDjOJYFAACO41gWAABYliWKAABgWZooAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAYcAAACDChDBQashIAiAIAcCiKZQHHsSzgOJYFJMmyAJYF0DyApgFEEQAIAAAocAAACLBBU2JxgEJDVgIAUQAABsWxLE0TRZKkaZoniiRJ0zxPFGma53meacLzPM80IYqiaJoQRVE0TZimaaoqME1VFQAAUOAAABBgg6bE4gCFhqwEAEICAByKYlma5nmeJ4qmqZokSdM8TxRF0TRNU1VJkqZ5niiKommapqqyLE3zPFEURdNUVVWFpnmeKIqiaaqq6sLzPE8URdE0VdV14XmeJ4qiaJqq6roQRVE0TdNUTVV1XSCKpmmaqqqqrgtETxRNU1Vd13WB54miaaqqq7ouEE3TVFVVdV1ZBpimaaqq68oyQFVV1XVdV5YBqqqqruu6sgxQVdd1XVmWZQCu67qyLMsCAAAOHAAAAoygk4wqi7DRhAsPQKEhKwKAKAAAwBimFFPKMCYhpBAaxiSEFEImJaXSUqogpFJSKRWEVEoqJaOUUmopVRBSKamUCkIqJZVSAADYgQMA2IGFUGjISgAgDwCAMEYpxhhzTiKkFGPOOScRUoox55yTSjHmnHPOSSkZc8w556SUzjnnnHNSSuacc845KaVzzjnnnJRSSuecc05KKSWEzkEnpZTSOeecEwAAVOAAABBgo8jmBCNBhYasBABSAQAMjmNZmuZ5omialiRpmud5niiapiZJmuZ5nieKqsnzPE8URdE0VZXneZ4oiqJpqirXFUXTNE1VVV2yLIqmaZqq6rowTdNUVdd1XZimaaqq67oubFtVVdV1ZRm2raqq6rqyDFzXdWXZloEsu67s2rIAAPAEBwCgAhtWRzgpGgssNGQlAJABAEAYg5BCCCFlEEIKIYSUUggJAAAYcAAACDChDBQashIASAUAAIyx1lprrbXWQGettdZaa62AzFprrbXWWmuttdZaa6211lJrrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmuttdZaa6211lprrbXWWmstpZRSSimllFJKKaWUUkoppZRSSgUA+lU4APg/2LA6wknRWGChISsBgHAAAMAYpRhzDEIppVQIMeacdFRai7FCiDHnJKTUWmzFc85BKCGV1mIsnnMOQikpxVZjUSmEUlJKLbZYi0qho5JSSq3VWIwxqaTWWoutxmKMSSm01FqLMRYjbE2ptdhqq7EYY2sqLbQYY4zFCF9kbC2m2moNxggjWywt1VprMMYY3VuLpbaaizE++NpSLDHWXAAAd4MDAESCjTOsJJ0VjgYXGrISAAgJACAQUooxxhhzzjnnpFKMOeaccw5CCKFUijHGnHMOQgghlIwx5pxzEEIIIYRSSsaccxBCCCGEkFLqnHMQQgghhBBKKZ1zDkIIIYQQQimlgxBCCCGEEEoopaQUQgghhBBCCKmklEIIIYRSQighlZRSCCGEEEIpJaSUUgohhFJCCKGElFJKKYUQQgillJJSSimlEkoJJYQSUikppRRKCCGUUkpKKaVUSgmhhBJKKSWllFJKIYQQSikFAAAcOAAABBhBJxlVFmGjCRcegEJDVgIAZAAAkKKUUiktRYIipRikGEtGFXNQWoqocgxSzalSziDmJJaIMYSUk1Qy5hRCDELqHHVMKQYtlRhCxhik2HJLoXMOAAAAQQCAgJAAAAMEBTMAwOAA4XMQdAIERxsAgCBEZohEw0JweFAJEBFTAUBigkIuAFRYXKRdXECXAS7o4q4DIQQhCEEsDqCABByccMMTb3jCDU7QKSp1IAAAAAAADADwAACQXAAREdHMYWRobHB0eHyAhIiMkAgAAAAAABcAfAAAJCVAREQ0cxgZGhscHR4fICEiIyQBAIAAAgAAAAAggAAEBAQAAAAAAAIAAAAEBB9DtnUBAAAAAAAEPueBAKOFggAAgACjzoEAA4BwBwCdASqwAJAAAEcIhYWIhYSIAgIABhwJ7kPfbJyHvtk5D32ych77ZOQ99snIe+2TkPfbJyHvtk5D32ych77ZOQ99YAD+/6tQgKOFggADgAqjhYIAD4AOo4WCACSADqOZgQArADECAAEQEAAYABhYL/QACIBDmAYAAKOFggA6gA6jhYIAT4AOo5mBAFMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAGSADqOFggB6gA6jmYEAewAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIAj4AOo5mBAKMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAKSADqOFggC6gA6jmYEAywAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIAz4AOo4WCAOSADqOZgQDzADECAAEQEAAYABhYL/QACIBDmAYAAKOFggD6gA6jhYIBD4AOo5iBARsAEQIAARAQFGAAYWC/0AAiAQ5gGACjhYIBJIAOo4WCATqADqOZgQFDADECAAEQEAAYABhYL/QACIBDmAYAAKOFggFPgA6jhYIBZIAOo5mBAWsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAXqADqOFggGPgA6jmYEBkwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIBpIAOo4WCAbqADqOZgQG7ADECAAEQEAAYABhYL/QACIBDmAYAAKOFggHPgA6jmYEB4wAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIB5IAOo4WCAfqADqOZgQILADECAAEQEAAYABhYL/QACIBDmAYAAKOFggIPgA6jhYICJIAOo5mBAjMAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAjqADqOFggJPgA6jmYECWwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYICZIAOo4WCAnqADqOZgQKDADECAAEQEAAYABhYL/QACIBDmAYAAKOFggKPgA6jhYICpIAOo5mBAqsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCArqADqOFggLPgA6jmIEC0wARAgABEBAUYABhYL/QACIBDmAYAKOFggLkgA6jhYIC+oAOo5mBAvsAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCAw+ADqOZgQMjADECAAEQEAAYABhYL/QACIBDmAYAAKOFggMkgA6jhYIDOoAOo5mBA0sAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCA0+ADqOFggNkgA6jmYEDcwAxAgABEBAAGAAYWC/0AAiAQ5gGAACjhYIDeoAOo4WCA4+ADqOZgQObADECAAEQEAAYABhYL/QACIBDmAYAAKOFggOkgA6jhYIDuoAOo5mBA8MAMQIAARAQABgAGFgv9AAIgEOYBgAAo4WCA8+ADqOFggPkgA6jhYID+oAOo4WCBA+ADhxTu2sBAAAAAAAAEbuPs4EDt4r3gQHxghEr8IEK"),this._addSourceToVideo(this.noSleepVideo,"mp4","data:video/mp4;base64,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"),this.noSleepVideo.addEventListener("loadedmetadata",(()=>{this.noSleepVideo.duration<=1?this.noSleepVideo.setAttribute("loop",""):this.noSleepVideo.addEventListener("timeupdate",(()=>{this.noSleepVideo.currentTime>.5&&(this.noSleepVideo.currentTime=Math.random())}))})))}_addSourceToVideo(e,t,i){var o=document.createElement("source");o.src=i,o.type=`video/${t}`,e.appendChild(o)}get isEnabled(){return this.enabled}enable(){const e=this.player.debug;if(rt())return navigator.wakeLock.request("screen").then((t=>{this._wakeLock=t,this.enabled=!0,e.log("wakeLock","Wake Lock active."),this._wakeLock.addEventListener("release",(()=>{e.log("wakeLock","Wake Lock released.")}))})).catch((t=>{throw this.enabled=!1,e.error("wakeLock",`${t.name}, ${t.message}`),t}));if(ot())return this.disable(),this.noSleepTimer=window.setInterval((()=>{document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))}),15e3),this.enabled=!0,Promise.resolve();return this.noSleepVideo.play().then((e=>(this.enabled=!0,e))).catch((e=>{throw this.enabled=!1,e}))}disable(){const e=this.player.debug;rt()?(this._wakeLock&&this._wakeLock.release(),this._wakeLock=null):ot()?this.noSleepTimer&&(e.warn("wakeLock","NoSleep now disabled for older iOS devices."),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause(),this.enabled=!1}}class at extends we{constructor(e,t){var i;super(),this.$container=e,this._opt=Object.assign({},d,t),this.debug=new ae(this),this._opt.useWCS&&(this._opt.useWCS="VideoEncoder"in window),this._opt.useMSE&&(this._opt.useMSE=window.MediaSource&&window.MediaSource.isTypeSupported(Z)),this._opt.wcsUseVideoRender&&(this._opt.wcsUseVideoRender=window.MediaStreamTrackGenerator&&"function"==typeof window.MediaStreamTrackGenerator),this._opt.useMSE&&(this._opt.useWCS&&this.debug.log("Player","useWCS set true->false"),this._opt.forceNoOffscreen||this.debug.log("Player","forceNoOffscreen set false->true"),this._opt.useWCS=!1,this._opt.forceNoOffscreen=!0),this._opt.forceNoOffscreen||("undefined"==typeof OffscreenCanvas?(this._opt.forceNoOffscreen=!0,this._opt.useOffscreen=!1):this._opt.useOffscreen=!0),this._opt.hasAudio||(this._opt.operateBtns.audio=!1),this._opt.hasControl=this._hasControl(),this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._checkHeartTimeout=null,this._checkLoadingTimeout=null,this._checkStatsInterval=null,this._startBpsTime=null,this._isPlayingBeforePageHidden=!1,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0},this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this._videoTimestamp=0,this._audioTimestamp=0,i=this,Object.defineProperty(i,"rect",{get:()=>{const e=i.$container.getBoundingClientRect();return e.width=Math.max(e.width,i.$container.clientWidth),e.height=Math.max(e.height,i.$container.clientHeight),e}}),["bottom","height","left","right","top","width"].forEach((e=>{Object.defineProperty(i,e,{get:()=>i.rect[e]})})),this.events=new ne(this),this.video=new De(this),this._opt.hasAudio&&(this.audio=new Le(this)),this.recorder=new Qe(this),this._onlyMseOrWcsVideo()?this.loaded=!0:this.decoderWorker=new We(this),this.stream=null,this.demux=null,this._lastVolume=null,this._opt.useWCS&&(this.webcodecsDecoder=new Xe(this),this.loaded=!0),this._opt.useMSE&&(this.mseDecoder=new it(this),this.loaded=!0),this.control=new $e(this),fe()&&(this.keepScreenOn=new st(this)),(e=>{try{const t=t=>{t.target===e.$container&&(e.emit(I.fullscreen,e.fullscreen),e.fullscreen?e._opt.useMSE&&e.resize():e.resize())};Ae.on("change",t),e.events.destroys.push((()=>{Ae.off("change",t)}))}catch(e){}if(e.on(T.decoderWorkerInit,(()=>{e.debug.log("player","has loaded"),e.loaded=!0})),e.on(T.play,(()=>{e.loading=!1})),e.on(T.fullscreen,(t=>{if(t)try{Ae.request(e.$container).then((()=>{})).catch((t=>{e.webFullscreen=!0}))}catch(t){e.webFullscreen=!0}else try{Ae.exit().then((()=>{})).catch((()=>{e.webFullscreen=!1}))}catch(t){e.webFullscreen=!1}})),e.on(T.webFullscreen,(t=>{t?e.$container.classList.add("jessibuca-fullscreen-web"):e.$container.classList.remove("jessibuca-fullscreen-web"),e.emit(I.fullscreen,e.fullscreen)})),e.on(T.resize,(()=>{e.video.resize()})),e._opt.debug){const t=[T.timeUpdate];Object.keys(T).forEach((i=>{e.on(T[i],(o=>{t.includes(i)||e.debug.log("player events",T[i],o)}))})),Object.keys(x).forEach((t=>{e.on(x[t],(i=>{e.debug.log("player event error",x[t],i)}))}))}})(this),et(this),this._opt.useWCS&&this.debug.log("Player","use WCS"),this._opt.useMSE&&this.debug.log("Player","use MSE"),this._opt.useOffscreen&&this.debug.log("Player","use offscreen"),this.debug.log("Player options",this._opt)}destroy(){this._loading=!1,this._playing=!1,this._hasLoaded=!1,this._lastVolume=null,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},this.decoderWorker&&(this.decoderWorker.destroy(),this.decoderWorker=null),this.video&&(this.video.destroy(),this.video=null),this.audio&&(this.audio.destroy(),this.audio=null),this.stream&&(this.stream.destroy(),this.stream=null),this.recorder&&(this.recorder.destroy(),this.recorder=null),this.control&&(this.control.destroy(),this.control=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.demux&&(this.demux.destroy(),this.demux=null),this.events&&(this.events.destroy(),this.events=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.releaseWakeLock(),this.keepScreenOn=null,this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this.emit("destroy"),this.off(),this.debug.log("play","destroy end")}set fullscreen(e){fe()?(this.emit(T.webFullscreen,e),setTimeout((()=>{this.updateOption({rotate:e?270:0}),this.resize()}),10)):this.emit(T.fullscreen,e)}get fullscreen(){return document.isFullScreen||document.mozIsFullScreen||document.webkitIsFullScreen||this.webFullscreen}set webFullscreen(e){this.emit(T.webFullscreen,e)}get webFullscreen(){return this.$container.classList.contains("jessibuca-fullscreen-web")}set loaded(e){this._hasLoaded=e}get loaded(){return this._hasLoaded}set playing(e){e&&(this.loading=!1),this.playing!==e&&(this._playing=e,this.emit(T.playing,e),this.emit(T.volumechange,this.volume),e?this.emit(T.play):this.emit(T.pause))}get playing(){return this._playing}get volume(){return this.audio&&this.audio.volume||0}set volume(e){e!==this.volume&&(this.audio&&this.audio.setVolume(e),this._lastVolume=e)}get lastVolume(){return this._lastVolume}set loading(e){this.loading!==e&&(this._loading=e,this.emit(T.loading,this._loading))}get loading(){return this._loading}set recording(e){e?this.playing&&this.recorder&&this.recorder.startRecord():this.recorder&&this.recorder.stopRecordAndSave()}get recording(){return!!this.recorder&&this.recorder.recording}set audioTimestamp(e){null!==e&&(this._audioTimestamp=e)}get audioTimestamp(){return this._audioTimestamp}set videoTimestamp(e){null!==e&&(this._videoTimestamp=e,this._opt.useWCS||this._opt.useMSE||this.audioTimestamp&&this.videoTimestamp&&this.audio&&this.audio.emit(T.videoSyncAudio,{audioTimestamp:this.audioTimestamp,videoTimestamp:this.videoTimestamp,diff:this.audioTimestamp-this.videoTimestamp}))}get videoTimestamp(){return this._videoTimestamp}get isDebug(){return!0===this._opt.debug}updateOption(e){this._opt=Object.assign({},this._opt,e)}init(){return new Promise(((e,t)=>{this.stream||(this.stream=new Ve(this)),this.audio||this._opt.hasAudio&&(this.audio=new Le(this)),this.demux||(this.demux=new Ne(this)),this._opt.useWCS&&(this.webcodecsDecoder||(this.webcodecsDecoder=new Xe(this))),this._opt.useMSE&&(this.mseDecoder||(this.mseDecoder=new it(this))),this.decoderWorker||this._onlyMseOrWcsVideo()?e():(this.decoderWorker=new We(this),this.once(T.decoderWorkerInit,(()=>{e()})))}))}play(e,t){return new Promise(((i,o)=>{if(!e&&!this._opt.url)return o();this.loading=!0,this.playing=!1,this._times.playInitStart=le(),e||(e=this._opt.url),this._opt.url=e,this.clearCheckHeartTimeout(),this.init().then((()=>{this._times.playStart=le(),this._opt.isNotMute&&this.mute(!1),this.webcodecsDecoder&&this.webcodecsDecoder.once(x.webcodecsH265NotSupport,(()=>{this.emit(x.webcodecsH265NotSupport),this._opt.autoWasm||this.emit(T.error,x.webcodecsH265NotSupport)})),this.mseDecoder&&(this.mseDecoder.once(x.mediaSourceH265NotSupport,(()=>{this.emit(x.mediaSourceH265NotSupport),this._opt.autoWasm||this.emit(T.error,x.mediaSourceH265NotSupport)})),this.mseDecoder.once(x.mediaSourceFull,(()=>{this.emit(x.mediaSourceFull)})),this.mseDecoder.once(x.mediaSourceAppendBufferError,(()=>{this.emit(x.mediaSourceAppendBufferError)})),this.mseDecoder.once(x.mediaSourceBufferListLarge,(()=>{this.emit(x.mediaSourceBufferListLarge)})),this.mseDecoder.once(x.mediaSourceAppendBufferEndTimeout,(()=>{this.emit(x.mediaSourceAppendBufferEndTimeout)}))),this.enableWakeLock(),this.stream.fetchStream(e,t),this.checkLoadingTimeout(),this.stream.once(x.fetchError,(e=>{o(e)})),this.stream.once(x.websocketError,(e=>{o(e)})),this.stream.once(T.streamEnd,(()=>{o()})),this.stream.once(x.hlsError,(e=>{o(e)})),this.stream.once(T.streamSuccess,(()=>{i(),this._times.streamResponse=le(),this.video.play(),this.checkStatsInterval()}))})).catch((e=>{o(e)}))}))}close(){return new Promise(((e,t)=>{this._close().then((()=>{this.video&&this.video.clearView(),e()}))}))}resumeAudioAfterPause(){this.lastVolume&&(this.volume=this.lastVolume)}_close(){return new Promise(((e,t)=>{this.stream&&(this.stream.destroy(),this.stream=null),this.demux&&(this.demux.destroy(),this.demux=null),this.decoderWorker&&(this.decoderWorker.destroy(),this.decoderWorker=null),this.webcodecsDecoder&&(this.webcodecsDecoder.destroy(),this.webcodecsDecoder=null),this.mseDecoder&&(this.mseDecoder.destroy(),this.mseDecoder=null),this.audio&&(this.audio.destroy(),this.audio=null),this.clearCheckHeartTimeout(),this.clearCheckLoadingTimeout(),this.clearStatsInterval(),this.playing=!1,this.loading=!1,this.recording=!1,this.video&&(this.video.resetInit(),this.video.pause(!0)),this.releaseWakeLock(),this.resetStats(),this._audioTimestamp=0,this._videoTimestamp=0,this._times={playInitStart:"",playStart:"",streamStart:"",streamResponse:"",demuxStart:"",decodeStart:"",videoStart:"",playTimestamp:"",streamTimestamp:"",streamResponseTimestamp:"",demuxTimestamp:"",decodeTimestamp:"",videoTimestamp:"",allTimestamp:""},setTimeout((()=>{e()}),0)}))}pause(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]?this.close():this._close()}mute(e){this.audio&&this.audio.mute(e)}resize(){this.video.resize()}startRecord(e,t){this.recording||(this.recorder.setFileName(e,t),this.recording=!0)}stopRecordAndSave(){this.recording&&(this.recording=!1)}_hasControl(){let e=!1,t=!1;return Object.keys(this._opt.operateBtns).forEach((e=>{this._opt.operateBtns[e]&&(t=!0)})),(this._opt.showBandwidth||this._opt.text||t)&&(e=!0),e}_onlyMseOrWcsVideo(){return!1===this._opt.hasAudio&&(this._opt.useMSE||this._opt.useWCS&&!this._opt.useOffscreen)}checkHeart(){this.clearCheckHeartTimeout(),this.checkHeartTimeout()}checkHeartTimeout(){this._checkHeartTimeout=setTimeout((()=>{this.pause().then((()=>{this.emit(T.timeout,T.delayTimeout),this.emit(T.delayTimeout)}))}),1e3*this._opt.heartTimeout)}checkStatsInterval(){this._checkStatsInterval=setInterval((()=>{this.updateStats()}),1e3)}clearCheckHeartTimeout(){this._checkHeartTimeout&&(clearTimeout(this._checkHeartTimeout),this._checkHeartTimeout=null)}checkLoadingTimeout(){this._checkLoadingTimeout=setTimeout((()=>{this.pause().then((()=>{this.emit(T.timeout,T.loadingTimeout),this.emit(T.loadingTimeout)}))}),1e3*this._opt.loadingTimeout)}clearCheckLoadingTimeout(){this._checkLoadingTimeout&&(clearTimeout(this._checkLoadingTimeout),this._checkLoadingTimeout=null)}clearStatsInterval(){this._checkStatsInterval&&(clearInterval(this._checkStatsInterval),this._checkStatsInterval=null)}handleRender(){this.loading&&(this.emit(T.start),this.loading=!1,this.clearCheckLoadingTimeout()),this.playing||(this.playing=!0),this.checkHeart()}updateStats(e){e=e||{},this._startBpsTime||(this._startBpsTime=le()),ve(e.ts)&&(this._stats.ts=e.ts),ve(e.buf)&&(this._stats.buf=e.buf),e.fps&&(this._stats.fps+=1),e.abps&&(this._stats.abps+=e.abps),e.vbps&&(this._stats.vbps+=e.vbps);const t=le();t-this._startBpsTime<1e3||(this.emit(T.stats,this._stats),this.emit(T.performance,function(e){let t=0;return e>=24?t=2:e>=15&&(t=1),t}(this._stats.fps)),this._stats.fps=0,this._stats.abps=0,this._stats.vbps=0,this._startBpsTime=t)}resetStats(){this._startBpsTime=null,this._stats={buf:0,fps:0,abps:0,vbps:0,ts:0}}enableWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.enable()}releaseWakeLock(){this._opt.keepScreenOn&&this.keepScreenOn&&this.keepScreenOn.disable()}handlePlayToRenderTimes(){const e=this._times;e.playTimestamp=e.playStart-e.playInitStart,e.streamTimestamp=e.streamStart-e.playStart,e.streamResponseTimestamp=e.streamResponse-e.streamStart,e.demuxTimestamp=e.demuxStart-e.streamResponse,e.decodeTimestamp=e.decodeStart-e.demuxStart,e.videoTimestamp=e.videoStart-e.decodeStart,e.allTimestamp=e.videoStart-e.playInitStart,this.emit(T.playToRenderTimes,e)}getOption(){return this._opt}}class nt extends we{constructor(e){super();let t=e,i=e.container;if("string"==typeof e.container&&(i=document.querySelector(e.container)),!i)throw new Error("Jessibuca need container option");if("CANVAS"===i.nodeName||"VIDEO"===i.nodeName)throw new Error(`Jessibuca container type can not be ${i.nodeName} type`);i.classList.add("jessibuca-container"),delete t.container,ve(t.videoBuffer)&&(t.videoBuffer=1e3*Number(t.videoBuffer)),ve(t.timeout)&&(ye(t.loadingTimeout)&&(t.loadingTimeout=t.timeout),ye(t.heartTimeout)&&(t.heartTimeout=t.timeout)),this._opt=t,this.$container=i,this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0,this.events=new ne(this),this._initPlayer(i,t)}destroy(){this.events&&(this.events.destroy(),this.events=null),this.player&&(this.player.destroy(),this.player=null),this.$container=null,this._opt=null,this._loadingTimeoutReplayTimes=0,this._heartTimeoutReplayTimes=0,this.off()}_initPlayer(e,t){this.player=new at(e,t),this.player.debug.log("jessibuca","_initPlayer",this.player.getOption()),this._bindEvents()}_resetPlayer(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.player.destroy(),this.player=null,this._opt=Object.assign(this._opt,e),this._opt.url="",this._initPlayer(this.$container,this._opt)}_bindEvents(){Object.keys(I).forEach((e=>{this.player.on(I[e],(t=>{this.emit(e,t)}))}))}setDebug(e){this.player.updateOption({debug:!!e})}mute(){this.player.mute(!0)}cancelMute(){this.player.mute(!1)}setVolume(e){this.player.volume=e}audioResume(){this.player.audio&&this.player.audio.audioEnabled(!0)}setTimeout(e){e=Number(e),this.player.updateOption({timeout:e,loadingTimeout:e,heartTimeout:e})}setScaleMode(e){let t={isFullResize:!1,isResize:!1};switch(e=Number(e)){case W:t.isFullResize=!1,t.isResize=!1;break;case J:t.isFullResize=!1,t.isResize=!0;break;case G:t.isFullResize=!0,t.isResize=!0}this.player.updateOption(t),this.resize()}pause(){return this.player.pause()}close(){return this._opt.url="",this._opt.playOptions={},this.player.close()}clearView(){this.player.video.clearView()}play(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(((i,o)=>{if(!e&&!this._opt.url)return this.emit(T.error,x.playError),void o();if(e){if(!this._opt.url)return this._play(e,t);e===this._opt.url?this.player.playing?i():(this.clearView(),this.player.play(this._opt.url,this._opt.playOptions).then((()=>{i(),this.player.resumeAudioAfterPause()})).catch((()=>{this.player.pause().then((()=>{o()}))}))):this.player.pause().then((()=>(this.clearView(),this._play(e,t)))).catch((()=>{o()}))}else this.player.play(this._opt.url,this._opt.playOptions).then((()=>{i(),this.player.resumeAudioAfterPause()})).catch((()=>{this.player.pause().then((()=>{o()}))}))}))}_play(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(((i,o)=>{this._opt.url=e,this._opt.playOptions=t;const r=0===e.indexOf("http"),d=r?a:s,c=r||-1!==e.indexOf(".flv")||this._opt.isFlv?n:A;this.player.updateOption({protocol:d,demuxType:c}),this.player.once(x.webglAlignmentError,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","webglAlignmentError"),this._resetPlayer({openWebglAlignment:!0}),this.play(e).then((()=>{this.player.debug.log("Jessibuca","webglAlignmentError and play success")})).catch((()=>{this.player.debug.log("Jessibuca","webglAlignmentError and play error")}))}))})),this.player.once(x.mediaSourceH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.player.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play"),this._resetPlayer({useMSE:!1}),this.play(e,t).then((()=>{this.player.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play success")})).catch((()=>{this.player.debug.log("Jessibuca","auto wasm [mse-> wasm] reset player and play error")})))}))})),this.player.once(x.webcodecsH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.player.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.player.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play success")})).catch((()=>{this.player.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play error")})))}))})),this.player.once(x.mediaSourceFull,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","media source full"),this._resetPlayer(),this.play(e).then((()=>{this.player.debug.log("Jessibuca","media source full and reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","media source full and reset player and play error")}))}))})),this.player.once(x.mediaSourceAppendBufferError,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","media source append buffer error"),this._resetPlayer(),this.play(e).then((()=>{this.player.debug.log("Jessibuca","media source append buffer error and reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","media source append buffer error and reset player and play error")}))}))})),this.player.once(x.mediaSourceBufferListLarge,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","media source buffer list large"),this._resetPlayer(),this.play(e).then((()=>{this.player.debug.log("Jessibuca","media source buffer list large and reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","media source buffer list large and reset player and play error")}))}))})),this.player.once(x.mediaSourceAppendBufferEndTimeout,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","media source append buffer end timeout"),this._resetPlayer(),this.play(e).then((()=>{this.player.debug.log("Jessibuca","media source append buffer end timeout and reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","media source append buffer end timeout and reset player and play error")}))}))})),this.player.once(x.mseSourceBufferError,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","mseSourceBufferError close success")}))})),this.player.once(x.webcodecsH265NotSupport,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.player.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e).then((()=>{this.player.debug.log("Jessibuca","auto wasm [wcs-> wasm] reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","auto wasm [wcs-> wasm] reset player and play error")})))}))})),this.player.once(x.webcodecsWidthOrHeightChange,(()=>{this.pause().then((()=>{this.player.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play"),this._resetPlayer({useWCS:!0}),this.play(e).then((()=>{this.player.debug.log("Jessibuca","webcodecs Width Or Height Change reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","webcodecs Width Or Height Change reset player and play error")}))}))})),this.player.once(x.webcodecsDecodeError,(()=>{this.pause().then((()=>{this.player._opt.autoWasm&&(this.player.debug.log("Jessibuca","webcodecs decode error reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e).then((()=>{this.player.debug.log("Jessibuca","webcodecs decode error reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","webcodecs decode error reset player and play error")})))}))})),this.player.once(x.wasmDecodeError,(()=>{this.player._opt.wasmDecodeErrorReplay&&this.pause().then((()=>{this.player.debug.log("Jessibuca","wasm decode error and reset player and play"),this._resetPlayer({useWCS:!1}),this.play(e,t).then((()=>{this.player.debug.log("Jessibuca","wasm decode error and reset player and play success")})).catch((()=>{this.player.debug.warn("Jessibuca","wasm decode error and reset player and play error")}))}))})),this.player.on(T.delayTimeout,(()=>{this.player._opt.heartTimeoutReplay&&(this._heartTimeoutReplayTimes<this.player._opt.heartTimeoutReplayTimes||-1===this.player._opt.heartTimeoutReplayTimes)&&(this.player.debug.log("Jessibuca",`delay timeout replay time is ${this._heartTimeoutReplayTimes}`),this._heartTimeoutReplayTimes+=1,this.play(e,t).then((()=>{this._heartTimeoutReplayTimes=0})).catch((()=>{})))})),this.player.on(T.loadingTimeout,(()=>{this.player._opt.loadingTimeoutReplay&&(this._loadingTimeoutReplayTimes<this.player._opt.loadingTimeoutReplayTimes||-1===this.player._opt.loadingTimeoutReplayTimes)&&(this.player.debug.log("Jessibuca",`loading timeout replay time is ${this._loadingTimeoutReplayTimes}`),this._loadingTimeoutReplayTimes+=1,this.play(e,t).then((()=>{this._loadingTimeoutReplayTimes=0})).catch((()=>{})))})),this.hasLoaded()?this.player.play(e,t).then((()=>{i()})).catch((()=>{this.player.pause().then((()=>{o()}))})):this.player.once(T.decoderWorkerInit,(()=>{this.player.play(e,t).then((()=>{i()})).catch((()=>{this.player.pause().then((()=>{o()}))}))}))}))}resize(){this.player.resize()}setBufferTime(e){e=Number(e),this.player.updateOption({videoBuffer:1e3*e}),this.player.decoderWorker&&this.player.decoderWorker.updateWorkConfig({key:"videoBuffer",value:1e3*e})}setRotate(e){e=parseInt(e,10);this._opt.rotate!==e&&-1!==[0,90,180,270].indexOf(e)&&(this.player.updateOption({rotate:e}),this.resize())}hasLoaded(){return this.player.loaded}setKeepScreenOn(){this.player.updateOption({keepScreenOn:!0})}setFullscreen(e){const t=!!e;this.player.fullscreen!==t&&(this.player.fullscreen=t)}screenshot(e,t,i,o){return this.player.video.screenshot(e,t,i,o)}startRecord(e,t){return new Promise(((i,o)=>{this.player.playing?(this.player.startRecord(e,t),i()):o()}))}stopRecordAndSave(){this.player.recording&&this.player.stopRecordAndSave()}isPlaying(){return!!this.player&&this.player.playing}isMute(){return!this.player.audio||this.player.audio.isMute}isRecording(){return this.player.recorder.recording}}return r(nt,"ERROR",x),r(nt,"TIMEOUT",{loadingTimeout:T.loadingTimeout,delayTimeout:T.delayTimeout}),window.Jessibuca=nt,nt})); | ... | ... |