soiz1 commited on
Commit
18e8957
·
verified ·
1 Parent(s): cff74b6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +508 -989
index.html CHANGED
@@ -1,900 +1,482 @@
1
  <!DOCTYPE html>
2
  <html lang="ja">
3
-
4
  <head>
5
- <meta charset="UTF-8">
6
- <title>ラジオ体操動画プレイヤー</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c&display=swap" rel="stylesheet">
10
- <link rel="icon" href="icon.png" type="image/png">
11
- <script src="https://cdn.jsdelivr.net/npm/video-frames@1/dist/videoframes.umd.min.js"></script>
12
- <style>
13
  body {
14
- display: flex;
15
- flex-direction: column;
16
- align-items: center;
17
- background-color: #0a0a12;
18
- color: #00ffcc;
19
- font-family: "M PLUS Rounded 1c", monospace;
20
- padding: 20px;
21
- margin: 0;
22
- overflow-x: hidden;
23
- }
24
-
25
- h1 {
26
- color: #00aaff;
27
- text-shadow: 0 0 5px #0066ff;
28
- border-bottom: 1px solid #0066ff;
29
- padding-bottom: 10px;
30
- text-align: center;
31
- }
32
-
33
- .video-container {
34
- position: relative;
35
- max-width: 800px;
36
- margin-bottom: 20px;
37
- margin-top: 30px;
38
- border: 2px solid #0066ff;
39
- box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
40
- background: #000;
41
- }
42
-
43
- video {
44
- width: 100%;
45
- display: block;
46
- }
47
-
48
- /* 字幕スタイル */
49
- video::cue {
50
- background-color: rgba(0, 0, 0, 0.7) !important;
51
- color: #c7dbed !important;
52
- font-family: "M PLUS Rounded 1c", monospace !important;
53
- text-shadow: 1px 1px 2px #000 !important;
54
- outline: 3px solid #0b3e8f !important;
55
- border-radius: 10px !important;
56
- }
57
-
58
- /* カスタム動画コントロール */
59
- video::-webkit-media-controls {
60
- display: none !important;
61
- }
62
-
63
- .custom-controls {
64
- position: absolute;
65
- bottom: 0;
66
- left: 0;
67
- right: 0;
68
- padding: 10px;
69
- display: flex;
70
- flex-direction: column;
71
- opacity: 0;
72
- transition: opacity 0.3s;
73
- }
74
-
75
- .video-container:hover .custom-controls {
76
- opacity: 1;
77
- }
78
-
79
- .progress-container {
80
- width: 100%;
81
- height: 8px;
82
- background: #001133;
83
- margin-bottom: 10px;
84
- cursor: pointer;
85
- position: relative;
86
- }
87
-
88
- .progress-bar {
89
- height: 100%;
90
- background: #00aaff;
91
- width: 0%;
92
- position: relative;
93
- }
94
-
95
- .progress-bar::after {
96
- content: '';
97
- position: absolute;
98
- right: -5px;
99
- top: 50%;
100
- transform: translateY(-50%);
101
- width: 10px;
102
- height: 10px;
103
- background: #00ccff;
104
- border-radius: 50%;
105
- box-shadow: 0 0 5px #00ccff;
106
- }
107
-
108
- .buttons-container {
109
- display: flex;
110
- align-items: center;
111
- justify-content: space-between;
112
- }
113
-
114
- .left-controls, .right-controls {
115
- display: flex;
116
- align-items: center;
117
- gap: 15px;
118
- }
119
-
120
- .control-btn {
121
- background: none;
122
- border: none;
123
- color: #00ccff;
124
- font-size: 16px;
125
- cursor: pointer;
126
- transition: all 0.3s;
127
- }
128
-
129
- .control-btn:hover {
130
- color: #00ffcc;
131
- text-shadow: 0 0 5px #00ffcc;
132
- }
133
-
134
- .time-display {
135
- font-size: 14px;
136
- color: #00aaff;
137
- box-shadow: 0.1px 0.1px 0.1px black;
138
- font-family: "M PLUS Rounded 1c", monospace;
139
- }
140
-
141
- .volume-container {
142
- display: flex;
143
- align-items: center;
144
- gap: 5px;
145
- }
146
-
147
- .volume-slider {
148
- width: 80px;
149
- -webkit-appearance: none;
150
- height: 4px;
151
- background: #001133;
152
- outline: none;
153
- }
154
-
155
- .volume-slider::-webkit-slider-thumb {
156
- -webkit-appearance: none;
157
- width: 12px;
158
- height: 12px;
159
- background: #00aaff;
160
- border-radius: 50%;
161
- cursor: pointer;
162
- }
163
-
164
- .controls {
165
- display: flex;
166
- flex-direction: column;
167
- gap: 15px;
168
- width: 100%;
169
- max-width: 800px;
170
- background-color: #0f0f1a;
171
- padding: 20px;
172
- border: 1px solid #0066ff;
173
- box-shadow: 0 0 15px rgba(0, 102, 255, 0.3);
174
- }
175
-
176
- .control-group {
177
- display: flex;
178
- flex-direction: row;
179
- align-items: center;
180
- justify-content: flex-start;
181
- gap: 10px;
182
- flex-wrap: nowrap;
183
- }
184
-
185
- .control-group label {
186
- white-space: nowrap;
187
- min-width: 100px;
188
- text-align: right;
189
- color: #00ccff;
190
- }
191
-
192
- input[type="range"] {
193
- flex-grow: 1;
194
- -webkit-appearance: none;
195
- height: 8px;
196
- background: #001133;
197
- border-radius: 5px;
198
- outline: none;
199
- }
200
-
201
- input[type="range"]::-webkit-slider-thumb {
202
- -webkit-appearance: none;
203
- width: 18px;
204
- height: 18px;
205
- background: #00aaff;
206
- border-radius: 50%;
207
- cursor: pointer;
208
- box-shadow: 0 0 5px #00aaff;
209
- }
210
-
211
- input[type="number"], select {
212
- background-color: #001133;
213
- color: #00ccff;
214
- border: 1px solid #0066ff;
215
- padding: 5px;
216
- font-family: "M PLUS Rounded 1c", monospace;
217
- }
218
-
219
- button {
220
- background-color: #001133;
221
- color: #00ccff;
222
- border: 1px solid #0066ff;
223
- padding: 8px 15px;
224
- cursor: pointer;
225
- font-family: "M PLUS Rounded 1c", monospace;
226
- transition: all 0.3s;
227
- align-self: flex-start;
228
- }
229
-
230
- button:hover {
231
- background-color: #0066ff;
232
- color: #000;
233
- box-shadow: 0 0 10px #0066ff;
234
- }
235
-
236
- select {
237
- width: 300px;
238
- background-color: #001133;
239
- color: #00ccff;
240
- border: 1px solid #0066ff;
241
- padding: 5px;
242
- }
243
-
244
- input[type="checkbox"] {
245
- -webkit-appearance: none;
246
- width: 18px;
247
- height: 18px;
248
- background: #001133;
249
- border: 1px solid #0066ff;
250
- position: relative;
251
- }
252
-
253
- input[type="checkbox"]:checked {
254
- background: #0066ff;
255
- box-shadow: 0 0 5px #0066ff;
256
- }
257
-
258
- input[type="checkbox"]:checked::after {
259
- content: "✓";
260
- position: absolute;
261
- color: #000;
262
- font-size: 14px;
263
- top: 50%;
264
- left: 50%;
265
- transform: translate(-50%, -50%);
266
- }
267
-
268
- /* 字幕設定用スタイル */
269
- .subtitle-settings {
270
- margin-top: 10px;
271
- padding: 10px;
272
- background-color: rgba(0, 20, 40, 0.5);
273
- border: 1px solid #0066ff;
274
- }
275
-
276
- /* 字幕サイズ調整用のCSS変数 */
277
- :root {
278
- --subtitle-scale: 1;
279
- --subtitle-border-radius: 10px;
280
- }
281
-
282
- video::cue {
283
- font-size: calc(16px * var(--subtitle-scale)) !important;
284
- line-height: 1.5 !important;
285
- border-radius: var(--subtitle-border-radius) !important;
286
- }
287
-
288
- /* 全画面時の字幕サイズ調整 */
289
- .video-container:fullscreen video::cue,
290
- .video-container:-webkit-full-screen video::cue,
291
- .video-container:-moz-full-screen video::cue,
292
- .video-container:-ms-fullscreen video::cue {
293
- font-size: calc(16px * var(--subtitle-scale) * var(--fullscreen-scale, 1)) !important;
294
- }
295
- body {
296
- margin: 0;
297
- padding: 0;
298
- background-color: #0a192f;
299
- height: 100vh;
300
- width: 100vw;
301
- }
302
-
303
- .ripple {
304
- position: absolute;
305
- border-radius: 50%;
306
- background: transparent;
307
- border: 1px solid rgba(100, 210, 255, 0.3);
308
- transform: translate(-50%, -50%);
309
- pointer-events: none;
310
- animation: ripple-animation 4s ease-out forwards;
311
- z-index: -1;
312
- position: absolute;
313
- }
314
-
315
- @keyframes ripple-animation {
316
- 0% {
317
- width: 0;
318
- height: 0;
319
- opacity: 0.6;
320
- }
321
- 100% {
322
- width: 600px;
323
- height: 600px;
324
- opacity: 0;
325
- }
326
- }
327
-
328
- /* ローディングアニメーション */
329
- .loading-overlay {
330
- position: fixed;
331
- top: 0;
332
- left: 0;
333
- width: 100%;
334
- height: 100%;
335
- background-color: rgba(0, 0, 0, 0.8);
336
- display: flex;
337
- justify-content: center;
338
- align-items: center;
339
- z-index: 9999;
340
- transition: opacity 1s ease-out;
341
- }
342
-
343
- .spinner-box {
344
- width: 300px;
345
- height: 300px;
346
- display: flex;
347
- justify-content: center;
348
- align-items: center;
349
- background-color: transparent;
350
- }
351
-
352
- /* 軌道スタイル */
353
- .leo {
354
- position: absolute;
355
- display: flex;
356
- justify-content: center;
357
- align-items: center;
358
- border-radius: 50%;
359
- }
360
-
361
- .blue-orbit {
362
- width: 165px;
363
- height: 165px;
364
- border: 1px solid #91daffa5;
365
- animation: spin3D 3s linear .2s infinite;
366
- }
367
-
368
- .green-orbit {
369
- width: 120px;
370
- height: 120px;
371
- border: 1px solid #91ffbfa5;
372
- animation: spin3D 2s linear 0s infinite;
373
- }
374
-
375
- .red-orbit {
376
- width: 90px;
377
- height: 90px;
378
- border: 1px solid #ffca91a5;
379
- animation: spin3D 1s linear 0s infinite;
380
- }
381
-
382
- .white-orbit {
383
- width: 60px;
384
- height: 60px;
385
- border: 2px solid #ffffff;
386
- animation: spin3D 10s linear 0s infinite;
387
- }
388
-
389
- .w1 {
390
- transform: rotate3D(1, 1, 1, 90deg);
391
- }
392
-
393
- .w2 {
394
- transform: rotate3D(1, 2, .5, 90deg);
395
- }
396
-
397
- .w3 {
398
- transform: rotate3D(.5, 1, 2, 90deg);
399
- }
400
-
401
- /* キーフレームアニメーション */
402
- @keyframes spin3D {
403
- from {
404
- transform: rotate3d(.5,.5,.5, 360deg);
405
- }
406
- to {
407
- transform: rotate3d(0,0,0, 0deg);
408
- }
409
- }
410
-
411
- @keyframes spin {
412
- from {
413
- transform: rotate(0deg);
414
- }
415
- to {
416
- transform: rotate(360deg);
417
- }
418
- }
419
-
420
- /* フレームプレビュー */
421
- .frame-preview {
422
- position: absolute;
423
- bottom: 30px;
424
- transform: translateX(-50%);
425
- width: 160px;
426
- height: 90px;
427
- background: #000;
428
- border: 2px solid #00aaff;
429
- box-shadow: 0 0 10px rgba(0, 170, 255, 0.7);
430
- display: none;
431
- z-index: 100;
432
- pointer-events: none;
433
- }
434
-
435
- .frame-preview img {
436
- width: 100%;
437
- height: 100%;
438
- object-fit: contain;
439
- }
440
-
441
- .frame-time {
442
- position: absolute;
443
- bottom: -25px;
444
- left: 50%;
445
- transform: translateX(-50%);
446
- background: rgba(0, 0, 0, 0.8);
447
- color: #00ccff;
448
- padding: 3px 8px;
449
- border-radius: 4px;
450
- font-size: 12px;
451
- white-space: nowrap;
452
- }
453
-
454
- /* 右クリックメニュー */
455
- .context-menu {
456
- position: fixed;
457
- background-color: #0f0f1a;
458
- border: 1px solid #0066ff;
459
- box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
460
- z-index: 1000;
461
- display: none;
462
- min-width: 200px;
463
- }
464
-
465
- .context-menu button {
466
- width: 100%;
467
- text-align: left;
468
- padding: 8px 15px;
469
- border: none;
470
- border-bottom: 1px solid #003366;
471
- background: none;
472
- color: #00ccff;
473
- font-family: "M PLUS Rounded 1c", monospace;
474
- cursor: pointer;
475
- }
476
-
477
- .context-menu button:hover {
478
- background-color: #0066ff;
479
- color: #000;
480
- }
481
-
482
- /* 音声/字幕のみモード */
483
- .audio-only-mode {
484
- position: absolute;
485
- top: 10px;
486
- right: 10px;
487
- background: rgba(0, 0, 0, 0.7);
488
- color: #00ccff;
489
- padding: 5px 10px;
490
- border-radius: 4px;
491
- font-size: 12px;
492
- display: none;
493
- z-index: 10;
494
- }
495
-
496
- .audio-only-mode.active {
497
- display: block;
498
- }
499
- </style>
500
- </head>
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  <body>
503
- <!-- ローディングオーバーレイ -->
504
- <div class="loading-overlay" id="loadingOverlay">
505
- <div class="spinner-box">
506
- <div class="blue-orbit leo">
507
- </div>
508
- <div class="green-orbit leo">
509
- </div>
510
- <div class="red-orbit leo">
511
- </div>
512
- <div class="white-orbit w1 leo">
513
- </div>
514
- <div class="white-orbit w2 leo">
515
- </div>
516
- <div class="white-orbit w3 leo">
517
- </div>
518
- </div>
519
  </div>
