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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +414 -243
index.html CHANGED
@@ -1,18 +1,24 @@
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 {
@@ -42,7 +48,7 @@
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; /* 角を丸める */
@@ -58,7 +64,7 @@
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;
@@ -127,7 +133,8 @@
127
  .time-display {
128
  font-size: 14px;
129
  color: #00aaff;
130
- font-family: 'Courier New', monospace;
 
131
  }
132
 
133
  .volume-container {
@@ -205,7 +212,7 @@
205
  color: #00ccff;
206
  border: 1px solid #0066ff;
207
  padding: 5px;
208
- font-family: 'Courier New', monospace;
209
  }
210
 
211
  button {
@@ -214,7 +221,7 @@
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
  }
@@ -284,11 +291,161 @@
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>
@@ -302,18 +459,15 @@
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">
@@ -322,8 +476,8 @@
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>
@@ -333,17 +487,17 @@
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">
@@ -355,249 +509,266 @@
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) {
483
- videoContainer.webkitRequestFullscreen();
484
- } else if (videoContainer.msRequestFullscreen) {
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));
553
- });
554
-
555
- speedInput.addEventListener('input', () => updatePlaybackRate(speedInput.value));
556
- volumeInput.addEventListener('input', () => updateVolume(volumeInput.value));
557
- subtitleSizeInput.addEventListener('input', () => updateSubtitleSize(subtitleSizeInput.value));
558
-
559
- loopCheckbox.addEventListener('change', () => {
560
- video.loop = loopCheckbox.checked;
561
- });
562
-
563
- subtitleToggle.addEventListener('change', toggleSubtitles);
564
- subtitleTrack.addEventListener('change', changeSubtitleTrack);
565
- subtitleBtn.addEventListener('click', toggleSubtitleMenu);
566
-
567
- playPauseBtn.addEventListener('click', togglePlayPause);
568
- video.addEventListener('click', togglePlayPause);
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);
584
- document.addEventListener('MSFullscreenChange', updateSubtitleScaleForFullscreen);
585
-
586
- video.addEventListener('loadedmetadata', () => {
587
- updatePlaybackRate(speedRange.value);
588
- updateVolume(volumeRange.value);
589
- updateSubtitleSize(subtitleSize.value);
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>
 
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
+ <style>
12
+ body {
13
  display: flex;
14
  flex-direction: column;
15
  align-items: center;
16
  background-color: #0a0a12;
17
  color: #00ffcc;
18
+ font-family: "M PLUS Rounded 1c", monospace;
19
  padding: 20px;
20
  margin: 0;
21
+ overflow-x: hidden; /* 横スクロール禁止 */
22
  }
23
 
24
  h1 {
 
48
  video::cue {
49
  background-color: rgba(0, 0, 0, 0.7) !important;
50
  color: #c7dbed !important;
51
+ font-family: "M PLUS Rounded 1c", monospace !important;
52
  text-shadow: 1px 1px 2px #000 !important;
53
  outline: 3px solid #0b3e8f !important;
54
  border-radius: 10px !important; /* 角を丸める */
 
64
  bottom: 0;
65
  left: 0;
66
  right: 0;
67
+ /*background: linear-gradient(to top, rgba(0, 20, 40, 0.9), transparent);*/
68
  padding: 10px;
69
  display: flex;
70
  flex-direction: column;
 
133
  .time-display {
134
  font-size: 14px;
135
  color: #00aaff;
136
+ box-shadow: 0.1px 0.1px 0.1px black;
137
+ font-family: "M PLUS Rounded 1c", monospace;
138
  }
139
 
140
  .volume-container {
 
212
  color: #00ccff;
213
  border: 1px solid #0066ff;
214
  padding: 5px;
215
+ font-family: "M PLUS Rounded 1c", monospace;
216
  }
217
 
218
  button {
 
221
  border: 1px solid #0066ff;
222
  padding: 8px 15px;
223
  cursor: pointer;
224
+ font-family: "M PLUS Rounded 1c", monospace;
225
  transition: all 0.3s;
226
  align-self: flex-start;
227
  }
 
291
  .video-container:-ms-fullscreen video::cue {
292
  font-size: calc(16px * var(--subtitle-scale) * var(--fullscreen-scale, 1)) !important;
293
  }
294
+ body {
295
+ margin: 0;
296
+ padding: 0;
297
+ background-color: #0a192f; /* 暗い青 */
298
+ height: 100vh;
299
+ width: 100vw;
300
+ }
301
+
302
+ .ripple {
303
+ position: absolute;
304
+ border-radius: 50%;
305
+ background: transparent;
306
+ border: 1px solid rgba(100, 210, 255, 0.3); /* 半透明の薄い水色 */
307
+ transform: translate(-50%, -50%);
308
+ pointer-events: none;
309
+ animation: ripple-animation 4s ease-out forwards;
310
+ z-index: -1;
311
+ position: absolute; /* または fixed / relative、必要に応じて調整 */
312
+ }
313
+
314
+ @keyframes ripple-animation {
315
+ 0% {
316
+ width: 0;
317
+ height: 0;
318
+ opacity: 0.6;
319
+ }
320
+ 100% {
321
+ width: 600px;
322
+ height: 600px;
323
+ opacity: 0;
324
+ }
325
+ }
326
  </style>
327
  </head>
328
+
329
  <body>
330
+ <div id="ripple-container">
331
+ </div>
332
+ <script>
333
+ document.addEventListener('DOMContentLoaded', function() {
334
+ const container = document.getElementById('ripple-container');
335
+
336
+ function createRipple() {
337
+ const ripple = document.createElement('div');
338
+ ripple.classList.add('ripple');
339
+
340
+ // ランダムな位置
341
+ const posX = Math.random() * 100;
342
+ const posY = Math.random() * 100;
343
+
344
+ // ランダムなサイズとアニメーション時間
345
+ const maxSize = 400 + Math.random() * 500;
346
+ const duration = 3 + Math.random() * 3;
347
+
348
+ // 半透明の薄い水色のバリエーション
349
+ const hue = 190 + Math.random() * 20 - 10; // 水色を中心に少し変化
350
+ const saturation = 80 + Math.random() * 15;
351
+ const lightness = 70 + Math.random() * 20;
352
+ const opacity = 0.3 + Math.random() * 0.3;
353
+
354
+ ripple.style.left = `${posX}%`;
355
+ ripple.style.top = `${posY}%`;
356
+ ripple.style.borderColor = `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`;
357
+ ripple.style.animationDuration = `${duration}s`;
358
+
359
+ // キーフレームを動的に変更
360
+ const animationName = `ripple-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
361
+ ripple.style.animationName = animationName;
362
+
363
+ const style = document.createElement('style');
364
+ style.innerHTML = `
365
+ @keyframes ${animationName} {
366
+ 0% {
367
+ width: 0;
368
+ height: 0;
369
+ opacity: ${opacity};
370
+ }
371
+ 100% {
372
+ width: ${maxSize}px;
373
+ height: ${maxSize}px;
374
+ opacity: 0;
375
+ }
376
+ }
377
+ `;
378
+ document.head.appendChild(style);
379
+
380
+ container.appendChild(ripple);
381
+
382
+ // アニメーション終了後に要素を削除
383
+ ripple.addEventListener('animationend', function() {
384
+ ripple.remove();
385
+ style.remove();
386
+ });
387
+ }
388
+
389
+ // 最初の波紋をいくつか作成
390
+ for (let i = 0; i < 8; i++) {
391
+ setTimeout(createRipple, i * 600);
392
+ }
393
+
394
+ // 定期的に新しい波紋を作成
395
+ setInterval(createRipple, 1200);
396
+
397
+ // クリックでも波紋を作成
398
+ document.addEventListener('click', function(e) {
399
+ createRippleAtPosition(e.clientX, e.clientY);
400
+ });
401
+
402
+ function createRippleAtPosition(x, y) {
403
+ const ripple = document.createElement('div');
404
+ ripple.classList.add('ripple');
405
+
406
+ const maxSize = 400 + Math.random() * 500;
407
+ const duration = 3 + Math.random() * 3;
408
+ const hue = 190 + Math.random() * 20 - 10;
409
+ const saturation = 80 + Math.random() * 15;
410
+ const lightness = 70 + Math.random() * 20;
411
+ const opacity = 0.3 + Math.random() * 0.3;
412
+
413
+ ripple.style.left = `${x}px`;
414
+ ripple.style.top = `${y}px`;
415
+ ripple.style.borderColor = `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`;
416
+ ripple.style.animationDuration = `${duration}s`;
417
+
418
+ const animationName = `ripple-click-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
419
+ ripple.style.animationName = animationName;
420
+
421
+ const style = document.createElement('style');
422
+ style.innerHTML = `
423
+ @keyframes ${animationName} {
424
+ 0% {
425
+ width: 0;
426
+ height: 0;
427
+ opacity: ${opacity};
428
+ }
429
+ 100% {
430
+ width: ${maxSize}px;
431
+ height: ${maxSize}px;
432
+ opacity: 0;
433
+ }
434
+ }
435
+ `;
436
+ document.head.appendChild(style);
437
+
438
+ container.appendChild(ripple);
439
+
440
+ ripple.addEventListener('animationend', function() {
441
+ ripple.remove();
442
+ style.remove();
443
+ });
444
+ }
445
+ });
446
+ </script>
447
+ <h1>ラジオ体操動画プレイヤー
448
+ <br>For Kushihara</h1>
449
  <div class="controls">
450
  <div class="control-group">
451
  <label for="videoSelect">動画の音量:</label>
 
459
  <input type="range" id="speedRange" min="0.0001" max="20" step="0.0001" value="1" style="width:700px !important;">
460
  <input type="number" id="speedInput" min="0.0001" step="0.0001" value="1">
461
  </div>
 
462
  <div class="control-group">
463
  <label for="volumeRange">音量:</label>
464
  <input type="range" id="volumeRange" min="0" max="1" step="0.01" value="1">
465
  <input type="number" id="volumeInput" min="0" max="1" step="0.01" value="1">
466
  </div>
 
467
  <div class="control-group">
468
  <label for="loopCheckbox">ループ再生:</label>
469
  <input type="checkbox" id="loopCheckbox" checked>
470
  </div>
 
471
  <!-- 字幕設定セクション -->
472
  <div class="subtitle-settings">
473
  <div class="control-group">
 
476
  </div>
477
  <div class="control-group">
478
  <label for="subtitleSize">文字サイズ:</label>
479
+ <input type="range" id="subtitleSize" min="0.5" max="5" step="0.1" value="1.5">
480
+ <input type="number" id="subtitleSizeInput" min="0.01" step="0.01" value="1.5">
481
  </div>
482
  <div class="control-group">
483
  <label for="subtitleTrack">字幕トラック:</label>
 
487
  </select>
488
  </div>
489
  </div>
 
490
  <button onclick="goFullscreen()">全画面</button>
491
  </div>
 
492
  <div class="video-container">
493
  <video id="videoPlayer" src="v.mp4">
494
  <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
495
+ </track>
496
  </video>
497
  <div class="custom-controls">
498
  <div class="progress-container" id="progressContainer">
499
+ <div class="progress-bar" id="progressBar">
500
+ </div>
501
  </div>
502
  <div class="buttons-container">
503
  <div class="left-controls">
 
509
  <button class="control-btn" id="volumeBtn">🔊</button>
510
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
511
  </div>
512
+ <button class="control-btn" id="subtitleBtn" title="字幕">🔤</button>
513
  <button class="control-btn" id="fullscreenBtn">⛶</button>
514
  </div>
515
  </div>
516
  </div>
517
  </div>
518
+ <script>
 
519
  const video = document.getElementById('videoPlayer');
520
+ const videoSelect = document.getElementById('videoSelect');
521
+ const speedRange = document.getElementById('speedRange');
522
+ const speedInput = document.getElementById('speedInput');
523
+ const volumeRange = document.getElementById('volumeRange');
524
+ const volumeInput = document.getElementById('volumeInput');
525
+ const loopCheckbox = document.getElementById('loopCheckbox');
526
+ const playPauseBtn = document.getElementById('playPauseBtn');
527
+ const progressBar = document.getElementById('progressBar');
528
+ const progressContainer = document.getElementById('progressContainer');
529
+ const timeDisplay = document.getElementById('timeDisplay');
530
+ const volumeBtn = document.getElementById('volumeBtn');
531
+ const volumeSlider = document.getElementById('volumeSlider');
532
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
533
+ const subtitleBtn = document.getElementById('subtitleBtn');
534
+ const subtitleToggle = document.getElementById('subtitleToggle');
535
+ const subtitleSize = document.getElementById('subtitleSize');
536
+ const subtitleSizeInput = document.getElementById('subtitleSizeInput');
537
+ const subtitleTrack = document.getElementById('subtitleTrack');
538
+ const subtitleTrackElement = document.getElementById('subtitleTrackElement');
539
+ const videoContainer = document.querySelector('.video-container');
540
+
541
+ // 初期設定
542
+ video.controls = false;
543
+ let isDragging = false;
544
+ let subtitlesEnabled = true;
545
+ let normalVideoWidth = videoContainer.clientWidth;
546
+
547
+ function updatePlaybackRate(value) {
548
+ const speed = parseFloat(value);
549
+ speedInput.value = speed;
550
+ speedRange.value = speed;
551
+ video.playbackRate = speed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  }
553
+
554
+ function updateVolume(value) {
555
+ const volume = parseFloat(value);
556
+ volumeInput.value = volume;
557
+ volumeRange.value = volume;
558
+ volumeSlider.value = volume;
559
+ video.volume = volume;
560
+
561
+ if (volume === 0) {
562
+ volumeBtn.textContent = '🔇';
563
+ } else if (volume < 0.5) {
564
+ volumeBtn.textContent = '🔈';
565
+ } else {
566
+ volumeBtn.textContent = '🔊';
567
  }
568
  }
569
+
570
+ function handleVideoChange() {
571
+ const selected = videoSelect.value;
572
+
573
+ if (selected === 'v-2.mp4') {
574
+ const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
575
+ if (!confirmPlay) {
576
+ videoSelect.value = video.src.split('/').pop();
577
+ return;
578
+ }
579
+ }
580
+
581
+ video.src = selected;
582
+ video.load();
583
+ video.play().then(() => {
584
+ playPauseBtn.textContent = '⏸';
585
+ }).catch(e => console.log(e));
586
  }
587
+
588
+ function togglePlayPause() {
589
+ if (video.paused) {
590
+ video.play();
591
+ playPauseBtn.textContent = '⏸';
592
+ } else {
593
+ video.pause();
594
+ playPauseBtn.textContent = '';
595
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  }
597
+
598
+ function updateProgress() {
599
+ const percent = (video.currentTime / video.duration) * 100;
600
+ progressBar.style.width = `${percent}%`;
601
+
602
+ const currentMinutes = Math.floor(video.currentTime / 60);
603
+ const currentSeconds = Math.floor(video.currentTime % 60).toString().padStart(2, '0');
604
+ const durationMinutes = Math.floor(video.duration / 60);
605
+ const durationSeconds = Math.floor(video.duration % 60).toString().padStart(2, '0');
606
+
607
+ timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
 
 
 
608
  }
609
+
610
+ function setProgress(e) {
611
+ const width = progressContainer.clientWidth;
612
+ const clickX = e.offsetX;
613
+ const duration = video.duration;
614
+ video.currentTime = (clickX / width) * duration;
615
+ }
616
+
617
+ function toggleMute() {
618
+ video.muted = !video.muted;
619
+ if (video.muted) {
620
+ volumeBtn.textContent = '🔇';
621
+ volumeSlider.value = 0;
622
+ } else {
623
+ updateVolume(video.volume);
624
+ }
625
+ }
626
+
627
+ function handleVolumeChange() {
628
+ video.muted = false;
629
+ updateVolume(volumeSlider.value);
630
+ }
631
+
632
+ function goFullscreen() {
633
+ if (
634
+ document.fullscreenElement ||
635
+ document.webkitFullscreenElement ||
636
+ document.msFullscreenElement
637
+ ) {
638
+ // フルスクリーンを解除
639
+ if (document.exitFullscreen) {
640
+ document.exitFullscreen();
641
+ } else if (document.webkitExitFullscreen) {
642
+ document.webkitExitFullscreen();
643
+ } else if (document.msExitFullscreen) {
644
+ document.msExitFullscreen();
645
+ }
646
  } else {
647
+ // フルスクリーンにする
648
+ if (videoContainer.requestFullscreen) {
649
+ videoContainer.requestFullscreen();
650
+ } else if (videoContainer.webkitRequestFullscreen) {
651
+ videoContainer.webkitRequestFullscreen();
652
+ } else if (videoContainer.msRequestFullscreen) {
653
+ videoContainer.msRequestFullscreen();
654
+ }
655
  }
656
  }
657
+
658
+
659
+ // 全画面変更時の字幕サイズ調整
660
+ function updateSubtitleScaleForFullscreen() {
661
+ if (document.fullscreenElement || document.webkitFullscreenElement ||
662
+ document.mozFullScreenElement || document.msFullscreenElement) {
663
+ // 全画面モード
664
+ const fullscreenWidth = window.innerWidth;
665
+ const scaleFactor = fullscreenWidth / normalVideoWidth;
666
+ document.documentElement.style.setProperty('--fullscreen-scale', scaleFactor);
667
+ } else {
668
+ // 通常モード
669
+ document.documentElement.style.setProperty('--fullscreen-scale', 1);
 
 
 
 
 
 
 
 
 
 
670
  }
671
  }
 
672
 
673
+ // 字幕関連の関数
674
+ function toggleSubtitles() {
675
+ subtitlesEnabled = subtitleToggle.checked;
676
+ subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
677
+ subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
678
+ }
679
+
680
+ function updateSubtitleSize(value) {
681
+ const size = parseFloat(value);
682
+ subtitleSizeInput.value = size;
683
+ subtitleSize.value = size;
684
+
685
+ // CSS変数で字幕サイズを制御
686
+ document.documentElement.style.setProperty('--subtitle-scale', size);
687
+
688
+ // VTTCueのlineプロパティには数値のみを設定('bottom'は無効)
689
+ const track = subtitleTrackElement.track;
690
+ if (track && track.cues) {
691
+ for (let i = 0; i < track.cues.length; i++) {
692
+ // 画面下部に表示するため、適切な数値を設定(例: 90)
693
+ track.cues[i].line = 90;
694
+ track.cues[i].snapToLines = false; // ラインスナップを無効に
695
+ }
696
+ }
697
+ }
698
 
699
+ function changeSubtitleTrack() {
700
+ const selectedTrack = subtitleTrack.value;
701
+ subtitleTrackElement.src = selectedTrack;
702
+ subtitleTrackElement.track.mode = selectedTrack && subtitlesEnabled ? 'showing' : 'hidden';
703
+
704
+ // トラック変更後に再度読み込み
705
+ video.textTracks[0].mode = 'hidden';
706
+ if (selectedTrack) {
707
+ video.textTracks[0].mode = subtitlesEnabled ? 'showing' : 'hidden';
708
+ }
709
  }
710
+
711
+ function toggleSubtitleMenu() {
712
+ document.getElementById('subtitleToggle').checked ^= true;
713
+ toggleSubtitles();
714
+ }
715
+
716
+ // イベントリスナー
717
+ videoSelect.addEventListener('change', handleVideoChange);
718
+
719
+ ['input', 'change', 'mouseup'].forEach(eventName => {
720
+ speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
721
+ volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
722
+ subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
723
+ });
724
+
725
+ speedInput.addEventListener('input', () => updatePlaybackRate(speedInput.value));
726
+ volumeInput.addEventListener('input', () => updateVolume(volumeInput.value));
727
+ subtitleSizeInput.addEventListener('input', () => updateSubtitleSize(subtitleSizeInput.value));
728
+
729
+ loopCheckbox.addEventListener('change', () => {
730
+ video.loop = loopCheckbox.checked;
731
+ });
732
+
733
+ subtitleToggle.addEventListener('change', toggleSubtitles);
734
+ subtitleTrack.addEventListener('change', changeSubtitleTrack);
735
+ subtitleBtn.addEventListener('click', toggleSubtitleMenu);
736
+
737
+ playPauseBtn.addEventListener('click', togglePlayPause);
738
+ video.addEventListener('click', togglePlayPause);
739
+ video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
740
+ video.addEventListener('pause', () => playPauseBtn.textContent = '');
741
+ video.addEventListener('timeupdate', updateProgress);
742
+ progressContainer.addEventListener('click', setProgress);
743
+ progressContainer.addEventListener('mousedown', () => isDragging = true);
744
+ document.addEventListener('mouseup', () => isDragging = false);
745
+ progressContainer.addEventListener('mousemove', (e) => isDragging && setProgress(e));
746
+ volumeBtn.addEventListener('click', toggleMute);
747
+ volumeSlider.addEventListener('input', handleVolumeChange);
748
+ fullscreenBtn.addEventListener('click', goFullscreen);
749
+
750
+ // 全画面変更イベントを監視
751
+ document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
752
+ document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
753
+ document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
754
+ document.addEventListener('MSFullscreenChange', updateSubtitleScaleForFullscreen);
755
+
756
+ video.addEventListener('loadedmetadata', () => {
757
+ updatePlaybackRate(speedRange.value);
758
+ updateVolume(volumeRange.value);
759
+ updateSubtitleSize(subtitleSize.value);
760
+ video.loop = loopCheckbox.checked;
761
+ toggleSubtitles();
762
+ updateProgress();
763
+ // 通常時の動画幅を記録
764
+ normalVideoWidth = videoContainer.clientWidth;
765
+ });
766
+
767
+ // CSS変数を設定
768
+ document.documentElement.style.setProperty('--subtitle-scale', '1');
769
+ document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
770
+ document.documentElement.style.setProperty('--fullscreen-scale', '1');
771
+ </script>
 
772
  </body>
773
+
774
  </html>