soiz1 commited on
Commit
6ec4f66
·
verified ·
1 Parent(s): 1c1f695

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +657 -225
index.html CHANGED
@@ -38,7 +38,38 @@
38
  box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
39
  background: #000;
40
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  video {
43
  width: 100%;
44
  display: block;
@@ -82,6 +113,7 @@
82
  background: #001133;
83
  margin-bottom: 10px;
84
  cursor: pointer;
 
85
  }
86
 
87
  .progress-bar {
@@ -104,6 +136,36 @@
104
  box-shadow: 0 0 5px #00ccff;
105
  }
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  .buttons-container {
108
  display: flex;
109
  align-items: center;
@@ -291,6 +353,21 @@
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;
@@ -415,6 +492,63 @@
415
  transform: rotate(360deg);
416
  }
417
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  </style>
419
  </head>
420
 
@@ -431,6 +565,14 @@
431
  </div>
432
  </div>
433
 
 
 
 
 
 
 
 
 
434
  <div id="ripple-container">
435
  </div>
436
  <script>
@@ -573,7 +715,7 @@
573
  </div>
574
  <div class="control-group">
575
  <label for="speedRange">再生速度:</label>
576
- <input type="range" id="speedRange" min="0.0001" max="20" step="0.0001" value="1" style="width:700px !important;">
577
  <input type="number" id="speedInput" min="0.0001" step="0.0001" value="1">
578
  </div>
579
  <div class="control-group">
@@ -606,11 +748,18 @@
606
  </div>
607
  <button onclick="goFullscreen()">全画面</button>
608
  </div>
609
- <div class="video-container">
610
- <video id="videoPlayer" src="v.mp4">
 
 
 
611
  <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
612
  </track>
613
  </video>
 
 
 
 
614
  <div class="custom-controls">
615
  <div class="progress-container" id="progressContainer">
616
  <div class="progress-bar" id="progressBar">
@@ -627,264 +776,547 @@
627
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
628
  </div>
629
  <button class="control-btn" id="subtitleBtn" title="字幕">🔤</button>
 
630
  <button class="control-btn" id="fullscreenBtn">⛶</button>
631
  </div>
632
  </div>
633
  </div>
634
  </div>
635
  <script>
636
- const video = document.getElementById('videoPlayer');
637
- const videoSelect = document.getElementById('videoSelect');
638
- const speedRange = document.getElementById('speedRange');
639
- const speedInput = document.getElementById('speedInput');
640
- const volumeRange = document.getElementById('volumeRange');
641
- const volumeInput = document.getElementById('volumeInput');
642
- const loopCheckbox = document.getElementById('loopCheckbox');
643
- const playPauseBtn = document.getElementById('playPauseBtn');
644
- const progressBar = document.getElementById('progressBar');
645
- const progressContainer = document.getElementById('progressContainer');
646
- const timeDisplay = document.getElementById('timeDisplay');
647
- const volumeBtn = document.getElementById('volumeBtn');
648
- const volumeSlider = document.getElementById('volumeSlider');
649
- const fullscreenBtn = document.getElementById('fullscreenBtn');
650
- const subtitleBtn = document.getElementById('subtitleBtn');
651
- const subtitleToggle = document.getElementById('subtitleToggle');
652
- const subtitleSize = document.getElementById('subtitleSize');
653
- const subtitleSizeInput = document.getElementById('subtitleSizeInput');
654
- const subtitleTrack = document.getElementById('subtitleTrack');
655
- const subtitleTrackElement = document.getElementById('subtitleTrackElement');
656
- const videoContainer = document.querySelector('.video-container');
657
-
658
- // 初期設定
659
- video.controls = false;
660
- let isDragging = false;
661
- let subtitlesEnabled = true;
662
- let normalVideoWidth = videoContainer.clientWidth;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
 
664
- function updatePlaybackRate(value) {
665
- const speed = parseFloat(value);
666
- speedInput.value = speed;
667
- speedRange.value = speed;
668
- video.playbackRate = speed;
669
- }
 
 
 
 
 
670
 