520
- <div id="ripple-container">
 
 
 
521
  </div>
522
- <!-- フレームプレビュー -->
523
- <div class="frame-preview" id="framePreview">
524
- <img id="previewImage" src="">
525
- <div class="frame-time" id="frameTime">
526
- </div>
527
  </div>
528
- <!-- 右クリックメニュー -->
529
- <div class="context-menu" id="contextMenu">
530
- <button onclick="togglePlayPause()">再生/一時停止</button>
531
- <button onclick="toggleMute()">ミュート切り替え</button>
532
- <button onclick="toggleSubtitles()">字幕表示切り替え</button>
533
- <button onclick="toggleAudioOnlyMode()">音声/字幕のみモード</button>
534
- <button onclick="goFullscreen()">全画面表示</button>
535
  </div>
536
- <!-- 音声/字幕のみモード表示 -->
537
- <div class="audio-only-mode" id="audioOnlyModeIndicator">音声/字幕のみモード</div>
538
- <h1>ラジオ体操動画プレイヤー
539
- <br>For Kushihara</h1>
540
- <div class="controls">
541
- <div class="control-group">
542
- <label for="videoSelect">動画の音量:</label>
543
- <select id="videoSelect">
544
- <option value="v.mp4">小</option>
545
- <option value="v-2.mp4">大(+50dB)</option>
546
- </select>
547
- </div>
548
- <div class="control-group">
549
- <label for="speedRange">再生速度:</label>
550
- <input type="range" id="speedRange" min="0.0001" max="10" step="0.0001" value="1" style="width:700px !important;">
551
- <input type="number" id="speedInput" min="0.0001" step="0.0001" value="1">
552
- </div>
553
- <div class="control-group">
554
- <label for="volumeRange">音量:</label>
555
- <input type="range" id="volumeRange" min="0" max="1" step="0.01" value="1">
556
- <input type="number" id="volumeInput" min="0" max="1" step="0.01" value="1">
557
- </div>
558
- <div class="control-group">
559
- <label for="loopCheckbox">ループ再生:</label>
560
- <input type="checkbox" id="loopCheckbox" checked>
561
- </div>
562
- <!-- 字幕設定セクション -->
563
- <div class="subtitle-settings">
564
- <div class="control-group">
565
- <label for="subtitleToggle">字幕表示:</label>
566
- <input type="checkbox" id="subtitleToggle" checked>
567
- </div>
568
- <div class="control-group">
569
- <label for="subtitleSize">文字サイズ:</label>
570
- <input type="range" id="subtitleSize" min="0.5" max="5" step="0.1" value="1.5">
571
- <input type="number" id="subtitleSizeInput" min="0.01" step="0.01" value="1.5">
572
- </div>
573
- <div class="control-group">
574
- <label for="subtitleTrack">字幕トラック:</label>
575
- <select id="subtitleTrack">
576
- <option value="v.vtt">日本語</option>
577
- <option value="">字幕なし</option>
578
- </select>
579
- </div>
580
- </div>
581
- <div class="control-group">
582
- <button onclick="goFullscreen()">全画面</button>
583
- <button onclick="toggleAudioOnlyMode()">音声/字幕のみモード</button>
584
- </div>
585
  </div>
