hy666 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.html +1400 -0
  2. package/package.json +18 -0
package/index.html ADDED
@@ -0,0 +1,1400 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no">
6
+ <title>TIZEN-CAST 手动投屏</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ font-family: system-ui, -apple-system, "Microsoft YaHei", sans-serif;
13
+ }
14
+ html, body {
15
+ width: 100%;
16
+ height: 100%;
17
+ overflow: hidden;
18
+ background: #0B0C10;
19
+ color: #fff;
20
+ }
21
+ .app {
22
+ display: flex;
23
+ flex-direction: column;
24
+ height: 100vh;
25
+ }
26
+ .header {
27
+ padding: 12px 16px;
28
+ background: #1F2833;
29
+ border-bottom: 2px solid #66FCF1;
30
+ text-align: center;
31
+ }
32
+ .title {
33
+ font-size: clamp(22px, 5vw, 48px);
34
+ color: #66FCF1;
35
+ }
36
+ .title-bar {
37
+ padding: 8px 16px;
38
+ background: #1F2833;
39
+ color: #FFB30F;
40
+ font-size: 18px;
41
+ text-align: center;
42
+ white-space: nowrap;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ margin-bottom: 10px;
46
+ }
47
+ .device-list-container {
48
+ background: #252c38;
49
+ padding: 14px;
50
+ border-radius: 12px;
51
+ box-shadow: 0 0 12px rgba(0,0,0,0.4), 0 0 0 1px rgba(102, 252, 241, 0.2);
52
+ margin-top: 20px;
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: center;
56
+ justify-content: center;
57
+ min-height: 80px;
58
+ }
59
+ .device-list {
60
+ display: flex;
61
+ gap: 10px;
62
+ flex-wrap: wrap;
63
+ margin-top: 10px;
64
+ justify-content: center;
65
+ width: 100%;
66
+ }
67
+ .device-item {
68
+ padding: 10px 14px;
69
+ background: #252c38;
70
+ color: #66FCF1;
71
+ border-radius: 8px;
72
+ font-size: 16px;
73
+ border: 1px solid #66FCF1;
74
+ }
75
+ .device-item:focus {
76
+ outline: 2px solid #FFB30F;
77
+ background: #38A3A5;
78
+ color: #000;
79
+ }
80
+ .device-item.active {
81
+ background: #FFB30F;
82
+ color: #000;
83
+ font-weight: bold;
84
+ }
85
+ .tip-bottom {
86
+ text-align: center;
87
+ color: #888;
88
+ font-size: clamp(12px, 2.5vw, 16px);
89
+ margin-top: 8px;
90
+ }
91
+ .content {
92
+ flex: 1;
93
+ display: flex;
94
+ }
95
+ @media (max-width: 768px) {
96
+ .content { flex-direction: column; }
97
+ }
98
+ .sidebar {
99
+ width: clamp(260px, 30vw, 400px);
100
+ background: #1F2833;
101
+ padding: 16px;
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: 20px;
105
+ }
106
+ @media (max-width: 768px) {
107
+ .sidebar { width: 100%; height: auto; }
108
+ }
109
+ .module-container {
110
+ outline: 2px solid transparent;
111
+ border-radius: 12px;
112
+ transition: outline 0.2s;
113
+ }
114
+ .module-container:focus {
115
+ outline: 2px solid #FFB30F;
116
+ background: rgba(56, 163, 165, 0.1);
117
+ }
118
+ .ip-config {
119
+ padding: 14px;
120
+ background: #252c38;
121
+ border-radius: 12px;
122
+ box-shadow: 0 0 12px rgba(0,0,0,0.4), 0 0 0 1px rgba(102, 252, 241, 0.2);
123
+ display: flex;
124
+ flex-direction: column;
125
+ align-items: center;
126
+ }
127
+ .ip-config h2 {
128
+ margin-bottom: 10px;
129
+ text-align: center;
130
+ }
131
+ .ip-config input {
132
+ padding: 8px 10px;
133
+ border: none;
134
+ border-radius: 4px;
135
+ background: #292C35;
136
+ color: #fff;
137
+ font-size: clamp(12px, 2.5vw, 16px);
138
+ outline: none;
139
+ }
140
+ .ip-config input:focus {
141
+ outline: 2px solid #FFB30F;
142
+ }
143
+ .full-ip-group {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 8px;
147
+ margin-bottom: 12px;
148
+ width: 100%;
149
+ }
150
+ .full-ip-input {
151
+ flex: 1;
152
+ text-align: center;
153
+ }
154
+ .port-group {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ width: 100%;
159
+ }
160
+ .port-input {
161
+ width: 100px;
162
+ text-align: center;
163
+ }
164
+ .connect {
165
+ padding: 10px;
166
+ border-radius: 8px;
167
+ background: #38A3A5;
168
+ color: #fff;
169
+ border: none;
170
+ width: 100%;
171
+ }
172
+ .connect:focus {
173
+ outline: 3px solid #FFB30F;
174
+ }
175
+ .playlist {
176
+ display: none;
177
+ flex: 1;
178
+ flex-direction: column;
179
+ gap: 10px;
180
+ }
181
+ .eps {
182
+ display: grid;
183
+ grid-template-columns: repeat(auto-fill, minmax(55px, 1fr));
184
+ gap: 8px;
185
+ flex: 1;
186
+ max-height: 320px;
187
+ overflow-y: auto;
188
+ padding-right: 4px;
189
+ }
190
+ .ep {
191
+ aspect-ratio: 1/1;
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: center;
195
+ background: #292C35;
196
+ color: #66FCF1;
197
+ border-radius: 8px;
198
+ font-size: clamp(12px, 3vw, 16px);
199
+ }
200
+ .ep:focus {
201
+ outline: 2px solid #FFB30F;
202
+ background: #45A29E;
203
+ color: #000;
204
+ }
205
+ .ep.active {
206
+ background: #FFB30F !important;
207
+ color: #000 !important;
208
+ font-weight: bold;
209
+ }
210
+ .player {
211
+ flex: 1;
212
+ display: flex;
213
+ flex-direction: column;
214
+ background: #000;
215
+ }
216
+ .video-box {
217
+ flex: 1;
218
+ position: relative;
219
+ background: #000;
220
+ }
221
+ .video-wrapper {
222
+ position: absolute;
223
+ top: 0;
224
+ left: 0;
225
+ width: 100%;
226
+ height: 100%;
227
+ display: flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ }
231
+ video {
232
+ background: #000;
233
+ display: none;
234
+ max-width: 100%;
235
+ max-height: 100%;
236
+ }
237
+ .empty {
238
+ position: absolute;
239
+ inset: 0;
240
+ display: flex;
241
+ flex-direction: column;
242
+ align-items: center;
243
+ justify-content: center;
244
+ color: #66FCF1;
245
+ gap: 16px;
246
+ font-size: clamp(16px, 4vw, 26px);
247
+ z-index: 5;
248
+ }
249
+ .empty span {
250
+ font-size: clamp(40px, 10vw, 80px);
251
+ }
252
+ .progress-bar {
253
+ position: absolute;
254
+ bottom: 120px;
255
+ left: 0;
256
+ transform: none;
257
+ width: 100%;
258
+ height: 12px;
259
+ background: rgba(255,255,255,0.2);
260
+ border-radius: 6px;
261
+ cursor: pointer;
262
+ z-index: 10;
263
+ opacity: 0;
264
+ pointer-events: none;
265
+ transition: opacity 0.5s ease, bottom 0.5s ease;
266
+ }
267
+ .progress-fill {
268
+ height: 100%;
269
+ background: #FF4500;
270
+ border-radius: 5px;
271
+ width: 0%;
272
+ transition: width 0.1s linear;
273
+ box-shadow: 0 0 8px rgba(255, 69, 0, 0.8);
274
+ }
275
+ .progress-time {
276
+ position: absolute;
277
+ bottom: 100px;
278
+ left: 20px;
279
+ transform: none;
280
+ display: flex;
281
+ gap: 8px;
282
+ font-size: clamp(14px, 2.5vw, 18px);
283
+ color: #FFFFFF;
284
+ font-weight: bold;
285
+ text-shadow: 0 0 4px #000;
286
+ z-index: 10;
287
+ opacity: 0;
288
+ pointer-events: none;
289
+ transition: opacity 0.5s ease, bottom 0.5s ease;
290
+ }
291
+ .video-box.fullscreen .controls {
292
+ opacity: 0;
293
+ bottom: -100px;
294
+ pointer-events: none;
295
+ transition: opacity 0.5s ease, bottom 0.5s ease;
296
+ }
297
+ .video-box.show-progress .progress-bar,
298
+ .video-box.show-progress .progress-time {
299
+ opacity: 1;
300
+ pointer-events: all;
301
+ }
302
+ .video-box.show-progress .progress-bar {
303
+ bottom: 120px;
304
+ }
305
+ .video-box.show-progress .progress-time {
306
+ bottom: 100px;
307
+ }
308
+ .controls {
309
+ position: absolute;
310
+ bottom: 20px;
311
+ left: 50%;
312
+ transform: translateX(-50%);
313
+ display: flex;
314
+ gap: 25px;
315
+ z-index: 10;
316
+ opacity: 1;
317
+ transition: opacity 0.5s ease, bottom 0.5s ease;
318
+ }
319
+ .ctrl {
320
+ width: clamp(50px, 10vw, 70px);
321
+ height: clamp(50px, 10vw, 70px);
322
+ border-radius: 50%;
323
+ background: rgba(0,0,0,0.7);
324
+ color: #66FCF1;
325
+ border: 2px solid #66FCF1;
326
+ font-size: clamp(16px, 5vw, 24px);
327
+ display: flex;
328
+ align-items: center;
329
+ justify-content: center;
330
+ }
331
+ .ctrl:focus {
332
+ border-color: #FFB30F;
333
+ color: #FFB30F;
334
+ outline: none;
335
+ box-shadow: 0 0 10px rgba(255, 179, 15, 0.8);
336
+ }
337
+ .toast {
338
+ position: absolute;
339
+ top: 50%;
340
+ left: 50%;
341
+ transform: translate(-50%, -50%);
342
+ background: rgba(0,0,0,0.8);
343
+ color: #66FCF1;
344
+ padding: 12px 24px;
345
+ border-radius: 8px;
346
+ font-size: 18px;
347
+ z-index: 9999;
348
+ opacity: 0;
349
+ transition: opacity 0.3s;
350
+ pointer-events: none;
351
+ }
352
+ .toast.show {
353
+ opacity: 1;
354
+ }
355
+ ::-webkit-scrollbar {
356
+ width: 6px;
357
+ }
358
+ ::-webkit-scrollbar-thumb {
359
+ background: #66FCF1;
360
+ border-radius: 3px;
361
+ }
362
+ .video-box:fullscreen,
363
+ .video-box:-webkit-full-screen,
364
+ .video-box:-moz-full-screen {
365
+ width: 100vw !important;
366
+ height: 100vh !important;
367
+ position: fixed !important;
368
+ top: 0 !important;
369
+ left: 0 !important;
370
+ }
371
+ .video-box:fullscreen .video-wrapper,
372
+ .video-box:-webkit-full-screen .video-wrapper,
373
+ .video-box:-moz-full-screen .video-wrapper {
374
+ width: 100% !important;
375
+ height: 100% !important;
376
+ display: flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ }
380
+ </style>
381
+ </head>
382
+ <body>
383
+ <div class="app">
384
+ <div class="header">
385
+ <h1 class="title">TIZEN-CAST 手动投屏</h1>
386
+ </div>
387
+ <div class="content">
388
+ <div class="sidebar">
389
+ <div class="ip-config module-container" id="ipConfigModule" tabindex="0">
390
+ <h2 class="sub-title">设备连接配置</h2>
391
+ <div class="full-ip-group">
392
+ <span style="color: #66FCF1;">IP:</span>
393
+ <input type="text" id="fullIpInput" class="full-ip-input" placeholder="输入设备IP地址" value="192.168.1.100" tabindex="-1">
394
+ </div>
395
+ <div class="port-group">
396
+ <span style="color: #66FCF1;">端口:</span>
397
+ <input type="text" id="portInput" class="port-input" placeholder="52020" value="52020" tabindex="-1">
398
+ </div>
399
+ </div>
400
+ <button id="connectBtn" class="connect module-container" tabindex="0">连接设备</button>
401
+ <div class="device-list-container module-container" id="deviceListModule" tabindex="0">
402
+ <div class="tip-bottom" id="deviceTip">未连接设备</div>
403
+ <div class="device-list" id="deviceList"></div>
404
+ </div>
405
+ <div class="playlist module-container" id="playlistModule" tabindex="0">
406
+ <div class="title-bar" id="titleBar">当前:未播放</div>
407
+ <h2 class="sub-title">剧集列表</h2>
408
+ <div id="eps" class="eps"></div>
409
+ </div>
410
+ </div>
411
+ <div class="player">
412
+ <div class="video-box" id="videoBox">
413
+ <div class="empty" id="empty">
414
+ <span>📺</span>
415
+ <div>输入IP连接设备即可投屏</div>
416
+ </div>
417
+ <div class="video-wrapper" id="videoWrapper">
418
+ <video id="video" crossorigin="anonymous"></video>
419
+ </div>
420
+ <div class="progress-bar" id="progressBar" tabindex="0">
421
+ <div class="progress-fill" id="progressFill"></div>
422
+ </div>
423
+ <div class="progress-time" id="progressTime">
424
+ <span id="currentTime">00:00</span>
425
+ <span>/</span>
426
+ <span id="totalTime">00:00</span>
427
+ </div>
428
+ <div class="controls">
429
+ <button class="ctrl" id="back10" tabindex="0">◀◀</button>
430
+ <button class="ctrl" id="play" tabindex="0">▶</button>
431
+ <button class="ctrl" id="for10" tabindex="0">▶▶</button>
432
+ <button class="ctrl" id="ratioToggle" tabindex="0">16:9</button>
433
+ <button class="ctrl" id="fs" tabindex="0">⛶</button>
434
+ </div>
435
+ <div class="toast" id="toast"></div>
436
+ </div>
437
+ </div>
438
+ </div>
439
+ </div>
440
+
441
+ <script>
442
+ // ES5 兼容的工具函数:替代 Array.from
443
+ function arrayFrom(arr) {
444
+ var result = [];
445
+ for (var i = 0; i < arr.length; i++) {
446
+ result.push(arr[i]);
447
+ }
448
+ return result;
449
+ }
450
+
451
+ // ES5 兼容的工具函数:替代 String.prototype.padStart
452
+ function padStart(str, length, padChar) {
453
+ str = String(str);
454
+ padChar = padChar || '0';
455
+ while (str.length < length) {
456
+ str = padChar + str;
457
+ }
458
+ return str;
459
+ }
460
+
461
+ document.addEventListener('DOMContentLoaded', function() {
462
+ var CONFIG = {
463
+ PORT: '52020',
464
+ AUTO_CONNECT: true,
465
+ SAVE_DEVICE: true,
466
+ REFRESH_INTERVAL: 2000,
467
+ PROGRESS_HIDE_DELAY: 5000
468
+ };
469
+
470
+ var state = {
471
+ devices: [],
472
+ server: null,
473
+ playlist: [],
474
+ ratioMode: 0,
475
+ lastDevice: localStorage.getItem('last_device'),
476
+ currentEp: 0,
477
+ refreshTimer: null,
478
+ lastPlayUrl: '',
479
+ currentVideoTitle: '未命名视频',
480
+ focusState: {
481
+ current: 0,
482
+ lastGlobalFocus: null
483
+ },
484
+ progressHideTimer: null,
485
+ isLoadingPlayUrl: false,
486
+ isLoadingPlayList: false
487
+ };
488
+
489
+ var el = {
490
+ ipConfigModule: document.getElementById('ipConfigModule'),
491
+ deviceListModule: document.getElementById('deviceListModule'),
492
+ playlistModule: document.getElementById('playlistModule'),
493
+ connectBtn: document.getElementById('connectBtn'),
494
+ fullIpInput: document.getElementById('fullIpInput'),
495
+ titleBar: document.getElementById('titleBar'),
496
+ deviceTip: document.getElementById('deviceTip'),
497
+ deviceList: document.getElementById('deviceList'),
498
+ empty: document.getElementById('empty'),
499
+ video: document.getElementById('video'),
500
+ videoWrapper: document.getElementById('videoWrapper'),
501
+ videoBox: document.getElementById('videoBox'),
502
+ eps: document.getElementById('eps'),
503
+ back10: document.getElementById('back10'),
504
+ play: document.getElementById('play'),
505
+ for10: document.getElementById('for10'),
506
+ ratioToggle: document.getElementById('ratioToggle'),
507
+ fs: document.getElementById('fs'),
508
+ portInput: document.getElementById('portInput'),
509
+ toast: document.getElementById('toast'),
510
+ progressBar: document.getElementById('progressBar'),
511
+ progressFill: document.getElementById('progressFill'),
512
+ progressTime: document.getElementById('progressTime'),
513
+ currentTime: document.getElementById('currentTime'),
514
+ totalTime: document.getElementById('totalTime')
515
+ };
516
+
517
+ // 修正aspectRatio格式,移除空格(CSS不支持'16 / 9',需改为'16/9')
518
+ var ratioList = [
519
+ { key: '16:9', text: '16:9', ratio: '16/9' },
520
+ { key: '4:3', text: '4:3', ratio: '4/3' },
521
+ { key: '21:9', text: '21:9', ratio: '21/9' },
522
+ { key: '1.85:1',text: '1.85:1',ratio: '1.85/1' }
523
+ ];
524
+
525
+ var globalFocusElements = [
526
+ el.ipConfigModule,
527
+ el.connectBtn,
528
+ el.deviceListModule,
529
+ el.playlistModule,
530
+ el.back10,
531
+ el.play,
532
+ el.for10,
533
+ el.ratioToggle,
534
+ el.fs,
535
+ el.progressBar
536
+ ];
537
+
538
+ var ipConfigInnerElements = [
539
+ el.fullIpInput,
540
+ el.portInput
541
+ ];
542
+
543
+ // 工具函数 - 提示框
544
+ var toastTimer = null;
545
+ function showToast(text, duration) {
546
+ duration = duration || 2000;
547
+
548
+ if (text.indexOf('加载地址失败') !== -1 || text.indexOf('获取播放地址失败') !== -1) {
549
+ if (el.video.currentTime > 0) return;
550
+ if (state.lastPlayUrl !== '' && el.video.currentTime === 0) return;
551
+ }
552
+
553
+ clearTimeout(toastTimer);
554
+ el.toast.textContent = text;
555
+ el.toast.classList.add('show');
556
+ toastTimer = setTimeout(function() {
557
+ el.toast.classList.remove('show');
558
+ }, duration);
559
+ }
560
+
561
+ // 验证IP地址格式
562
+ function validateIpAddress(ip) {
563
+ var ipRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
564
+ return ipRegex.test(ip);
565
+ }
566
+
567
+ function validateNumberInput(value, min, max) {
568
+ var num = parseInt(value, 10);
569
+ return isNaN(num) ? min : Math.max(min, Math.min(max, num));
570
+ }
571
+
572
+ function formatTime(seconds) {
573
+ if (isNaN(seconds) || seconds === Infinity) return '00:00';
574
+ var mins = Math.floor(seconds / 60);
575
+ var secs = Math.floor(seconds % 60);
576
+ return padStart(mins.toString(), 2, '0') + ':' + padStart(secs.toString(), 2, '0');
577
+ }
578
+
579
+ function updateProgress() {
580
+ if (!el.video.duration) return;
581
+ var progress = (el.video.currentTime / el.video.duration) * 100;
582
+ el.progressFill.style.width = progress + '%';
583
+ el.currentTime.textContent = formatTime(el.video.currentTime);
584
+ el.totalTime.textContent = formatTime(el.video.duration);
585
+ }
586
+
587
+ // 进度条控制
588
+ function resetProgressHideTimer() {
589
+ var isFull = !!document.fullscreenElement;
590
+ if (!isFull) return;
591
+
592
+ clearTimeout(state.progressHideTimer);
593
+ el.videoBox.classList.add('show-progress');
594
+
595
+ state.progressHideTimer = setTimeout(function() {
596
+ el.videoBox.classList.remove('show-progress');
597
+ }, CONFIG.PROGRESS_HIDE_DELAY);
598
+ }
599
+
600
+ // 按横杠“-”后面的内容去匹配 playlist
601
+ function highlightEpByTitle(playTitle) {
602
+ if (!playTitle || state.playlist.length === 0) return;
603
+
604
+ // 1. 把标题按横杠切开
605
+ var parts = playTitle.split('-');
606
+
607
+ // 2. 取最后一段(最保险)
608
+ var key = parts[parts.length - 1].trim();
609
+
610
+ // 3. 去 playlist 里找 包含 这段文字的
611
+ var matchIndex = -1;
612
+ for (var i = 0; i < state.playlist.length; i++) {
613
+ var itemTrim = state.playlist[i].trim();
614
+ if (itemTrim.indexOf(key) !== -1) {
615
+ matchIndex = i;
616
+ break;
617
+ }
618
+ }
619
+
620
+ // 4. 找到了就高亮
621
+ if (matchIndex !== -1) {
622
+ state.currentEp = matchIndex;
623
+ var epElements = document.querySelectorAll('.ep');
624
+ for (var j = 0; j < epElements.length; j++) {
625
+ if (j === matchIndex) {
626
+ epElements[j].classList.add('active');
627
+ } else {
628
+ epElements[j].classList.remove('active');
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ // 焦点管理
635
+ function switchToGlobalFocus(activeElement) {
636
+ state.focusState.current = 0;
637
+ for (var i = 0; i < ipConfigInnerElements.length; i++) {
638
+ ipConfigInnerElements[i].tabIndex = -1;
639
+ }
640
+
641
+ var deviceItems = document.querySelectorAll('.device-item');
642
+ for (var j = 0; j < deviceItems.length; j++) {
643
+ deviceItems[j].tabIndex = -1;
644
+ }
645
+
646
+ var epsItems = document.querySelectorAll('.ep');
647
+ for (var k = 0; k < epsItems.length; k++) {
648
+ epsItems[k].tabIndex = -1;
649
+ }
650
+
651
+ for (var l = 0; l < globalFocusElements.length; l++) {
652
+ globalFocusElements[l].tabIndex = 0;
653
+ }
654
+
655
+ var target = activeElement || state.focusState.lastGlobalFocus || el.fs;
656
+ if (target && typeof target.focus === 'function') {
657
+ target.focus();
658
+ state.focusState.lastGlobalFocus = target;
659
+ } else {
660
+ el.fs.focus();
661
+ state.focusState.lastGlobalFocus = el.fs;
662
+ }
663
+ }
664
+
665
+ function enterModuleFocus(moduleType) {
666
+ state.focusState.lastGlobalFocus = document.activeElement;
667
+ for (var i = 0; i < globalFocusElements.length; i++) {
668
+ globalFocusElements[i].tabIndex = -1;
669
+ }
670
+
671
+ switch (moduleType) {
672
+ case 1:
673
+ state.focusState.current = 1;
674
+ for (var j = 0; j < ipConfigInnerElements.length; j++) {
675
+ ipConfigInnerElements[j].tabIndex = 0;
676
+ }
677
+ el.fullIpInput.focus();
678
+ break;
679
+ case 2:
680
+ state.focusState.current = 2;
681
+ var deviceItems = arrayFrom(document.querySelectorAll('.device-item'));
682
+ if (deviceItems.length) {
683
+ for (var k = 0; k < deviceItems.length; k++) {
684
+ deviceItems[k].tabIndex = 0;
685
+ }
686
+ deviceItems[0].focus();
687
+ } else {
688
+ switchToGlobalFocus(el.deviceListModule);
689
+ }
690
+ break;
691
+ case 3:
692
+ state.focusState.current = 3;
693
+ var epsItems = arrayFrom(document.querySelectorAll('.ep'));
694
+ if (epsItems.length) {
695
+ for (var m = 0; m < epsItems.length; m++) {
696
+ epsItems[m].tabIndex = 0;
697
+ }
698
+ epsItems[0].focus();
699
+ } else {
700
+ switchToGlobalFocus(el.playlistModule);
701
+ }
702
+ break;
703
+ }
704
+ }
705
+
706
+ function handleInnerFocusCycle(e, innerElements) {
707
+ var current = document.activeElement;
708
+ var idx = -1;
709
+ for (var i = 0; i < innerElements.length; i++) {
710
+ if (innerElements[i] === current) {
711
+ idx = i;
712
+ break;
713
+ }
714
+ }
715
+ if (idx < 0) return true;
716
+
717
+ if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
718
+ e.preventDefault();
719
+ innerElements[(idx - 1 + innerElements.length) % innerElements.length].focus();
720
+ return false;
721
+ }
722
+ if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
723
+ e.preventDefault();
724
+ innerElements[(idx + 1) % innerElements.length].focus();
725
+ return false;
726
+ }
727
+ return true;
728
+ }
729
+
730
+ // 键盘事件
731
+ document.addEventListener('keydown', function(e) {
732
+ var isFull = !!document.fullscreenElement;
733
+
734
+ if (isFull) {
735
+ resetProgressHideTimer();
736
+
737
+ if (e.key === 'Escape'|| e.key === 'XF86Back') {
738
+ e.preventDefault();
739
+ if (document.fullscreenElement) {
740
+ document.exitFullscreen();
741
+ } else {
742
+ switchToGlobalFocus();
743
+ }
744
+ return;
745
+ }
746
+
747
+ if (e.key === 'Backspace') {
748
+ e.preventDefault();
749
+ if (document.fullscreenElement) {
750
+ document.exitFullscreen();
751
+ } else {
752
+ switchToGlobalFocus();
753
+ }
754
+ return;
755
+ }
756
+
757
+ if (e.key === 'Enter' || e.key === 'OK') {
758
+ e.preventDefault();
759
+ el.video.paused ? el.video.play() : el.video.pause();
760
+ }
761
+ if (e.key === 'ArrowRight') {
762
+ e.preventDefault();
763
+ el.video.currentTime = Math.min(el.video.duration, el.video.currentTime + 10);
764
+ updateProgress();
765
+ }
766
+ if (e.key === 'ArrowLeft') {
767
+ e.preventDefault();
768
+ el.video.currentTime = Math.max(0, el.video.currentTime - 10);
769
+ updateProgress();
770
+ }
771
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
772
+ e.preventDefault();
773
+ toggleRatio();
774
+ }
775
+ return;
776
+ }
777
+
778
+ if ((e.key === 'Escape'|| e.key === 'XF86Back' || e.key === 'Backspace') && state.focusState.current !== 0) {
779
+ e.preventDefault();
780
+ switchToGlobalFocus();
781
+ return;
782
+ }
783
+
784
+ switch (state.focusState.current) {
785
+ case 0:
786
+ handleGlobalFocus(e);
787
+ break;
788
+ case 1:
789
+ handleIpConfigInnerFocus(e);
790
+ break;
791
+ case 2:
792
+ handleDeviceListInnerFocus(e);
793
+ break;
794
+ case 3:
795
+ handleEpsInnerFocus(e);
796
+ break;
797
+ }
798
+ });
799
+
800
+ // 进度条事件
801
+ el.progressBar.addEventListener('click', function(e) {
802
+ var rect = el.progressBar.getBoundingClientRect();
803
+ var pos = (e.clientX - rect.left) / rect.width;
804
+ el.video.currentTime = pos * el.video.duration;
805
+ updateProgress();
806
+ resetProgressHideTimer();
807
+ });
808
+
809
+ el.progressBar.addEventListener('keydown', function(e) {
810
+ if (e.key === 'Enter' || e.key === 'OK') {
811
+ e.preventDefault();
812
+ var rect = el.progressBar.getBoundingClientRect();
813
+ var centerX = rect.left + rect.width / 2;
814
+ var clickEvent = new MouseEvent('click', {
815
+ clientX: centerX,
816
+ clientY: rect.top + rect.height / 2
817
+ });
818
+ el.progressBar.dispatchEvent(clickEvent);
819
+ }
820
+ resetProgressHideTimer();
821
+ });
822
+
823
+ function handleGlobalFocus(e) {
824
+ var current = document.activeElement;
825
+ var idx = -1;
826
+ for (var i = 0; i < globalFocusElements.length; i++) {
827
+ if (globalFocusElements[i] === current) {
828
+ idx = i;
829
+ break;
830
+ }
831
+ }
832
+
833
+ if (e.key === 'Enter') {
834
+ e.preventDefault();
835
+ if (current === el.ipConfigModule) {
836
+ enterModuleFocus(1);
837
+ } else if (current === el.deviceListModule) {
838
+ enterModuleFocus(2);
839
+ } else if (current === el.playlistModule) {
840
+ enterModuleFocus(3);
841
+ } else if (current === el.progressBar) {
842
+ return;
843
+ } else if (idx !== -1) {
844
+ current.click();
845
+ }
846
+ return;
847
+ }
848
+
849
+ if (idx >= 0) {
850
+ if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
851
+ e.preventDefault();
852
+ var prev = (idx - 1 + globalFocusElements.length) % globalFocusElements.length;
853
+ globalFocusElements[prev].focus();
854
+ state.focusState.lastGlobalFocus = globalFocusElements[prev];
855
+ }
856
+ if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
857
+ e.preventDefault();
858
+ var next = (idx + 1) % globalFocusElements.length;
859
+ globalFocusElements[next].focus();
860
+ state.focusState.lastGlobalFocus = globalFocusElements[next];
861
+ }
862
+ }
863
+ }
864
+
865
+ function handleIpConfigInnerFocus(e) {
866
+ if (e.key === 'Enter') {
867
+ e.preventDefault();
868
+ switchToGlobalFocus(el.connectBtn);
869
+ return;
870
+ }
871
+ handleInnerFocusCycle(e, ipConfigInnerElements);
872
+ }
873
+
874
+ function handleDeviceListInnerFocus(e) {
875
+ var innerElements = arrayFrom(document.querySelectorAll('.device-item'));
876
+ if (innerElements.length === 0) {
877
+ if (e.key === 'Escape'|| e.key === 'XF86Back' || e.key === 'Backspace') {
878
+ e.preventDefault();
879
+ switchToGlobalFocus();
880
+ }
881
+ return;
882
+ }
883
+
884
+ if (e.key === 'Enter') {
885
+ e.preventDefault();
886
+ var current = document.activeElement;
887
+ var idx = -1;
888
+ for (var i = 0; i < innerElements.length; i++) {
889
+ if (innerElements[i] === current) {
890
+ idx = i;
891
+ break;
892
+ }
893
+ }
894
+ idx >= 0 && selectDevice(idx);
895
+ return;
896
+ }
897
+ handleInnerFocusCycle(e, innerElements);
898
+ }
899
+
900
+ function handleEpsInnerFocus(e) {
901
+ var innerElements = arrayFrom(document.querySelectorAll('.ep'));
902
+ if (innerElements.length === 0) {
903
+ if (e.key === 'Escape'|| e.key === 'XF86Back' || e.key === 'Backspace') {
904
+ e.preventDefault();
905
+ switchToGlobalFocus();
906
+ }
907
+ return;
908
+ }
909
+
910
+ if (e.key === 'Enter') {
911
+ e.preventDefault();
912
+ var current = document.activeElement;
913
+ var idx = -1;
914
+ for (var i = 0; i < innerElements.length; i++) {
915
+ if (innerElements[i] === current) {
916
+ idx = i;
917
+ break;
918
+ }
919
+ }
920
+ if (idx >= 0) {
921
+ playEp(idx);
922
+ }
923
+ return;
924
+ }
925
+ handleInnerFocusCycle(e, innerElements);
926
+ }
927
+
928
+ // 验证并连接手动输入的设备
929
+ function connectManualDevice() {
930
+ var ip = el.fullIpInput.value.trim();
931
+ var port = el.portInput.value.trim() || '52020';
932
+
933
+ // 验证IP格式
934
+ if (!validateIpAddress(ip)) {
935
+ showToast('请输入有效的IP地址', 2000);
936
+ return;
937
+ }
938
+
939
+ // 验证端口
940
+ var portNum = validateNumberInput(port, 1, 65535);
941
+ if (portNum.toString() !== port) {
942
+ el.portInput.value = portNum;
943
+ port = portNum.toString();
944
+ }
945
+
946
+ el.deviceTip.textContent = "正在连接 " + ip + ":" + port + "...";
947
+
948
+ // 测试设备是否可达
949
+ var testUrl = 'http://' + ip + ':' + port + '/playUrl';
950
+ fetch(testUrl, { mode: 'cors', cache: 'no-cache', timeout: 5000 })
951
+ .then(function(res) {
952
+ if (res.ok) {
953
+ // 设备可达,添加到设备列表
954
+ state.devices = [{ ip: ip, url: 'http://' + ip + ':' + port }];
955
+ renderDevices();
956
+ el.deviceTip.textContent = "已连接到 " + ip;
957
+ showToast('设备连接成功', 2000);
958
+ // 自动选择该设备
959
+ selectDevice(0);
960
+ } else {
961
+ throw new Error('设备不可达');
962
+ }
963
+ })
964
+ .catch(function() {
965
+ el.deviceTip.textContent = "连接失败:设备不可达";
966
+ showToast('连接失败,请检查IP和端口', 3000);
967
+ });
968
+ }
969
+
970
+ // 渲染已连接的设备
971
+ function renderDevices() {
972
+ if (state.devices.length === 0) {
973
+ el.deviceTip.textContent = "未连接设备";
974
+ return;
975
+ }
976
+ el.deviceList.innerHTML = '';
977
+
978
+ for (var i = 0; i < state.devices.length; i++) {
979
+ var dev = state.devices[i];
980
+ var item = document.createElement('div');
981
+ item.className = 'device-item active'; // 手动连接的设备默认激活
982
+ item.tabIndex = -1;
983
+ item.textContent = dev.ip;
984
+ item.dataset.index = i;
985
+ el.deviceList.appendChild(item);
986
+ }
987
+
988
+ if (state.focusState.current === 2) {
989
+ enterModuleFocus(2);
990
+ }
991
+ }
992
+
993
+ // 选择设备
994
+ function selectDevice(index) {
995
+ var dev = state.devices[index];
996
+ state.server = dev.url;
997
+
998
+ var deviceItems = document.querySelectorAll('.device-item');
999
+ for (var i = 0; i < deviceItems.length; i++) {
1000
+ if (i === index) {
1001
+ deviceItems[i].classList.add('active');
1002
+ } else {
1003
+ deviceItems[i].classList.remove('active');
1004
+ }
1005
+ }
1006
+
1007
+ el.empty.style.display = 'none';
1008
+ el.video.style.display = 'block';
1009
+
1010
+ if (CONFIG.SAVE_DEVICE) {
1011
+ localStorage.setItem('last_device', JSON.stringify(dev));
1012
+ }
1013
+
1014
+ setTimeout(function() {
1015
+ loadPlayUrl().then(function() {
1016
+ loadPlayList();
1017
+ });
1018
+ }, 500);
1019
+ startAutoRefresh();
1020
+ }
1021
+
1022
+ // 自动刷新
1023
+ function startAutoRefresh() {
1024
+ stopAutoRefresh();
1025
+ state.refreshTimer = setInterval(function() {
1026
+ if (state.server && !state.isLoadingPlayUrl) {
1027
+ loadPlayUrl();
1028
+ }
1029
+ }, CONFIG.REFRESH_INTERVAL);
1030
+ }
1031
+
1032
+ function stopAutoRefresh() {
1033
+ clearInterval(state.refreshTimer);
1034
+ state.lastPlayUrl = '';
1035
+ }
1036
+
1037
+ // 加载播放地址
1038
+ function loadPlayUrl() {
1039
+ if (!state.server || state.isLoadingPlayUrl) {
1040
+ return Promise.resolve();
1041
+ }
1042
+
1043
+ state.isLoadingPlayUrl = true;
1044
+ var api = state.server + '/playUrl?enhance=true&t=' + Date.now();
1045
+
1046
+ return fetch(api)
1047
+ .then(function(res) {
1048
+ if (!res.ok) {
1049
+ if (state.lastPlayUrl === '') {
1050
+ showToast('获取播放地址失败', 2000);
1051
+ }
1052
+ state.isLoadingPlayUrl = false;
1053
+ return;
1054
+ }
1055
+
1056
+ return res.text().then(function(text) {
1057
+ var data = JSON.parse(text);
1058
+ if (data.url) {
1059
+ var isUrlUpdated = data.url !== state.lastPlayUrl;
1060
+
1061
+ if (isUrlUpdated) {
1062
+ el.video.src = data.url;
1063
+ el.video.load();
1064
+ el.video.play().catch(function() {
1065
+ if (state.lastPlayUrl === '') {
1066
+ showToast('请手动点击播放按钮', 2000);
1067
+ }
1068
+ });
1069
+
1070
+ state.lastPlayUrl = data.url;
1071
+ state.currentVideoTitle = data.title || '当前播放视频';
1072
+
1073
+ // 用返回的title匹配剧集并高亮
1074
+ if (data.title) {
1075
+ setTimeout(function() {
1076
+ highlightEpByTitle(data.title);
1077
+ }, 500);
1078
+ }
1079
+
1080
+ setTimeout(function() {
1081
+ loadPlayList();
1082
+ }, 1000);
1083
+ }
1084
+
1085
+ el.titleBar.textContent = '当前播放:' + (data.title || state.currentVideoTitle);
1086
+
1087
+ if (data.jumpStartDuration > 0) {
1088
+ el.video.currentTime = data.jumpStartDuration;
1089
+ updateProgress();
1090
+ }
1091
+ } else {
1092
+ if (state.lastPlayUrl === '') {
1093
+ showToast('未获取到有效播放地址', 2000);
1094
+ }
1095
+ }
1096
+ });
1097
+ })
1098
+ .catch(function(err) {
1099
+ if (state.lastPlayUrl === '') {
1100
+ showToast('加载播放地址失败', 2000);
1101
+ }
1102
+ })
1103
+ .finally(function() {
1104
+ state.isLoadingPlayUrl = false;
1105
+ });
1106
+ }
1107
+
1108
+ // 加载选集列表
1109
+ function loadPlayList() {
1110
+ if (!state.server || state.isLoadingPlayList) {
1111
+ return Promise.resolve();
1112
+ }
1113
+
1114
+ state.isLoadingPlayList = true;
1115
+ var api = state.server + '/getPlayList?t=' + Date.now();
1116
+
1117
+ return fetch(api)
1118
+ .then(function(res) {
1119
+ var newPlaylist = [];
1120
+
1121
+ if (res.ok) {
1122
+ return res.text().then(function(text) {
1123
+ newPlaylist = JSON.parse(text) || [];
1124
+
1125
+ if (newPlaylist.length === 0) {
1126
+ newPlaylist = [state.currentVideoTitle || '当前播放视频'];
1127
+ }
1128
+
1129
+ if (JSON.stringify(newPlaylist) !== JSON.stringify(state.playlist)) {
1130
+ state.playlist = newPlaylist;
1131
+ el.playlistModule.style.display = 'flex';
1132
+ renderEps();
1133
+
1134
+ // 列表加载完成后,再次用当前播放标题匹配高亮
1135
+ highlightEpByTitle(state.currentVideoTitle);
1136
+ showToast('剧集列表已更新,共' + state.playlist.length + '集', 1500);
1137
+ }
1138
+ });
1139
+ } else {
1140
+ if (state.playlist.length === 0) {
1141
+ newPlaylist = [state.currentVideoTitle || '当前播放视频'];
1142
+ state.playlist = newPlaylist;
1143
+ el.playlistModule.style.display = 'flex';
1144
+ renderEps();
1145
+ highlightEpByTitle(state.currentVideoTitle);
1146
+ }
1147
+ }
1148
+ })
1149
+ .catch(function(err) {
1150
+ if (state.playlist.length === 0) {
1151
+ state.playlist = [state.currentVideoTitle || '当前播放视频'];
1152
+ el.playlistModule.style.display = 'flex';
1153
+ renderEps();
1154
+ highlightEpByTitle(state.currentVideoTitle);
1155
+ }
1156
+ })
1157
+ .finally(function() {
1158
+ state.isLoadingPlayList = false;
1159
+ });
1160
+ }
1161
+
1162
+ // 渲染剧集列表
1163
+ function renderEps() {
1164
+ el.eps.innerHTML = '';
1165
+ for (var i = 0; i < state.playlist.length; i++) {
1166
+ var title = state.playlist[i];
1167
+ var div = document.createElement('div');
1168
+ div.className = 'ep' + (i === state.currentEp ? ' active' : '');
1169
+ div.tabIndex = -1;
1170
+ div.textContent = (i + 1).toString();
1171
+ el.eps.appendChild(div);
1172
+ }
1173
+
1174
+ if (state.focusState.current === 3) {
1175
+ enterModuleFocus(3);
1176
+ }
1177
+ }
1178
+
1179
+ // 播放指定剧集
1180
+ function playEp(index) {
1181
+ if (!state.server) {
1182
+ showToast('未连接到投屏设备', 2000);
1183
+ return;
1184
+ }
1185
+
1186
+ if (index < 0 || index >= state.playlist.length) {
1187
+ showToast('剧集不存在', 2000);
1188
+ return;
1189
+ }
1190
+
1191
+ state.currentEp = index;
1192
+ var epElements = document.querySelectorAll('.ep');
1193
+ for (var i = 0; i < epElements.length; i++) {
1194
+ if (i === index) {
1195
+ epElements[i].classList.add('active');
1196
+ } else {
1197
+ epElements[i].classList.remove('active');
1198
+ }
1199
+ }
1200
+
1201
+ var episodeTitle = state.playlist[index];
1202
+
1203
+ return fetch(state.server + '/playMe?index=' + index + '&title=' + encodeURIComponent(episodeTitle))
1204
+ .then(function(response) {
1205
+ return response.text().then(function(result) {
1206
+ if (result === 'true') {
1207
+ showToast('正在播放:第' + (index + 1) + '集', 2000);
1208
+ return loadPlayUrl();
1209
+ } else {
1210
+ showToast('播放失败:设备播放页已关闭', 3000);
1211
+ }
1212
+ });
1213
+ })
1214
+ .catch(function(err) {
1215
+ showToast('切换剧集失败:第' + (index + 1) + '集', 3000);
1216
+ });
1217
+ }
1218
+
1219
+ // 比例切换
1220
+ function toggleRatio() {
1221
+ state.ratioMode = (state.ratioMode + 1) % 4;
1222
+ var curr = ratioList[state.ratioMode];
1223
+ el.ratioToggle.textContent = curr.text;
1224
+ showToast(curr.text);
1225
+ setRatio(curr.ratio);
1226
+ }
1227
+
1228
+ // 核心修改:统一全屏/非全屏逻辑,都保证宽/高其一铺满容器
1229
+ function setRatio(ratio) {
1230
+ var isFullscreen = !!document.fullscreenElement;
1231
+ var wrapper = el.videoWrapper;
1232
+ var video = el.video;
1233
+
1234
+ // 重置容器样式(保证居中)
1235
+ wrapper.style.display = 'flex';
1236
+ wrapper.style.alignItems = 'center';
1237
+ wrapper.style.justifyContent = 'center';
1238
+
1239
+ // 基础样式:统一重置
1240
+ video.style.display = 'block';
1241
+ video.style.objectFit = 'fill'; // 保留拉伸,破坏原比例
1242
+ video.style.position = 'static'; // 恢复默认定位
1243
+ video.style.top = 'auto';
1244
+ video.style.left = 'auto';
1245
+
1246
+ // 获取容器尺寸(全屏取屏幕尺寸,非全屏取播放器容器尺寸)
1247
+ var containerWidth = isFullscreen ? window.innerWidth : wrapper.offsetWidth;
1248
+ var containerHeight = isFullscreen ? window.innerHeight : wrapper.offsetHeight;
1249
+
1250
+ // 解析目标比例(如 16/9 → 1.777...)
1251
+ var ratioParts = ratio.split('/').map(Number);
1252
+ var wRatio = ratioParts[0];
1253
+ var hRatio = ratioParts[1];
1254
+ var targetRatio = wRatio / hRatio;
1255
+ // 容器的实际比例
1256
+ var containerRatio = containerWidth / containerHeight;
1257
+
1258
+ // 核心逻辑:保证宽/高其一铺满容器,另一方向按比例适配(留黑边)
1259
+ if (targetRatio > containerRatio) {
1260
+ // 目标比例更宽 → 宽度铺满容器,高度按比例计算(高度不足则留上下黑边)
1261
+ video.style.width = '100%';
1262
+ video.style.height = 'auto';
1263
+ } else {
1264
+ // 目标比例更高 → 高度铺满容器,宽度按比例计算(宽度不足则留左右黑边)
1265
+ video.style.height = '100%';
1266
+ video.style.width = 'auto';
1267
+ }
1268
+
1269
+ // 强制应用比例,确保计算后的尺寸符合目标比例
1270
+ video.style.aspectRatio = ratio;
1271
+ // 移除所有尺寸限制,让计算纯粹
1272
+ video.style.minWidth = '0';
1273
+ video.style.minHeight = '0';
1274
+ video.style.maxWidth = '100%';
1275
+ video.style.maxHeight = '100%';
1276
+ }
1277
+
1278
+ // 事件绑定
1279
+ el.back10.onclick = function() {
1280
+ el.video.currentTime = Math.max(0, el.video.currentTime - 10);
1281
+ updateProgress();
1282
+ };
1283
+
1284
+ el.for10.onclick = function() {
1285
+ el.video.currentTime = Math.min(el.video.duration, el.video.currentTime + 10);
1286
+ updateProgress();
1287
+ };
1288
+
1289
+ el.play.onclick = function() {
1290
+ if (el.video.paused) {
1291
+ el.video.play().then(function() {
1292
+ el.play.textContent = '⏸';
1293
+ }).catch(function() {
1294
+ showToast('播放失败,请检查视频源', 2000);
1295
+ });
1296
+ } else {
1297
+ el.video.pause();
1298
+ el.play.textContent = '▶';
1299
+ }
1300
+ updateProgress();
1301
+ };
1302
+
1303
+ // 视频事件
1304
+ el.video.addEventListener('play', function() {
1305
+ el.play.textContent = '⏸';
1306
+ updateProgress();
1307
+ });
1308
+
1309
+ el.video.addEventListener('pause', function() {
1310
+ el.play.textContent = '▶';
1311
+ updateProgress();
1312
+ });
1313
+
1314
+ el.video.addEventListener('timeupdate', updateProgress);
1315
+
1316
+ el.video.addEventListener('loadedmetadata', function() {
1317
+ updateProgress();
1318
+ setRatio(ratioList[state.ratioMode].ratio);
1319
+ });
1320
+
1321
+ // 比例按钮
1322
+ el.ratioToggle.onclick = toggleRatio;
1323
+
1324
+ // 全屏按钮
1325
+ el.fs.onclick = function() {
1326
+ if (!document.fullscreenElement) {
1327
+ el.videoBox.requestFullscreen().then(function() {
1328
+ el.videoBox.classList.add('fullscreen');
1329
+ resetProgressHideTimer();
1330
+ // 延迟保证DOM更新后再设置比例
1331
+ setTimeout(function() {
1332
+ setRatio(ratioList[state.ratioMode].ratio);
1333
+ }, 200);
1334
+ }).catch(function() {
1335
+ showToast('进入全屏失败', 2000);
1336
+ });
1337
+ } else {
1338
+ document.exitFullscreen().then(function() {
1339
+ el.videoBox.classList.remove('fullscreen');
1340
+ clearTimeout(state.progressHideTimer);
1341
+ // 延迟保证DOM更新后再设置比例
1342
+ setTimeout(function() {
1343
+ setRatio(ratioList[state.ratioMode].ratio);
1344
+ }, 200);
1345
+ });
1346
+ }
1347
+ };
1348
+
1349
+ // 全屏监听
1350
+ document.addEventListener('fullscreenchange', function() {
1351
+ var isFull = !!document.fullscreenElement;
1352
+
1353
+ // 延迟保证DOM更新后再设置比例
1354
+ setTimeout(function() {
1355
+ setRatio(ratioList[state.ratioMode].ratio);
1356
+ }, 200);
1357
+
1358
+ if (isFull) {
1359
+ el.videoBox.classList.add('fullscreen');
1360
+ resetProgressHideTimer();
1361
+ } else {
1362
+ el.videoBox.classList.remove('fullscreen');
1363
+ el.videoBox.classList.remove('show-progress');
1364
+ clearTimeout(state.progressHideTimer);
1365
+ }
1366
+ if (!isFull) {
1367
+ setTimeout(function() {
1368
+ el.fs.focus();
1369
+ }, 100);
1370
+ }
1371
+ });
1372
+
1373
+ // 连接按钮点击事件
1374
+ el.connectBtn.addEventListener('click', connectManualDevice);
1375
+
1376
+ // 初始化比例
1377
+ setRatio('16/9');
1378
+
1379
+ // 自动填充上次连接的设备信息
1380
+ if (state.lastDevice) {
1381
+ try {
1382
+ var lastDev = JSON.parse(state.lastDevice);
1383
+ var ip = lastDev.ip || '';
1384
+ var port = lastDev.url ? lastDev.url.split(':')[2] || '52020' : '52020';
1385
+ if (port) {
1386
+ port = port.split('/')[0];
1387
+ }
1388
+ el.fullIpInput.value = ip;
1389
+ el.portInput.value = port;
1390
+ } catch (e) {
1391
+ // 解析失败不处理
1392
+ }
1393
+ }
1394
+
1395
+ // 初始焦点
1396
+ switchToGlobalFocus(el.connectBtn);
1397
+ });
1398
+ </script>
1399
+ </body>
1400
+ </html>
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "hy666",
3
+ "version": "1.1.1",
4
+ "description": "CAST006",
5
+ "packageType": "app",
6
+ "appName": "电视投屏PLUS6",
7
+ "appPath": "index.html",
8
+ "keys": [
9
+ "MediaPlayPause",
10
+ "MediaPlay",
11
+ "MediaPause",
12
+ "MediaStop",
13
+ "MediaTrackPrevious",
14
+ "MediaTrackNext",
15
+ "MediaRewind",
16
+ "MediaFastForward"
17
+ ]
18
+ }