671
- function updateVolume(value) {
672
- const volume = parseFloat(value);
673
- volumeInput.value = volume;
674
- volumeRange.value = volume;
675
- volumeSlider.value = volume;
676
- video.volume = volume;
 
 
 
677
 
678
- if (volume === 0) {
679
- volumeBtn.textContent = '🔇';
680
- } else if (volume < 0.5) {
681
- volumeBtn.textContent = '🔈';
682
- } else {
683
- volumeBtn.textContent = '🔊';
 
 
 
 
684
  }
685
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
 
687
- function handleVideoChange() {
688
- const selected = videoSelect.value;
 
689
 
690
- if (selected === 'v-2.mp4') {
691
- const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
692
- if (!confirmPlay) {
693
- videoSelect.value = video.src.split('/').pop();
694
- return;
695
- }
696
- }
697
 
698
- video.src = selected;
699
- video.load();
700
- video.play().then(() => {
701
- playPauseBtn.textContent = '⏸';
702
- }).catch(e => console.log(e));
703
- }
704
 
705
- function togglePlayPause() {
706
- if (video.paused) {
707
- video.play();
708
- playPauseBtn.textContent = '⏸';
709
- } else {
710
- video.pause();
711
- playPauseBtn.textContent = '▶';
712
- }
713
- }
714
 
715
- function updateProgress() {
716
- const percent = (video.currentTime / video.duration) * 100;
717
- progressBar.style.width = `${percent}%`;
718
-
719
- const currentMinutes = Math.floor(video.currentTime / 60);
720
- const currentSeconds = Math.floor(video.currentTime % 60).toString().padStart(2, '0');
721
- const durationMinutes = Math.floor(video.duration / 60);
722
- const durationSeconds = Math.floor(video.duration % 60).toString().padStart(2, '0');
723
-
724
- timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
725
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
 
727
- function setProgress(e) {
728
- const width = progressContainer.clientWidth;
729
- const clickX = e.offsetX;
730
- const duration = video.duration;
731
- video.currentTime = (clickX / width) * duration;
732
- }
 
 
 
 
 
 
 
 
 
 
733
 
734
- function toggleMute() {
735
- video.muted = !video.muted;
736
- if (video.muted) {
737
- volumeBtn.textContent = '🔇';
738
- volumeSlider.value = 0;
739
- } else {
740
- updateVolume(video.volume);
741
- }
742
- }
743
 
744
- function handleVolumeChange() {
745
- video.muted = false;
746
- updateVolume(volumeSlider.value);
747
- }
 
 
748
 
749
- function goFullscreen() {
750
- if (
751
- document.fullscreenElement ||
752
- document.webkitFullscreenElement ||
753
- document.msFullscreenElement
754
- ) {
755
- // フルスクリーンを解除
756
- if (document.exitFullscreen) {
757
- document.exitFullscreen();
758
- } else if (document.webkitExitFullscreen) {
759
- document.webkitExitFullscreen();
760
- } else if (document.msExitFullscreen) {
761
- document.msExitFullscreen();
762
- }
763
- } else {
764
- // フルスクリーンにする
765
- if (videoContainer.requestFullscreen) {
766
- videoContainer.requestFullscreen();
767
- } else if (videoContainer.webkitRequestFullscreen) {
768
- videoContainer.webkitRequestFullscreen();
769
- } else if (videoContainer.msRequestFullscreen) {
770
- videoContainer.msRequestFullscreen();
771
- }
772
- }
773
  }
774
 
 
 
 
 
 
 
 
 
 
 
 
 
775
 
776
- // 全画面変更時の字幕サイズ調整
777
- function updateSubtitleScaleForFullscreen() {
778
- if (document.fullscreenElement || document.webkitFullscreenElement ||
779
- document.mozFullScreenElement || document.msFullscreenElement) {
780
- // 全画面モード
781
- const fullscreenWidth = window.innerWidth;
782
- const scaleFactor = fullscreenWidth / normalVideoWidth;
783
- document.documentElement.style.setProperty('--fullscreen-scale', scaleFactor);
784
- } else {
785
- // 通常モード
786
- document.documentElement.style.setProperty('--fullscreen-scale', 1);
787
- }
788
- }
 
 
 
 
789
 
790
- // 字幕関連の関数
791
- function toggleSubtitles() {
792
- subtitlesEnabled = subtitleToggle.checked;
793
- subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
794
- subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
795
  }
 
796
 
797
- function updateSubtitleSize(value) {
798
- const size = parseFloat(value);
799
- subtitleSizeInput.value = size;
800
- subtitleSize.value = size;
801
-
802
- // 字幕サイズを制御
803
- document.documentElement.style.setProperty('--subtitle-scale', size);
804
-
805
- // VTTCueのlineプロパティには数値のみを設定('bottom'は無効)
806
- const track = subtitleTrackElement.track;
807
- if (track && track.cues) {
808
- for (let i = 0; i < track.cues.length; i++) {
809
- // 画面下部に表示するため、適切な数値を設定。とりあえず90。
810
- track.cues[i].line = 90;
811
- track.cues[i].snapToLines = false; // ラインスナップを無効に
812
- }
813
- }
814
- }
815
-
816
- function changeSubtitleTrack() {
817
- const selectedTrack = subtitleTrack.value;
818
- subtitleTrackElement.src = selectedTrack;
819
- subtitleTrackElement.track.mode = selectedTrack && subtitlesEnabled ? 'showing' : 'hidden';
820
-
821
- // トラック変更後に再度読み込み
822
- video.textTracks[0].mode = 'hidden';
823
- if (selectedTrack) {
824
- video.textTracks[0].mode = subtitlesEnabled ? 'showing' : 'hidden';
825
- }
826
- }
827
 
828
- function toggleSubtitleMenu() {
829
- document.getElementById('subtitleToggle').checked ^= true;
830
- toggleSubtitles();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
831
  }
 
 
 
 
 
 
 
 
832
 
833
- // イベントリスナー
834
- videoSelect.addEventListener('change', handleVideoChange);
835
 
836
- ['input', 'change', 'mouseup'].forEach(eventName => {
837
- speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
838
- volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
839
- subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
840
- });
 
 
 
 
 
 
 
 
841
 
842
- speedInput.addEventListener('input', () => updatePlaybackRate(speedInput.value));
843
- volumeInput.addEventListener('input', () => updateVolume(volumeInput.value));
844
- subtitleSizeInput.addEventListener('input', () => updateSubtitleSize(subtitleSizeInput.value));
 
 
 
 
 
 
 
 
 
 
845
 
846
- loopCheckbox.addEventListener('change', () => {
847
- video.loop = loopCheckbox.checked;
848
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
 
850
- subtitleToggle.addEventListener('change', toggleSubtitles);
851
- subtitleTrack.addEventListener('change', changeSubtitleTrack);
852
- subtitleBtn.addEventListener('click', toggleSubtitleMenu);
 
 
 
 
 
 
 
 
 
 
853
 
854
- playPauseBtn.addEventListener('click', togglePlayPause);
855
- video.addEventListener('click', togglePlayPause);
856
- video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
857
- video.addEventListener('pause', () => playPauseBtn.textContent = '▶');
858
- video.addEventListener('timeupdate', updateProgress);
859
- progressContainer.addEventListener('click', setProgress);
860
- progressContainer.addEventListener('mousedown', () => isDragging = true);
861
- document.addEventListener('mouseup', () => isDragging = false);
862
- progressContainer.addEventListener('mousemove', (e) => isDragging && setProgress(e));
863
- volumeBtn.addEventListener('click', toggleMute);
864
- volumeSlider.addEventListener('input', handleVolumeChange);
865
- fullscreenBtn.addEventListener('click', goFullscreen);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
866
 
867
- // 全画面変更イベントを監視
868
- document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
869
- document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
870
- document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
871
- document.addEventListener('MSFullscreenChange', updateSubtitleScaleForFullscreen);
 
 
 
 
872
 
873
- video.addEventListener('loadedmetadata', () => {
874
- updatePlaybackRate(speedRange.value);
875
- updateVolume(volumeRange.value);
876
- updateSubtitleSize(subtitleSize.value);
877
- video.loop = loopCheckbox.checked;
878
- toggleSubtitles();
879
- updateProgress();
880
- // 通常時の動画幅を記録
881
- normalVideoWidth = videoContainer.clientWidth;
882
- });
883
 
884
- // CSS変数を設定
885
- document.documentElement.style.setProperty('--subtitle-scale', '1');
886
- document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
887
- document.documentElement.style.setProperty('--fullscreen-scale', '1');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
888
  </script>
889
  </body>
890
 
 
38
  box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
39
  background: #000;
40
  }
41
+ /* ホバー時の時間表示スタイル */
42
+ .hover-time {
43
+ position: absolute;
44
+ top: -30px;
45
+ transform: translateX(-50%);
46
+ background: rgba(0, 20, 40, 0.9);
47
+ color: #00ccff;
48
+ padding: 3px 8px;
49
+ border-radius: 4px;
50
+ font-size: 12px;
51
+ pointer-events: none;
52
+ display: none;
53
+ white-space: nowrap;
54
+ font-family: "M PLUS Rounded 1c", monospace;
55
+ }
56
 
57
+ /* サムネイルプレビュー用スタイル */
58
+ .thumbnail-preview {
59
+ position: absolute;
60
+ width: 160px;
61
+ height: 90px;
62
+ background: #000;
63
+ border: 2px solid #00aaff;
64
+ box-shadow: 0 0 10px rgba(0, 170, 255, 0.5);
65
+ display: none;
66
+ pointer-events: none;
67
+ z-index: 10;
68
+ bottom: 50px;
69
+ transform: translateX(-50%);
70
+ border-radius: 4px;
71
+ object-fit: cover;
72
+ }
73
  video {
74
  width: 100%;
75
  display: block;
 
113
  background: #001133;
114
  margin-bottom: 10px;
115
  cursor: pointer;
116
+ position: relative;
117
  }
118
 
119
  .progress-bar {
 
136
  box-shadow: 0 0 5px #00ccff;
137
  }
138
 
139
+ /* プレビュー用スタイル */
140
+ .preview-tooltip {
141
+ position: absolute;
142
+ bottom: 20px;
143
+ transform: translateX(-50%);
144
+ background: rgba(0, 20, 40, 0.9);
145
+ border: 1px solid #0066ff;
146
+ padding: 5px 10px;
147
+ border-radius: 5px;
148
+ display: none;
149
+ z-index: 100;
150
+ pointer-events: none;
151
+ }
152
+
153
+ .preview-time {
154
+ color: #00ccff;
155
+ font-size: 12px;
156
+ text-align: center;
157
+ margin-bottom: 5px;
158
+ }
159
+
160
+ .preview-frame {
161
+ width: 160px;
162
+ height: 90px;
163
+ background-color: #000;
164
+ border: 1px solid #0066ff;
165
+ background-size: cover;
166
+ background-position: center;
167
+ }
168
+
169
  .buttons-container {
170
  display: flex;
171
  align-items: center;
 
353
  .video-container:-ms-fullscreen video::cue {
354
  font-size: calc(16px * var(--subtitle-scale) * var(--fullscreen-scale, 1)) !important;
355
  }
356
+ .thumbnail-preview {
357
+ position: absolute;
358
+ width: 160px;
359
+ height: 90px;
360
+ background: #000;
361
+ border: 2px solid #00aaff;
362
+ box-shadow: 0 0 10px rgba(0, 170, 255, 0.5);
363
+ display: none;
364
+ pointer-events: none;
365
+ z-index: 10;
366
+ bottom: 50px; /* プログレスバーの上に表示 */
367
+ transform: translateX(-50%);
368
+ border-radius: 4px;
369
+ object-fit: cover;
370
+ }
371
  body {
372
  margin: 0;
373
  padding: 0;
 
492
  transform: rotate(360deg);
493
  }
494
  }
495
+
496
+ /* 全画面コンテキストメニュー */
497
+ .fullscreen-context-menu {
498
+ position: fixed;
499
+ top: 50%;
500
+ left: 50%;
501
+ transform: translate(-50%, -50%);
502
+ background-color: #0f0f1a;
503
+ border: 1px solid #0066ff;
504
+ box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
505
+ padding: 15px;
506
+ z-index: 1000;
507
+ display: none;
508
+ }
509
+
510
+ .fullscreen-context-menu button {
511
+ display: block;
512
+ width: 100%;
513
+ margin-bottom: 5px;
514
+ }
515
+
516
+ .fullscreen-context-menu button:last-child {
517
+ margin-bottom: 0;
518
+ }
519
+
520
+ /* 音声/字幕のみモード */
521
+ .audio-only-mode {
522
+ position: relative;
523
+ }
524
+
525
+ .audio-only-mode .video-placeholder {
526
+ display: flex;
527
+ justify-content: center;
528
+ align-items: center;
529
+ background-color: #000;
530
+ color: #00ccff;
531
+ font-size: 24px;
532
+ height: 450px; /* 動画の高さに合わせて調整 */
533
+ }
534
+
535
+ .audio-only-mode video {
536
+ display: none;
537
+ }
538
+ .hover-time {
539
+ position: absolute;
540
+ top: -30px;
541
+ transform: translateX(-50%);
542
+ background: rgba(0, 20, 40, 0.9);
543
+ color: #00ccff;
544
+ padding: 3px 8px;
545
+ border-radius: 4px;
546
+ font-size: 12px;
547
+ pointer-events: none;
548
+ display: none;
549
+ white-space: nowrap;
550
+ font-family: "M PLUS Rounded 1c", monospace;
551
+ }
552
  </style>
553
  </head>
554
 
 
565
  </div>
566
  </div>
567
 
568
+ <!-- 全画面コンテキストメニュー -->
569
+ <div class="fullscreen-context-menu" id="fullscreenContextMenu">
570
+ <button id="audioOnlyBtn">音声/字幕のみモード</button>
571
+ <button id="showVideoBtn">動画表示モード</button>
572
+ <button id="exitFullscreenBtn">全画面を終了</button>
573
+ <button id="closeContextMenuBtn">閉じる</button>
574
+ </div>
575
+
576
  <div id="ripple-container">
577
  </div>
578
  <script>
 
715
  </div>
716
  <div class="control-group">
717
  <label for="speedRange">再生速度:</label>
718
+ <input type="range" id="speedRange" min="0.0001" max="10" step="0.0001" value="1" style="width:700px !important;">
719
  <input type="number" id="speedInput" min="0.0001" step="0.0001" value="1">
720
  </div>
721
  <div class="control-group">
 
748
  </div>
749
  <button onclick="goFullscreen()">全画面</button>
750
  </div>
751
+ <div class="video-container" id="videoContainer">
752
+ <div class="video-placeholder" id="videoPlaceholder" style="display: none;">
753
+ 音声/字幕のみモード
754
+ </div>
755
+ <video id="videoPlayer" src="v.mp4" crossorigin="anonymous">
756
  <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
757
  </track>
758
  </video>
759
+ <div class="preview-tooltip" id="previewTooltip">
760
+ <div class="preview-time" id="previewTime">00:00</div>
761
+ <div class="preview-frame" id="previewFrame"></div>
762
+ </div>
763
  <div class="custom-controls">
764
  <div class="progress-container" id="progressContainer">
765
  <div class="progress-bar" id="progressBar">
 
776
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
777
  </div>
778
  <button class="control-btn" id="subtitleBtn" title="字幕">🔤</button>
779
+ <button class="control-btn" id="audioOnlyBtn" title="音声/字幕のみ">🔈</button>
780
  <button class="control-btn" id="fullscreenBtn">⛶</button>
781
  </div>
782
  </div>
783
  </div>
784
  </div>
785
  <script>
786
+ // 要素取得
787
+ const video = document.getElementById('videoPlayer');
788
+ const videoSelect = document.getElementById('videoSelect');
789
+ const speedRange = document.getElementById('speedRange');
790
+ const speedInput = document.getElementById('speedInput');
791
+ const volumeRange = document.getElementById('volumeRange');
792
+ const volumeInput = document.getElementById('volumeInput');
793
+ const loopCheckbox = document.getElementById('loopCheckbox');
794
+ const playPauseBtn = document.getElementById('playPauseBtn');
795
+ const progressBar = document.getElementById('progressBar');
796
+ const progressContainer = document.getElementById('progressContainer');
797
+ const timeDisplay = document.getElementById('timeDisplay');
798
+ const volumeBtn = document.getElementById('volumeBtn');
799
+ const volumeSlider = document.getElementById('volumeSlider');
800
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
801
+ const subtitleBtn = document.getElementById('subtitleBtn');
802
+ const subtitleToggle = document.getElementById('subtitleToggle');
803
+ const subtitleSize = document.getElementById('subtitleSize');
804
+ const subtitleSizeInput = document.getElementById('subtitleSizeInput');
805
+ const subtitleTrack = document.getElementById('subtitleTrack');
806
+ const subtitleTrackElement = document.getElementById('subtitleTrackElement');
807
+ const videoContainer = document.getElementById('videoContainer');
808
+ const videoPlaceholder = document.getElementById('videoPlaceholder');
809
+ const previewTooltip = document.getElementById('previewTooltip');
810
+ const previewTime = document.getElementById('previewTime');
811
+ const previewFrame = document.getElementById('previewFrame');
812
+ const audioOnlyBtn = document.getElementById('audioOnlyBtn');
813
+ const fullscreenContextMenu = document.getElementById('fullscreenContextMenu');
814
+ const exitFullscreenBtn = document.getElementById('exitFullscreenBtn');
815
+ const showVideoBtn = document.getElementById('showVideoBtn');
816
+ const closeContextMenuBtn = document.getElementById('closeContextMenuBtn');
817
+
818
+ // プレビュー用動画を動的に作成
819
+ const previewVideo = document.createElement('video');
820
+ previewVideo.id = 'previewVideo';
821
+ previewVideo.className = 'thumbnail-preview';
822
+ previewVideo.muted = true;
823
+ previewVideo.preload = 'auto';
824
+ videoContainer.appendChild(previewVideo);
825
+
826
+ // 初期設定
827
+ video.controls = false;
828
+ previewVideo.src = video.src;
829
+ let isDragging = false;
830
+ let subtitlesEnabled = true;
831
+ let normalVideoWidth = videoContainer.clientWidth;
832
+ let isAudioOnlyMode = false;
833
+ let hoverTimeout;
834
+ let canvas = null;
835
+ let previewCanvas = null;
836
+ let previewContext = null;
837
+ let isGeneratingPreview = false;
838
+
839
+ // プレビュー用キャンバスを作成
840
+ function createPreviewCanvas() {
841
+ if (!canvas) {
842
+ canvas = document.createElement('canvas');
843
+ canvas.width = video.videoWidth || 640;
844
+ canvas.height = video.videoHeight || 360;
845
+ }
846
 
847
+ if (!previewCanvas) {
848
+ previewCanvas = document.createElement('canvas');
849
+ previewCanvas.width = 160;
850
+ previewCanvas.height = 90;
851
+ previewContext = previewCanvas.getContext('2d');
852
+ }
853
+ }
854
+
855
+ // プレビュー画像を生成
856
+ function generatePreview(time) {
857
+ if (isGeneratingPreview || !video.readyState) return;
858
 
859
+ try {
860
+ isGeneratingPreview = true;
861
+ createPreviewCanvas();
862
+
863
+ const currentTime = video.currentTime;
864
+ video.currentTime = time;
865
+
866
+ const onSeeked = () => {
867
+ video.removeEventListener('seeked', onSeeked);
868
 
869
+ try {
870
+ const ctx = canvas.getContext('2d');
871
+ canvas.width = video.videoWidth;
872
+ canvas.height = video.videoHeight;
873
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
874
+ previewContext.drawImage(canvas, 0, 0, previewCanvas.width, previewCanvas.height);
875
+ previewFrame.style.backgroundImage = `url(${previewCanvas.toDataURL()})`;
876
+ } catch (e) {
877
+ console.error('Preview generation error:', e);
878
+ previewFrame.style.backgroundImage = 'linear-gradient(to bottom, #0066ff, #00aaff)';
879
  }
880
+
881
+ video.currentTime = currentTime;
882
+ isGeneratingPreview = false;
883
+ };
884
+
885
+ video.addEventListener('seeked', onSeeked);
886
+ } catch (e) {
887
+ console.error('Preview error:', e);
888
+ isGeneratingPreview = false;
889
+ }
890
+ }
891
+
892
+ // プレビューツールチップを表示
893
+ function showPreviewTooltip(e) {
894
+ if (!video.duration) return;
895
 
896
+ const rect = progressContainer.getBoundingClientRect();
897
+ const percent = (e.clientX - rect.left) / rect.width;
898
+ const time = Math.max(0, Math.min(percent, 1)) * video.duration;
899
 
900
+ const tooltipWidth = previewTooltip.offsetWidth;
901
+ let left = e.clientX - rect.left;
902
+ left = Math.max(tooltipWidth / 2, Math.min(left, rect.width - tooltipWidth / 2));
 
 
 
 
903
 
904
+ previewTooltip.style.left = `${left}px`;
 
 
 
 
 
905
 
906
+ const minutes = Math.floor(time / 60);
907
+ const seconds = Math.floor(time % 60).toString().padStart(2, '0');
908
+ previewTime.textContent = `${minutes}:${seconds}`;
 
 
 
 
 
 
909
 
910
+ generatePreview(time);
911
+ previewTooltip.style.display = 'block';
912
+ }
913
+
914
+ // プレビューツールチップを非表示
915
+ function hidePreviewTooltip() {
916
+ previewTooltip.style.display = 'none';
917
+ }
918
+
919
+ // ホバー時の時間表示設定
920
+ function setupHoverTime() {
921
+ const hoverTime = document.createElement('div');
922
+ hoverTime.className = 'hover-time';
923
+ progressContainer.appendChild(hoverTime);
924
+
925
+ progressContainer.addEventListener("mousemove", (e) => {
926
+ if (!video.duration) return;
927
+
928
+ const rect = progressContainer.getBoundingClientRect();
929
+ const pos = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1);
930
+ const time = pos * video.duration;
931
+
932
+ const minutes = Math.floor(time / 60);
933
+ const seconds = Math.floor(time % 60).toString().padStart(2, '0');
934
+ hoverTime.textContent = `${minutes}:${seconds}`;
935
+ hoverTime.style.display = 'block';
936
+ hoverTime.style.left = `${e.clientX - rect.left}px`;
937
+
938
+ previewVideo.style.display = "block";
939
+ previewVideo.style.left = `${e.clientX}px`;
940
+
941
+ clearTimeout(hoverTimeout);
942
+ hoverTimeout = setTimeout(() => {
943
+ previewVideo.currentTime = time;
944
+ }, 50);
945
+ });
946
+
947
+ progressContainer.addEventListener("mouseleave", () => {
948
+ const hoverTime = document.querySelector('.hover-time');
949
+ if (hoverTime) hoverTime.style.display = "none";
950
+ previewVideo.style.display = "none";
951
+ clearTimeout(hoverTimeout);
952
+ });
953
+ }
954
+
955
+ // 再生速度を更新
956
+ function updatePlaybackRate(value) {
957
+ const speed = parseFloat(value);
958
+ speedInput.value = speed;
959
+ speedRange.value = speed;
960
+ video.playbackRate = speed;
961
+ }
962
+
963
+ // 音量を更新
964
+ function updateVolume(value) {
965
+ const volume = parseFloat(value);
966
+ volumeInput.value = volume;
967
+ volumeRange.value = volume;
968
+ volumeSlider.value = volume;
969
+ video.volume = volume;
970
 
971
+ if (volume === 0) {
972
+ volumeBtn.textContent = '🔇';
973
+ } else if (volume < 0.5) {
974
+ volumeBtn.textContent = '🔈';
975
+ } else {
976
+ volumeBtn.textContent = '🔊';
977
+ }
978
+ }
979
+
980
+
981
+ // プレビュー用動画のソースをメイン動画と同期
982
+ previewVideo.src = video.src;
983
+
984
+ // ホバー時のプレビュー更新関数(メイン動画を操作しない)
985
+ function updateHoverPreview(e) {
986
+ if (!video.duration) return;
987
 
988
+ const rect = progressContainer.getBoundingClientRect();
989
+ const pos = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1);
990
+ const time = pos * video.duration;
 
 
 
 
 
 
991
 
992
+ // 時間表示更新
993
+ const minutes = Math.floor(time / 60);
994
+ const seconds = Math.floor(time % 60).toString().padStart(2, '0');
995
+ hoverTime.textContent = `${minutes}:${seconds}`;
996
+ hoverTime.style.display = 'block';
997
+ hoverTime.style.left = `${e.clientX - rect.left}px`;
998
 
999
+ // プレビュー動画のみを更新(メイン動画は変更しない)
1000
+ if (previewVideo.readyState > 0) {
1001
+ previewVideo.currentTime = time;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1002
  }
1003
 
1004
+ // プレビュー動画の位置調整
1005
+ previewVideo.style.display = "block";
1006
+ previewVideo.style.left = `${e.clientX}px`;
1007
+ }
1008
+
1009
+ // ホバーイベントリスナーの設定
1010
+ function setupHoverEvents() {
1011
+ const hoverTime = document.createElement('div');
1012
+ hoverTime.className = 'hover-time';
1013
+ progressContainer.appendChild(hoverTime);
1014
+
1015
+ let hoverTimeout;
1016
 
1017
+ progressContainer.addEventListener("mousemove", (e) => {
1018
+ clearTimeout(hoverTimeout);
1019
+ hoverTimeout = setTimeout(() => {
1020
+ updateHoverPreview(e);
1021
+ }, 30); // 30msのデバウンス
1022
+ });
1023
+
1024
+ progressContainer.addEventListener("mouseleave", () => {
1025
+ hoverTime.style.display = "none";
1026
+ previewVideo.style.display = "none";
1027
+ clearTimeout(hoverTimeout);
1028
+ });
1029
+ }
1030
+
1031
+ // 動画変更時の処理(プレビュー動画も同期)
1032
+ function handleVideoChange() {
1033
+ const selected = videoSelect.value;
1034
 
1035
+ if (selected === 'v-2.mp4') {
1036
+ const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
1037
+ if (!confirmPlay) {
1038
+ videoSelect.value = video.src.split('/').pop();
1039
+ return;
1040
  }
1041
+ }
1042
 
1043
+ video.src = selected;
1044
+ previewVideo.src = selected; // プレビュー動画も更新
1045
+
1046
+ // 両方の動画をロード
1047
+ const loadVideo = video.load();
1048
+ const loadPreview = previewVideo.load();
1049
+
1050
+ Promise.all([loadVideo, loadPreview])
1051
+ .then(() => video.play())
1052
+ .catch(e => console.error("動画読み込みエラー:", e));
1053
+ }
1054
+
1055
+ // 初期化関数
1056
+ function init() {
1057
+ setupHoverEvents();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1058
 
1059
+ // プレビュー動画のメタデータが読み込まれたら準備完了
1060
+ previewVideo.addEventListener('loadedmetadata', () => {
1061
+ console.log("プレビュー動画の準備が完了しました");
1062
+ });
1063
+
1064
+ // メイン動画のイベントリスナー設定
1065
+ video.addEventListener('loadedmetadata', () => {
1066
+ previewVideo.src = video.src; // ソースを再度同期
1067
+ updatePlaybackRate(speedRange.value);
1068
+ updateVolume(volumeRange.value);
1069
+ });
1070
+ }
1071
+
1072
+ // 初期化を実行
1073
+ init();
1074
+ // 再生/一時停止を切り替え
1075
+ function togglePlayPause() {
1076
+ if (video.paused) {
1077
+ video.play().then(() => {
1078
+ playPauseBtn.textContent = '⏸';
1079
+ }).catch(e => console.log(e));
1080
+ } else {
1081
+ video.pause();
1082
+ playPauseBtn.textContent = '▶';
1083
+ }
1084
+ }
1085
+
1086
+ // 進捗バーを更新
1087
+ function updateProgress() {
1088
+ if (!video.duration) return;
1089
+
1090
+ const percent = (video.currentTime / video.duration) * 100;
1091
+ progressBar.style.width = `${percent}%`;
1092
+
1093
+ const currentMinutes = Math.floor(video.currentTime / 60);
1094
+ const currentSeconds = Math.floor(video.currentTime % 60).toString().padStart(2, '0');
1095
+ const durationMinutes = Math.floor(video.duration / 60);
1096
+ const durationSeconds = Math.floor(video.duration % 60).toString().padStart(2, '0');
1097
+
1098
+ timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
1099
+ }
1100
+
1101
+ // 進捗バーを設定
1102
+ function setProgress(e) {
1103
+ const width = progressContainer.clientWidth;
1104
+ const clickX = e.offsetX;
1105
+ const duration = video.duration;
1106
+ video.currentTime = (clickX / width) * duration;
1107
+ }
1108
+
1109
+ // ミュートを切り替え
1110
+ function toggleMute() {
1111
+ video.muted = !video.muted;
1112
+ if (video.muted) {
1113
+ volumeBtn.textContent = '🔇';
1114
+ volumeSlider.value = 0;
1115
+ } else {
1116
+ updateVolume(video.volume);
1117
+ }
1118
+ }
1119
+
1120
+ // 音量変更を処理
1121
+ function handleVolumeChange() {
1122
+ video.muted = false;
1123
+ updateVolume(volumeSlider.value);
1124
+ }
1125
+
1126
+ // 全画面表示を切り替え
1127
+ function goFullscreen() {
1128
+ if (document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
1129
+ if (document.exitFullscreen) {
1130
+ document.exitFullscreen();
1131
+ } else if (document.webkitExitFullscreen) {
1132
+ document.webkitExitFullscreen();
1133
+ } else if (document.msExitFullscreen) {
1134
+ document.msExitFullscreen();
1135
+ }
1136
+ } else {
1137
+ if (videoContainer.requestFullscreen) {
1138
+ videoContainer.requestFullscreen();
1139
+ } else if (videoContainer.webkitRequestFullscreen) {
1140
+ videoContainer.webkitRequestFullscreen();
1141
+ } else if (videoContainer.msRequestFullscreen) {
1142
+ videoContainer.msRequestFullscreen();
1143
  }
1144
+ }
1145
+ }
1146
+
1147
+ // 全画面コンテキストメニューを表示
1148
+ function showContextMenu(e) {
1149
+ if (!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement)) {
1150
+ return;
1151
+ }
1152
 
1153
+ e.preventDefault();
 
1154
 
1155
+ fullscreenContextMenu.style.display = 'block';
1156
+ fullscreenContextMenu.style.left = `${e.clientX}px`;
1157
+ fullscreenContextMenu.style.top = `${e.clientY}px`;
1158
+ }
1159
+
1160
+ // 全画面コンテキストメニューを非表示
1161
+ function hideContextMenu() {
1162
+ fullscreenContextMenu.style.display = 'none';
1163
+ }
1164
+
1165
+ // 音声/字幕のみモードを切り替え
1166
+ function toggleAudioOnlyMode() {
1167
+ isAudioOnlyMode = !isAudioOnlyMode;
1168
 
1169
+ if (isAudioOnlyMode) {
1170
+ videoContainer.classList.add('audio-only-mode');
1171
+ videoPlaceholder.style.display = 'flex';
1172
+ video.style.display = 'none';
1173
+ audioOnlyBtn.textContent = '🎥';
1174
+ audioOnlyBtn.title = '動画表示モード';
1175
+ } else {
1176
+ videoContainer.classList.remove('audio-only-mode');
1177
+ videoPlaceholder.style.display = 'none';
1178
+ video.style.display = 'block';
1179
+ audioOnlyBtn.textContent = '🔈';
1180
+ audioOnlyBtn.title = '音声/字幕のみ';
1181
+ }
1182
 
1183
+ hideContextMenu();
1184
+ }
1185
+
1186
+ // 全画面時の字幕サイズ調整
1187
+ function updateSubtitleScaleForFullscreen() {
1188
+ if (document.fullscreenElement || document.webkitFullscreenElement ||
1189
+ document.mozFullScreenElement || document.msFullscreenElement) {
1190
+ const fullscreenWidth = window.innerWidth;
1191
+ const scaleFactor = fullscreenWidth / normalVideoWidth;
1192
+ document.documentElement.style.setProperty('--fullscreen-scale', scaleFactor);
1193
+ } else {
1194
+ document.documentElement.style.setProperty('--fullscreen-scale', 1);
1195
+ }
1196
+ }
1197
+
1198
+ // 字幕表示を切り替え
1199
+ function toggleSubtitles() {
1200
+ subtitlesEnabled = subtitleToggle.checked;
1201
+ if (subtitleTrackElement.track) {
1202
+ subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
1203
+ }
1204
+ subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
1205
+ }
1206
+
1207
+ // 字幕サイズを更新
1208
+ function updateSubtitleSize(value) {
1209
+ const size = parseFloat(value);
1210
+ subtitleSizeInput.value = size;
1211
+ subtitleSize.value = size;
1212
+ document.documentElement.style.setProperty('--subtitle-scale', size);
1213
 
1214
+ const track = subtitleTrackElement.track;
1215
+ if (track && track.cues) {
1216
+ for (let i = 0; i < track.cues.length; i++) {
1217
+ track.cues[i].line = 90;
1218
+ track.cues[i].snapToLines = false;
1219
+ }
1220
+ }
1221
+ }
1222
+
1223
+ // 字幕トラックを変更
1224
+ function changeSubtitleTrack() {
1225
+ const selectedTrack = subtitleTrack.value;
1226
+ subtitleTrackElement.src = selectedTrack;
1227
 
1228
+ if (video.textTracks.length > 0) {
1229
+ video.textTracks[0].mode = selectedTrack && subtitlesEnabled ? 'showing' : 'hidden';
1230
+ }
1231
+ }
1232
+
1233
+ // 字幕メニューを切り替え
1234
+ function toggleSubtitleMenu() {
1235
+ subtitleToggle.checked = !subtitleToggle.checked;
1236
+ toggleSubtitles();
1237
+ }
1238
+
1239
+ // イベントリスナーを設定
1240
+ function setupEventListeners() {
1241
+ videoSelect.addEventListener('change', handleVideoChange);
1242
+
1243
+ ['input', 'change'].forEach(eventName => {
1244
+ speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
1245
+ volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
1246
+ subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
1247
+ });
1248
+
1249
+ speedInput.addEventListener('input', () => updatePlaybackRate(speedInput.value));
1250
+ volumeInput.addEventListener('input', () => updateVolume(volumeInput.value));
1251
+ subtitleSizeInput.addEventListener('input', () => updateSubtitleSize(subtitleSizeInput.value));
1252
+
1253
+ loopCheckbox.addEventListener('change', () => {
1254
+ video.loop = loopCheckbox.checked;
1255
+ });
1256
+
1257
+ subtitleToggle.addEventListener('change', toggleSubtitles);
1258
+ subtitleTrack.addEventListener('change', changeSubtitleTrack);
1259
+ subtitleBtn.addEventListener('click', toggleSubtitleMenu);
1260
+
1261
+ playPauseBtn.addEventListener('click', togglePlayPause);
1262
+ video.addEventListener('click', togglePlayPause);
1263
+ video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
1264
+ video.addEventListener('pause', () => playPauseBtn.textContent = '▶');
1265
+ video.addEventListener('timeupdate', updateProgress);
1266
 
1267
+ progressContainer.addEventListener('click', setProgress);
1268
+ progressContainer.addEventListener('mousedown', () => isDragging = true);
1269
+ document.addEventListener('mouseup', () => isDragging = false);
1270
+ progressContainer.addEventListener('mousemove', (e) => {
1271
+ if (isDragging) setProgress(e);
1272
+ showPreviewTooltip(e);
1273
+ });
1274
+ progressContainer.addEventListener('mouseenter', showPreviewTooltip);
1275
+ progressContainer.addEventListener('mouseleave', hidePreviewTooltip);
1276
 
1277
+ volumeBtn.addEventListener('click', toggleMute);
1278
+ volumeSlider.addEventListener('input', handleVolumeChange);
1279
+ fullscreenBtn.addEventListener('click', goFullscreen);
1280
+ audioOnlyBtn.addEventListener('click', toggleAudioOnlyMode);
 
 
 
 
 
 
1281
 
1282
+ videoContainer.addEventListener('contextmenu', showContextMenu);
1283
+ exitFullscreenBtn.addEventListener('click', () => {
1284
+ if (document.exitFullscreen) document.exitFullscreen();
1285
+ hideContextMenu();
1286
+ });
1287
+ showVideoBtn.addEventListener('click', () => {
1288
+ if (isAudioOnlyMode) toggleAudioOnlyMode();
1289
+ hideContextMenu();
1290
+ });
1291
+ closeContextMenuBtn.addEventListener('click', hideContextMenu);
1292
+ document.addEventListener('click', hideContextMenu);
1293
+
1294
+ document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
1295
+ document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
1296
+ document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
1297
+ document.addEventListener('MSFullscreenChange', updateSubtitleScaleForFullscreen);
1298
+
1299
+ video.addEventListener('loadedmetadata', () => {
1300
+ updatePlaybackRate(speedRange.value);
1301
+ updateVolume(volumeRange.value);
1302
+ updateSubtitleSize(subtitleSize.value);
1303
+ video.loop = loopCheckbox.checked;
1304
+ toggleSubtitles();
1305
+ updateProgress();
1306
+ normalVideoWidth = videoContainer.clientWidth;
1307
+ createPreviewCanvas();
1308
+ });
1309
+ }
1310
+
1311
+ // CSS変数を初期設定
1312
+ document.documentElement.style.setProperty('--subtitle-scale', '1');
1313
+ document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
1314
+ document.documentElement.style.setProperty('--fullscreen-scale', '1');
1315
+
1316
+ // 初期化
1317
+ setupHoverTime();
1318
+ setupEventListeners();
1319
+ createPreviewCanvas();
1320
  </script>
1321
  </body>
1322