586
- <div class="video-container">
587
- <video id="videoPlayer" src="v.mp4">
588
- <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
589
- </track>
590
- </video>
591
- <div class="preview-container" id="previewContainer">
592
- <img id="preview" style="max-width: 200px; max-height: 150px;">
593
- <div class="preview-time" id="previewTime"></div>
 
 
 
 
 
 
 
 
594
  </div>
595
- <div class="custom-controls">
596
- <div class="progress-container" id="progressContainer">
597
- <div class="progress-bar" id="progressBar">
598
- </div>
599
- </div>
600
- <div class="buttons-container">
601
- <div class="left-controls">
602
- <button class="control-btn" id="playPauseBtn">▶</button>
603
- <span class="time-display" id="timeDisplay">00:00 / 00:00</span>
604
- </div>
605
- <div class="right-controls">
606
- <div class="volume-container">
607
- <button class="control-btn" id="volumeBtn">🔊</button>
608
- <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
609
- </div>
610
- <button class="control-btn" id="subtitleBtn" title="字幕">🔤</button>
611
- <button class="control-btn" id="fullscreenBtn">⛶</button>
612
- </div>
613
- </div>
614
  </div>
 
615
  </div>
616
- <canvas id="canvas">
617
- </canvas>
618
- <!-- サムネイル用の非表示video要素 -->
619
- <video id="video-for-thumbnail" src="v.mp4" preload="auto" style="display:none;">
620
- </video>
621
- <script>
622
- // 要素取得
623
- const video = document.getElementById('videoPlayer');
624
- const videoSelect = document.getElementById('videoSelect');
625
- const speedRange = document.getElementById('speedRange');
626
- const speedInput = document.getElementById('speedInput');
627
- const volumeRange = document.getElementById('volumeRange');
628
- const volumeInput = document.getElementById('volumeInput');
629
- const loopCheckbox = document.getElementById('loopCheckbox');
630
- const playPauseBtn = document.getElementById('playPauseBtn');
631
- const progressBar = document.getElementById('progressBar');
632
- const progressContainer = document.getElementById('progressContainer');
633
- const timeDisplay = document.getElementById('timeDisplay');
634
- const volumeBtn = document.getElementById('volumeBtn');
635
- const volumeSlider = document.getElementById('volumeSlider');
636
- const fullscreenBtn = document.getElementById('fullscreenBtn');
637
- const subtitleBtn = document.getElementById('subtitleBtn');
638
- const subtitleToggle = document.getElementById('subtitleToggle');
639
- const subtitleSize = document.getElementById('subtitleSize');
640
- const subtitleSizeInput = document.getElementById('subtitleSizeInput');
641
- const subtitleTrack = document.getElementById('subtitleTrack');
642
- const subtitleTrackElement = document.getElementById('subtitleTrackElement');
643
- const videoContainer = document.getElementById('videoContainer');
644
- const videoPlaceholder = document.getElementById('videoPlaceholder');
645
- const previewTooltip = document.getElementById('previewTooltip');
646
- const previewTime = document.getElementById('previewTime');
647
- const previewFrame = document.getElementById('previewFrame');
648
- const audioOnlyBtn = document.getElementById('audioOnlyBtn');
649
- const fullscreenContextMenu = document.getElementById('fullscreenContextMenu');
650
- const exitFullscreenBtn = document.getElementById('exitFullscreenBtn');
651
- const showVideoBtn = document.getElementById('showVideoBtn');
652
- const closeContextMenuBtn = document.getElementById('closeContextMenuBtn');
653
-
654
- // プレビュー用動画を動的に作成
655
- const previewVideo = document.createElement('video');
656
- previewVideo.id = 'previewVideo';
657
- previewVideo.className = 'thumbnail-preview';
658
- previewVideo.muted = true;
659
- previewVideo.preload = 'auto';
660
- videoContainer.appendChild(previewVideo);
661
-
662
- // 初期設定
663
- video.controls = false;
664
- previewVideo.src = video.src;
665
- let isDragging = false;
666
- let subtitlesEnabled = true;
667
- let normalVideoWidth = videoContainer.clientWidth;
668
- let isAudioOnlyMode = false;
669
- let hoverTimeout;
670
- let canvas = null;
671
- let previewCanvas = null;
672
- let previewContext = null;
673
- let isGeneratingPreview = false;
674
-
675
- // プレビュー用キャンバスを作成
676
- function createPreviewCanvas() {
677
- if (!canvas) {
678
- canvas = document.createElement('canvas');
679
- canvas.width = video.videoWidth || 640;
680
- canvas.height = video.videoHeight || 360;
681
  }
682
-
683
- if (!previewCanvas) {
684
- previewCanvas = document.createElement('canvas');
685
- previewCanvas.width = 160;
686
- previewCanvas.height = 90;
687
- previewContext = previewCanvas.getContext('2d');
 
 
 
 
 
 
 
 
 
688
  }
689
- }
690
 
691
- // プレビュー画像を生成
692
- function generatePreview(time) {
693
- if (isGeneratingPreview || !video.readyState) return;
694
-
695
- try {
696
- isGeneratingPreview = true;
697
- createPreviewCanvas();
698
-
699
- const currentTime = video.currentTime;
700
- video.currentTime = time;
701
-
702
- const onSeeked = () => {
703
- video.removeEventListener('seeked', onSeeked);
704
-
705
- try {
706
- const ctx = canvas.getContext('2d');
707
- canvas.width = video.videoWidth;
708
- canvas.height = video.videoHeight;
709
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
710
- previewContext.drawImage(canvas, 0, 0, previewCanvas.width, previewCanvas.height);
711
- previewFrame.style.backgroundImage = `url(${previewCanvas.toDataURL()})`;
712
- } catch (e) {
713
- console.error('Preview generation error:', e);
714
- previewFrame.style.backgroundImage = 'linear-gradient(to bottom, #0066ff, #00aaff)';
715
  }
716
-
717
- video.currentTime = currentTime;
718
- isGeneratingPreview = false;
719
- };
720
-
721
- video.addEventListener('seeked', onSeeked);
722
- } catch (e) {
723
- console.error('Preview error:', e);
724
- isGeneratingPreview = false;
725
  }
726
- }
727
 
728
- // プレビューツールチップを表示
729
- function showPreviewTooltip(e) {
730
- if (!video.duration) return;
731
-
732
- const rect = progressContainer.getBoundingClientRect();
733
- const percent = (e.clientX - rect.left) / rect.width;
734
- const time = Math.max(0, Math.min(percent, 1)) * video.duration;
735
-
736
- const tooltipWidth = previewTooltip.offsetWidth;
737
- let left = e.clientX - rect.left;
738
- left = Math.max(tooltipWidth / 2, Math.min(left, rect.width - tooltipWidth / 2));
739
-
740
- previewTooltip.style.left = `${left}px`;
741
-
742
- const minutes = Math.floor(time / 60);
743
- const seconds = Math.floor(time % 60).toString().padStart(2, '0');
744
- previewTime.textContent = `${minutes}:${seconds}`;
745
-
746
- generatePreview(time);
747
- previewTooltip.style.display = 'block';
748
- }
749
-
750
- // プレビューツールチップを非表示
751
- function hidePreviewTooltip() {
752
- previewTooltip.style.display = 'none';
753
- }
754
-
755
- // ホバー時の時間表示設定
756
- function setupHoverTime() {
757
- const hoverTime = document.createElement('div');
758
- hoverTime.className = 'hover-time';
759
- progressContainer.appendChild(hoverTime);
760
-
761
- progressContainer.addEventListener("mousemove", (e) => {
762
- if (!video.duration) return;
763
-
764
- const rect = progressContainer.getBoundingClientRect();
765
- const pos = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1);
766
- const time = pos * video.duration;
767
-
768
- const minutes = Math.floor(time / 60);
769
- const seconds = Math.floor(time % 60).toString().padStart(2, '0');
770
- hoverTime.textContent = `${minutes}:${seconds}`;
771
- hoverTime.style.display = 'block';
772
- hoverTime.style.left = `${e.clientX - rect.left}px`;
773
 
774
- previewVideo.style.display = "block";
775
- previewVideo.style.left = `${e.clientX}px`;
 
 
776
 
777
- clearTimeout(hoverTimeout);
778
- hoverTimeout = setTimeout(() => {
779
- previewVideo.currentTime = time;
780
- }, 50);
781
- });
782
 
783
- progressContainer.addEventListener("mouseleave", () => {
784
- const hoverTime = document.querySelector('.hover-time');
785
- if (hoverTime) hoverTime.style.display = "none";
786
- previewVideo.style.display = "none";
787
- clearTimeout(hoverTimeout);
788
- });
789
- }
790
-
791
- // 再生速度を更新
792
- function updatePlaybackRate(value) {
793
- const speed = parseFloat(value);
794
- speedInput.value = speed;
795
- speedRange.value = speed;
796
- video.playbackRate = speed;
797
- }
798
-
799
- // 音量を更新
800
- function updateVolume(value) {
801
- const volume = parseFloat(value);
802
- volumeInput.value = volume;
803
- volumeRange.value = volume;
804
- volumeSlider.value = volume;
805
- video.volume = volume;
806
-
807
- if (volume === 0) {
808
- volumeBtn.textContent = '🔇';
809
- } else if (volume < 0.5) {
810
- volumeBtn.textContent = '🔈';
811
- } else {
812
- volumeBtn.textContent = '🔊';
813
  }
814
- }
815
 
816
- // 動画変更処理
817
- function handleVideoChange() {
818
- const selected = videoSelect.value;
819
-
820
- if (selected === 'v-2.mp4') {
821
- const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
822
- if (!confirmPlay) {
823
- videoSelect.value = video.src.split('/').pop();
824
- return;
825
  }
826
  }
827
-
828
- video.src = selected;
829
- previewVideo.src = selected;
830
- video.load();
831
- previewVideo.load();
832
- video.play().catch(e => console.log(e));
833
- }
834
-
835
- // 再生/一時停止を切り替え
836
- function togglePlayPause() {
837
- if (video.paused) {
838
- video.play().then(() => {
839
- playPauseBtn.textContent = '⏸';
840
- }).catch(e => console.log(e));
841
- } else {
842
- video.pause();
843
- playPauseBtn.textContent = '▶';
844
- }
845
- }
846
 
847
- // 進捗バーを更新
848
- function updateProgress() {
849
- if (!video.duration) return;
850
-
851
- const percent = (video.currentTime / video.duration) * 100;
852
- progressBar.style.width = `${percent}%`;
853
-
854
- const currentMinutes = Math.floor(video.currentTime / 60);
855
- const currentSeconds = Math.floor(video.currentTime % 60).toString().padStart(2, '0');
856
- const durationMinutes = Math.floor(video.duration / 60);
857
- const durationSeconds = Math.floor(video.duration % 60).toString().padStart(2, '0');
858
-
859
- timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
860
- }
861
-
862
- // 進捗バーを設定
863
- function setProgress(e) {
864
- const width = progressContainer.clientWidth;
865
- const clickX = e.offsetX;
866
- const duration = video.duration;
867
- video.currentTime = (clickX / width) * duration;
868
- }
869
-
870
- // ミュートを切り替え
871
- function toggleMute() {
872
- video.muted = !video.muted;
873
- if (video.muted) {
874
- volumeBtn.textContent = '🔇';
875
- volumeSlider.value = 0;
876
- } else {
877
- updateVolume(video.volume);
878
  }
879
- }
880
-
881
- // 音量変更を処理
882
- function handleVolumeChange() {
883
- video.muted = false;
884
- updateVolume(volumeSlider.value);
885
- }
886
-
887
- // 全画面表示を切り替え
888
- function goFullscreen() {
889
- if (document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
890
- if (document.exitFullscreen) {
891
- document.exitFullscreen();
892
- } else if (document.webkitExitFullscreen) {
893
- document.webkitExitFullscreen();
894
- } else if (document.msExitFullscreen) {
895
- document.msExitFullscreen();
896
- }
897
- } else {
898
  if (videoContainer.requestFullscreen) {
899
  videoContainer.requestFullscreen();
900
  } else if (videoContainer.webkitRequestFullscreen) {
@@ -903,105 +485,68 @@ function goFullscreen() {
903
  videoContainer.msRequestFullscreen();
904
  }
905
  }
906
- }
907
 
908
- // 全画面コンテキストメニューを表示
909
- function showContextMenu(e) {
910
- if (!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement)) {
911
- return;
912
- }
913
-
914
- e.preventDefault();
915
-
916
- fullscreenContextMenu.style.display = 'block';
917
- fullscreenContextMenu.style.left = `${e.clientX}px`;
918
- fullscreenContextMenu.style.top = `${e.clientY}px`;
919
- }
920
-
921
- // 全画面コンテキストメニューを非表示
922
- function hideContextMenu() {
923
- fullscreenContextMenu.style.display = 'none';
924
- }
925
-
926
- // 音声/字幕のみモードを切り替え
927
- function toggleAudioOnlyMode() {
928
- isAudioOnlyMode = !isAudioOnlyMode;
929
-
930
- if (isAudioOnlyMode) {
931
- videoContainer.classList.add('audio-only-mode');
932
- videoPlaceholder.style.display = 'flex';
933
- video.style.display = 'none';
934
- audioOnlyBtn.textContent = '🎥';
935
- audioOnlyBtn.title = '動画表示モード';
936
- } else {
937
- videoContainer.classList.remove('audio-only-mode');
938
- videoPlaceholder.style.display = 'none';
939
- video.style.display = 'block';
940
- audioOnlyBtn.textContent = '🔈';
941
- audioOnlyBtn.title = '音声/字幕のみ';
942
- }
943
-
944
- hideContextMenu();
945
- }
946
-
947
- // 全画面時の字幕サイズ調整
948
- function updateSubtitleScaleForFullscreen() {
949
- if (document.fullscreenElement || document.webkitFullscreenElement ||
950
- document.mozFullScreenElement || document.msFullscreenElement) {
951
- const fullscreenWidth = window.innerWidth;
952
- const scaleFactor = fullscreenWidth / normalVideoWidth;
953
- document.documentElement.style.setProperty('--fullscreen-scale', scaleFactor);
954
- } else {
955
- document.documentElement.style.setProperty('--fullscreen-scale', 1);
956
  }
957
- }
958
 
959
- // 字幕表示を切り替え
960
- function toggleSubtitles() {
961
- subtitlesEnabled = subtitleToggle.checked;
962
- if (subtitleTrackElement.track) {
963
  subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
 
964
  }
965
- subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
966
- }
967
-
968
- // 字幕サイズを更新
969
- function updateSubtitleSize(value) {
970
- const size = parseFloat(value);
971
- subtitleSizeInput.value = size;
972
- subtitleSize.value = size;
973
- document.documentElement.style.setProperty('--subtitle-scale', size);
974
-
975
- const track = subtitleTrackElement.track;
976
- if (track && track.cues) {
977
- for (let i = 0; i < track.cues.length; i++) {
978
- track.cues[i].line = 90;
979
- track.cues[i].snapToLines = false;
 
 
980
  }
981
  }
982
- }
983
-
984
- // 字幕トラックを変更
985
- function changeSubtitleTrack() {
986
- const selectedTrack = subtitleTrack.value;
987
- subtitleTrackElement.src = selectedTrack;
988
 
989
- if (video.textTracks.length > 0) {
990
- video.textTracks[0].mode = selectedTrack && subtitlesEnabled ? 'showing' : 'hidden';
 
 
 
 
 
 
 
 
991
  }
992
- }
993
 
994
- // 字幕メニューを切り替え
995
- function toggleSubtitleMenu() {
996
- subtitleToggle.checked = !subtitleToggle.checked;
997
- toggleSubtitles();
998
- }
999
 
1000
- // イベントリスナーを設定
1001
- function setupEventListeners() {
1002
  videoSelect.addEventListener('change', handleVideoChange);
1003
 
1004
- ['input', 'change'].forEach(eventName => {
1005
  speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
1006
  volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
1007
  subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
@@ -1024,34 +569,15 @@ function setupEventListeners() {
1024
  video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
1025
  video.addEventListener('pause', () => playPauseBtn.textContent = '▶');
1026
  video.addEventListener('timeupdate', updateProgress);
1027
-
1028
  progressContainer.addEventListener('click', setProgress);
1029
  progressContainer.addEventListener('mousedown', () => isDragging = true);
1030
  document.addEventListener('mouseup', () => isDragging = false);
1031
- progressContainer.addEventListener('mousemove', (e) => {
1032
- if (isDragging) setProgress(e);
1033
- showPreviewTooltip(e);
1034
- });
1035
- progressContainer.addEventListener('mouseenter', showPreviewTooltip);
1036
- progressContainer.addEventListener('mouseleave', hidePreviewTooltip);
1037
-
1038
  volumeBtn.addEventListener('click', toggleMute);
1039
  volumeSlider.addEventListener('input', handleVolumeChange);
1040
  fullscreenBtn.addEventListener('click', goFullscreen);
1041
- audioOnlyBtn.addEventListener('click', toggleAudioOnlyMode);
1042
-
1043
- videoContainer.addEventListener('contextmenu', showContextMenu);
1044
- exitFullscreenBtn.addEventListener('click', () => {
1045
- if (document.exitFullscreen) document.exitFullscreen();
1046
- hideContextMenu();
1047
- });
1048
- showVideoBtn.addEventListener('click', () => {
1049
- if (isAudioOnlyMode) toggleAudioOnlyMode();
1050
- hideContextMenu();
1051
- });
1052
- closeContextMenuBtn.addEventListener('click', hideContextMenu);
1053
- document.addEventListener('click', hideContextMenu);
1054
 
 
1055
  document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
1056
  document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
1057
  document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
@@ -1064,21 +590,14 @@ function setupEventListeners() {
1064
  video.loop = loopCheckbox.checked;
1065
  toggleSubtitles();
1066
  updateProgress();
 
1067
  normalVideoWidth = videoContainer.clientWidth;
1068
- createPreviewCanvas();
1069
  });
1070
- }
1071
-
1072
- // CSS変数を初期設定
1073
- document.documentElement.style.setProperty('--subtitle-scale', '1');
1074
- document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
1075
- document.documentElement.style.setProperty('--fullscreen-scale', '1');
1076
-
1077
- // 初期化
1078
- setupHoverTime();
1079
- setupEventListeners();
1080
- createPreviewCanvas();
1081
- </script>
1082
- </body>
1083
 
 
 
 
 
 
 
1084
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="ja">
 
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <title>動画プレイヤー</title>
6
+ <style>
 
 
 
 
 
7
  body {
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: center;
11
+ background-color: #0a0a12;
12
+ color: #00ffcc;
13
+ font-family: 'Courier New', monospace;
14
+ padding: 20px;
15
+ margin: 0;
16
+ }
17
+
18
+ h1 {
19
+ color: #00aaff;
20
+ text-shadow: 0 0 5px #0066ff;
21
+ border-bottom: 1px solid #0066ff;
22
+ padding-bottom: 10px;
23
+ text-align: center;
24
+ }
25
+
26
+ .video-container {
27
+ position: relative;
28
+ max-width: 800px;
29
+ margin-bottom: 20px;
30
+ margin-top: 30px;
31
+ border: 2px solid #0066ff;
32
+ box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
33
+ background: #000;
34
+ }
35
+
36
+ video {
37
+ width: 100%;
38
+ display: block;
39
+ }
40
+
41
+ /* 字幕スタイル */
42
+ video::cue {
43
+ background-color: rgba(0, 0, 0, 0.7) !important;
44
+ color: #c7dbed !important;
45
+ font-family: 'Courier New', monospace !important;
46
+ text-shadow: 1px 1px 2px #000 !important;
47
+ outline: 3px solid #0b3e8f !important;
48
+ border-radius: 10px !important; /* 角を丸める */
49
+ }
50
+
51
+ /* カスタム動画コントロール */
52
+ video::-webkit-media-controls {
53
+ display: none !important;
54
+ }
55
+
56
+ .custom-controls {
57
+ position: absolute;
58
+ bottom: 0;
59
+ left: 0;
60
+ right: 0;
61
+ background: linear-gradient(to top, rgba(0, 20, 40, 0.9), transparent);
62
+ padding: 10px;
63
+ display: flex;
64
+ flex-direction: column;
65
+ opacity: 0;
66
+ transition: opacity 0.3s;
67
+ }
68
+
69
+ .video-container:hover .custom-controls {
70
+ opacity: 1;
71
+ }
72
+
73
+ .progress-container {
74
+ width: 100%;
75
+ height: 8px;
76
+ background: #001133;
77
+ margin-bottom: 10px;
78
+ cursor: pointer;
79
+ }
80
+
81
+ .progress-bar {
82
+ height: 100%;
83
+ background: #00aaff;
84
+ width: 0%;
85
+ position: relative;
86
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ .progress-bar::after {
89
+ content: '';
90
+ position: absolute;
91
+ right: -5px;
92
+ top: 50%;
93
+ transform: translateY(-50%);
94
+ width: 10px;
95
+ height: 10px;
96
+ background: #00ccff;
97
+ border-radius: 50%;
98
+ box-shadow: 0 0 5px #00ccff;
99
+ }
100
+
101
+ .buttons-container {
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: space-between;
105
+ }
106
+
107
+ .left-controls, .right-controls {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 15px;
111
+ }
112
+
113
+ .control-btn {
114
+ background: none;
115
+ border: none;
116
+ color: #00ccff;
117
+ font-size: 16px;
118
+ cursor: pointer;
119
+ transition: all 0.3s;
120
+ }
121
+
122
+ .control-btn:hover {
123
+ color: #00ffcc;
124
+ text-shadow: 0 0 5px #00ffcc;
125
+ }
126
+
127
+ .time-display {
128
+ font-size: 14px;
129
+ color: #00aaff;
130
+ font-family: 'Courier New', monospace;
131
+ }
132
+
133
+ .volume-container {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 5px;
137
+ }
138
+
139
+ .volume-slider {
140
+ width: 80px;
141
+ -webkit-appearance: none;
142
+ height: 4px;
143
+ background: #001133;
144
+ outline: none;
145
+ }
146
+
147
+ .volume-slider::-webkit-slider-thumb {
148
+ -webkit-appearance: none;
149
+ width: 12px;
150
+ height: 12px;
151
+ background: #00aaff;
152
+ border-radius: 50%;
153
+ cursor: pointer;
154
+ }
155
+
156
+ .controls {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 15px;
160
+ width: 100%;
161
+ max-width: 800px;
162
+ background-color: #0f0f1a;
163
+ padding: 20px;
164
+ border: 1px solid #0066ff;
165
+ box-shadow: 0 0 15px rgba(0, 102, 255, 0.3);
166
+ }
167
+
168
+ .control-group {
169
+ display: flex;
170
+ flex-direction: row;
171
+ align-items: center;
172
+ justify-content: flex-start;
173
+ gap: 10px;
174
+ flex-wrap: nowrap;
175
+ }
176
+
177
+ .control-group label {
178
+ white-space: nowrap;
179
+ min-width: 100px;
180
+ text-align: right;
181
+ color: #00ccff;
182
+ }
183
+
184
+ input[type="range"] {
185
+ flex-grow: 1;
186
+ -webkit-appearance: none;
187
+ height: 8px;
188
+ background: #001133;
189
+ border-radius: 5px;
190
+ outline: none;
191
+ }
192
+
193
+ input[type="range"]::-webkit-slider-thumb {
194
+ -webkit-appearance: none;
195
+ width: 18px;
196
+ height: 18px;
197
+ background: #00aaff;
198
+ border-radius: 50%;
199
+ cursor: pointer;
200
+ box-shadow: 0 0 5px #00aaff;
201
+ }
202
+
203
+ input[type="number"], select {
204
+ background-color: #001133;
205
+ color: #00ccff;
206
+ border: 1px solid #0066ff;
207
+ padding: 5px;
208
+ font-family: 'Courier New', monospace;
209
+ }
210
+
211
+ button {
212
+ background-color: #001133;
213
+ color: #00ccff;
214
+ border: 1px solid #0066ff;
215
+ padding: 8px 15px;
216
+ cursor: pointer;
217
+ font-family: 'Courier New', monospace;
218
+ transition: all 0.3s;
219
+ align-self: flex-start;
220
+ }
221
+
222
+ button:hover {
223
+ background-color: #0066ff;
224
+ color: #000;
225
+ box-shadow: 0 0 10px #0066ff;
226
+ }
227
+
228
+ select {
229
+ width: 300px;
230
+ background-color: #001133;
231
+ color: #00ccff;
232
+ border: 1px solid #0066ff;
233
+ padding: 5px;
234
+ }
235
+
236
+ input[type="checkbox"] {
237
+ -webkit-appearance: none;
238
+ width: 18px;
239
+ height: 18px;
240
+ background: #001133;
241
+ border: 1px solid #0066ff;
242
+ position: relative;
243
+ }
244
+
245
+ input[type="checkbox"]:checked {
246
+ background: #0066ff;
247
+ box-shadow: 0 0 5px #0066ff;
248
+ }
249
+
250
+ input[type="checkbox"]:checked::after {
251
+ content: "✓";
252
+ position: absolute;
253
+ color: #000;
254
+ font-size: 14px;
255
+ top: 50%;
256
+ left: 50%;
257
+ transform: translate(-50%, -50%);
258
+ }
259
+
260
+ /* 字幕設定用スタイル */
261
+ .subtitle-settings {
262
+ margin-top: 10px;
263
+ padding: 10px;
264
+ background-color: rgba(0, 20, 40, 0.5);
265
+ border: 1px solid #0066ff;
266
+ }
267
+
268
+ /* 字幕サイズ調整用のCSS変数 */
269
+ :root {
270
+ --subtitle-scale: 1;
271
+ --subtitle-border-radius: 10px;
272
+ }
273
+
274
+ video::cue {
275
+ font-size: calc(16px * var(--subtitle-scale)) !important;
276
+ line-height: 1.5 !important;
277
+ border-radius: var(--subtitle-border-radius) !important;
278
+ }
279
+
280
+ /* 全画面時の字幕サイズ調整 */
281
+ .video-container:fullscreen video::cue,
282
+ .video-container:-webkit-full-screen video::cue,
283
+ .video-container:-moz-full-screen video::cue,
284
+ .video-container:-ms-fullscreen video::cue {
285
+ font-size: calc(16px * var(--subtitle-scale) * var(--fullscreen-scale, 1)) !important;
286
+ }
287
+ </style>
288
+ </head>
289
  <body>
290
+ <h1>ラジオ体操動画プレイヤー<br>For Kushihara</h1>
291
+
292
+ <div class="controls">
293
+ <div class="control-group">
294
+ <label for="videoSelect">動画の音量:</label>
295
+ <select id="videoSelect">
296
+ <option value="v.mp4">小</option>
297
+ <option value="v-2.mp4">大(+50dB)</option>
298
+ </select>
 
 
 
 
 
 
 
299
  </div>
300
+ <div class="control-group">
301
+ <label for="speedRange">再生速度:</label>
302
+ <input type="range" id="speedRange" min="0.0001" max="20" step="0.0001" value="1" style="width:700px !important;">
303
+ <input type="number" id="speedInput" min="0.0001" step="0.0001" value="1">
304
  </div>
305
+
306
+ <div class="control-group">
307
+ <label for="volumeRange">音量:</label>
308
+ <input type="range" id="volumeRange" min="0" max="1" step="0.01" value="1">
309
+ <input type="number" id="volumeInput" min="0" max="1" step="0.01" value="1">
310
  </div>
311
+
312
+ <div class="control-group">
313
+ <label for="loopCheckbox">ループ再生:</label>
314
+ <input type="checkbox" id="loopCheckbox" checked>
 
 
 
315
  </div>
316
+
317
+ <!-- 字幕設定セクション -->
318
+ <div class="subtitle-settings">
319
+ <div class="control-group">
320
+ <label for="subtitleToggle">字幕表示:</label>
321
+ <input type="checkbox" id="subtitleToggle" checked>
322
+ </div>
323
+ <div class="control-group">
324
+ <label for="subtitleSize">文字サイズ:</label>
325
+ <input type="range" id="subtitleSize" min="0.5" max="2" step="0.1" value="1">
326
+ <input type="number" id="subtitleSizeInput" min="0.5" max="2" step="0.1" value="1">
327
+ </div>
328
+ <div class="control-group">
329
+ <label for="subtitleTrack">字幕トラック:</label>
330
+ <select id="subtitleTrack">
331
+ <option value="v.vtt">日本語</option>
332
+ <option value="">字幕なし</option>
333
+ </select>
334
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  </div>
336
+
337
+ <button onclick="goFullscreen()">全画面</button>
338
+ </div>
339
+
340
+ <div class="video-container">
341
+ <video id="videoPlayer" src="v.mp4">
342
+ <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
343
+ </video>
344
+ <div class="custom-controls">
345
+ <div class="progress-container" id="progressContainer">
346
+ <div class="progress-bar" id="progressBar"></div>
347
+ </div>
348
+ <div class="buttons-container">
349
+ <div class="left-controls">
350
+ <button class="control-btn" id="playPauseBtn">▶</button>
351
+ <span class="time-display" id="timeDisplay">00:00 / 00:00</span>
352
  </div>
353
+ <div class="right-controls">
354
+ <div class="volume-container">
355
+ <button class="control-btn" id="volumeBtn">🔊</button>
356
+ <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
357
+ </div>
358
+ <button class="control-btn" id="subtitleBtn" title="字幕">📝</button>
359
+ <button class="control-btn" id="fullscreenBtn">⛶</button>
 
 
 
 
 
 
 
 
 
 
 
 
360
  </div>
361
+ </div>
362
  </div>
363
+ </div>
364
+
365
+ <script>
366
+ const video = document.getElementById('videoPlayer');
367
+ const videoSelect = document.getElementById('videoSelect');
368
+ const speedRange = document.getElementById('speedRange');
369
+ const speedInput = document.getElementById('speedInput');
370
+ const volumeRange = document.getElementById('volumeRange');
371
+ const volumeInput = document.getElementById('volumeInput');
372
+ const loopCheckbox = document.getElementById('loopCheckbox');
373
+ const playPauseBtn = document.getElementById('playPauseBtn');
374
+ const progressBar = document.getElementById('progressBar');
375
+ const progressContainer = document.getElementById('progressContainer');
376
+ const timeDisplay = document.getElementById('timeDisplay');
377
+ const volumeBtn = document.getElementById('volumeBtn');
378
+ const volumeSlider = document.getElementById('volumeSlider');
379
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
380
+ const subtitleBtn = document.getElementById('subtitleBtn');
381
+ const subtitleToggle = document.getElementById('subtitleToggle');
382
+ const subtitleSize = document.getElementById('subtitleSize');
383
+ const subtitleSizeInput = document.getElementById('subtitleSizeInput');
384
+ const subtitleTrack = document.getElementById('subtitleTrack');
385
+ const subtitleTrackElement = document.getElementById('subtitleTrackElement');
386
+ const videoContainer = document.querySelector('.video-container');
387
+
388
+ // 初期設定
389
+ video.controls = false;
390
+ let isDragging = false;
391
+ let subtitlesEnabled = true;
392
+ let normalVideoWidth = videoContainer.clientWidth;
393
+
394
+ function updatePlaybackRate(value) {
395
+ const speed = parseFloat(value);
396
+ speedInput.value = speed;
397
+ speedRange.value = speed;
398
+ video.playbackRate = speed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  }
400
+
401
+ function updateVolume(value) {
402
+ const volume = parseFloat(value);
403
+ volumeInput.value = volume;
404
+ volumeRange.value = volume;
405
+ volumeSlider.value = volume;
406
+ video.volume = volume;
407
+
408
+ if (volume === 0) {
409
+ volumeBtn.textContent = '🔇';
410
+ } else if (volume < 0.5) {
411
+ volumeBtn.textContent = '🔈';
412
+ } else {
413
+ volumeBtn.textContent = '🔊';
414
+ }
415
  }
 
416
 
417
+ function handleVideoChange() {
418
+ const selected = videoSelect.value;
419
+
420
+ if (selected === 'v-2.mp4') {
421
+ const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
422
+ if (!confirmPlay) {
423
+ videoSelect.value = video.src.split('/').pop();
424
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  }
426
+ }
427
+
428
+ video.src = selected;
429
+ video.load();
430
+ video.play().then(() => {
431
+ playPauseBtn.textContent = '';
432
+ }).catch(e => console.log(e));
 
 
433
  }
 
434
 
435
+ function togglePlayPause() {
436
+ if (video.paused) {
437
+ video.play();
438
+ playPauseBtn.textContent = '⏸';
439
+ } else {
440
+ video.pause();
441
+ playPauseBtn.textContent = '▶';
442
+ }
443
+ }
444
+
445
+ function updateProgress() {
446
+ const percent = (video.currentTime / video.duration) * 100;
447
+ progressBar.style.width = `${percent}%`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
 
449
+ const currentMinutes = Math.floor(video.currentTime / 60);
450
+ const currentSeconds = Math.floor(video.currentTime % 60).toString().padStart(2, '0');
451
+ const durationMinutes = Math.floor(video.duration / 60);
452
+ const durationSeconds = Math.floor(video.duration % 60).toString().padStart(2, '0');
453
 
454
+ timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
455
+ }
 
 
 
456
 
457
+ function setProgress(e) {
458
+ const width = progressContainer.clientWidth;
459
+ const clickX = e.offsetX;
460
+ const duration = video.duration;
461
+ video.currentTime = (clickX / width) * duration;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  }
 
463
 
464
+ function toggleMute() {
465
+ video.muted = !video.muted;
466
+ if (video.muted) {
467
+ volumeBtn.textContent = '🔇';
468
+ volumeSlider.value = 0;
469
+ } else {
470
+ updateVolume(video.volume);
 
 
471
  }
472
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
+ function handleVolumeChange() {
475
+ video.muted = false;
476
+ updateVolume(volumeSlider.value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  }
478
+
479
+ function goFullscreen() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  if (videoContainer.requestFullscreen) {
481
  videoContainer.requestFullscreen();
482
  } else if (videoContainer.webkitRequestFullscreen) {
 
485
  videoContainer.msRequestFullscreen();
486
  }
487
  }
 
488
 
489
+ // 全画面変更時の字幕サイズ調整
490
+ function updateSubtitleScaleForFullscreen() {
491
+ if (document.fullscreenElement || document.webkitFullscreenElement ||
492
+ document.mozFullScreenElement || document.msFullscreenElement) {
493
+ // 全画面モード
494
+ const fullscreenWidth = window.innerWidth;
495
+ const scaleFactor = fullscreenWidth / normalVideoWidth;
496
+ document.documentElement.style.setProperty('--fullscreen-scale', scaleFactor);
497
+ } else {
498
+ // 通常モード
499
+ document.documentElement.style.setProperty('--fullscreen-scale', 1);
500
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  }
 
502
 
503
+ // 字幕関連の関数
504
+ function toggleSubtitles() {
505
+ subtitlesEnabled = subtitleToggle.checked;
 
506
  subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
507
+ subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
508
  }
509
+
510
+ function updateSubtitleSize(value) {
511
+ const size = parseFloat(value);
512
+ subtitleSizeInput.value = size;
513
+ subtitleSize.value = size;
514
+
515
+ // CSS変数で字幕サイズを制御
516
+ document.documentElement.style.setProperty('--subtitle-scale', size);
517
+
518
+ // VTTCueのlineプロパティには数値のみを設定('bottom'は無効)
519
+ const track = subtitleTrackElement.track;
520
+ if (track && track.cues) {
521
+ for (let i = 0; i < track.cues.length; i++) {
522
+ // 画面下部に表示するため、適切な数値を設定(例: 90
523
+ track.cues[i].line = 90;
524
+ track.cues[i].snapToLines = false; // ラインスナップを無効に
525
+ }
526
  }
527
  }
 
 
 
 
 
 
528
 
529
+ function changeSubtitleTrack() {
530
+ const selectedTrack = subtitleTrack.value;
531
+ subtitleTrackElement.src = selectedTrack;
532
+ subtitleTrackElement.track.mode = selectedTrack && subtitlesEnabled ? 'showing' : 'hidden';
533
+
534
+ // トラック変更後に再度読み込み
535
+ video.textTracks[0].mode = 'hidden';
536
+ if (selectedTrack) {
537
+ video.textTracks[0].mode = subtitlesEnabled ? 'showing' : 'hidden';
538
+ }
539
  }
 
540
 
541
+ function toggleSubtitleMenu() {
542
+ const subtitleSettings = document.querySelector('.subtitle-settings');
543
+ subtitleSettings.style.display = subtitleSettings.style.display === 'none' ? 'block' : 'none';
544
+ }
 
545
 
546
+ // イベントリスナー
 
547
  videoSelect.addEventListener('change', handleVideoChange);
548
 
549
+ ['input', 'change', 'mouseup'].forEach(eventName => {
550
  speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
551
  volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
552
  subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
 
569
  video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
570
  video.addEventListener('pause', () => playPauseBtn.textContent = '▶');
571
  video.addEventListener('timeupdate', updateProgress);
 
572
  progressContainer.addEventListener('click', setProgress);
573
  progressContainer.addEventListener('mousedown', () => isDragging = true);
574
  document.addEventListener('mouseup', () => isDragging = false);
575
+ progressContainer.addEventListener('mousemove', (e) => isDragging && setProgress(e));
 
 
 
 
 
 
576
  volumeBtn.addEventListener('click', toggleMute);
577
  volumeSlider.addEventListener('input', handleVolumeChange);
578
  fullscreenBtn.addEventListener('click', goFullscreen);
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
580
+ // 全画面変更イベントを監視
581
  document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
582
  document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
583
  document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
 
590
  video.loop = loopCheckbox.checked;
591
  toggleSubtitles();
592
  updateProgress();
593
+ // 通常時の動画幅を記録
594
  normalVideoWidth = videoContainer.clientWidth;
 
595
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
+ // CSS変数を設定
598
+ document.documentElement.style.setProperty('--subtitle-scale', '1');
599
+ document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
600
+ document.documentElement.style.setProperty('--fullscreen-scale', '1');
601
+ </script>
602
+ </body>
603
  </html>