soiz1 commited on
Commit
194d6e2
·
verified ·
1 Parent(s): fe79a34

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +276 -167
index.html CHANGED
@@ -105,18 +105,34 @@
105
  box-shadow: 0 0 5px #00ccff;
106
  }
107
 
108
- /* ホバー時の時間表示 */
109
- .hover-time {
110
  position: absolute;
111
- top: -30px;
112
  transform: translateX(-50%);
113
- background: rgba(0, 20, 40, 0.8);
 
 
 
 
 
 
 
 
 
114
  color: #00ccff;
115
- padding: 3px 6px;
116
- border-radius: 4px;
117
  font-size: 12px;
118
- pointer-events: none;
119
- display: none;
 
 
 
 
 
 
 
 
 
120
  }
121
 
122
  .buttons-container {
@@ -430,33 +446,49 @@
430
  transform: rotate(360deg);
431
  }
432
  }
433
-
434
- /* 動画非表示モード用スタイル */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  .audio-only-mode video {
436
  display: none;
437
  }
438
- .audio-only-mode .video-container {
439
- background: transparent;
440
- border: none;
441
- box-shadow: none;
442
- }
443
- .audio-only-mode .controls {
444
- margin-top: 0;
445
- }
446
- /* サムネイルプレビュー用スタイル */
447
- .thumbnail-preview {
448
- position: absolute;
449
- width: 160px;
450
- height: 90px;
451
- background: #000;
452
- border: 2px solid #00aaff;
453
- box-shadow: 0 0 10px rgba(0, 170, 255, 0.5);
454
- display: none;
455
- pointer-events: none;
456
- z-index: 10;
457
- bottom: 40px; /* プログレスバーの上に表示 */
458
- transform: translateX(-50%);
459
- }
460
  </style>
461
  </head>
462
 
@@ -473,6 +505,14 @@
473
  </div>
474
  </div>
475
 
 
 
 
 
 
 
 
 
476
  <div id="ripple-container">
477
  </div>
478
  <script>
@@ -627,10 +667,6 @@
627
  <label for="loopCheckbox">ループ再生:</label>
628
  <input type="checkbox" id="loopCheckbox" checked>
629
  </div>
630
- <div class="control-group">
631
- <label for="audioOnlyCheckbox">音声のみモード:</label>
632
- <input type="checkbox" id="audioOnlyCheckbox">
633
- </div>
634
  <!-- 字幕設定セクション -->
635
  <div class="subtitle-settings">
636
  <div class="control-group">
@@ -652,16 +688,22 @@
652
  </div>
653
  <button onclick="goFullscreen()">全画面</button>
654
  </div>
655
- <div class="video-container">
 
 
 
656
  <video id="videoPlayer" src="v.mp4">
657
  <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
658
  </track>
659
  </video>
 
 
 
 
660
  <div class="custom-controls">
661
  <div class="progress-container" id="progressContainer">
662
  <div class="progress-bar" id="progressBar">
663
  </div>
664
- <div class="hover-time" id="hoverTime"></div>
665
  </div>
666
  <div class="buttons-container">
667
  <div class="left-controls">
@@ -674,7 +716,7 @@
674
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
675
  </div>
676
  <button class="control-btn" id="subtitleBtn" title="字幕">🔤</button>
677
- <button class="control-btn" id="audioOnlyBtn" title="音声のみ">🎵</button>
678
  <button class="control-btn" id="fullscreenBtn">⛶</button>
679
  </div>
680
  </div>
@@ -688,104 +730,143 @@
688
  const volumeRange = document.getElementById('volumeRange');
689
  const volumeInput = document.getElementById('volumeInput');
690
  const loopCheckbox = document.getElementById('loopCheckbox');
691
- const audioOnlyCheckbox = document.getElementById('audioOnlyCheckbox');
692
  const playPauseBtn = document.getElementById('playPauseBtn');
693
  const progressBar = document.getElementById('progressBar');
694
  const progressContainer = document.getElementById('progressContainer');
695
- const hoverTime = document.getElementById('hoverTime');
696
  const timeDisplay = document.getElementById('timeDisplay');
697
  const volumeBtn = document.getElementById('volumeBtn');
698
  const volumeSlider = document.getElementById('volumeSlider');
699
  const fullscreenBtn = document.getElementById('fullscreenBtn');
700
  const subtitleBtn = document.getElementById('subtitleBtn');
701
- const audioOnlyBtn = document.getElementById('audioOnlyBtn');
702
  const subtitleToggle = document.getElementById('subtitleToggle');
703
  const subtitleSize = document.getElementById('subtitleSize');
704
  const subtitleSizeInput = document.getElementById('subtitleSizeInput');
705
  const subtitleTrack = document.getElementById('subtitleTrack');
706
  const subtitleTrackElement = document.getElementById('subtitleTrackElement');
707
- const videoContainer = document.querySelector('.video-container');
708
- const controls = document.querySelector('.controls');
709
-
 
 
 
 
 
 
 
 
 
710
  // 初期設定
711
  video.controls = false;
712
- //let isDragging = false;
713
  let subtitlesEnabled = true;
714
  let normalVideoWidth = videoContainer.clientWidth;
715
- let audioOnlyMode = false;
716
-
717
- const previewVideo = document.createElement('video');
718
- previewVideo.id = 'previewVideo';
719
- previewVideo.className = 'thumbnail-preview';
720
- document.body.appendChild(previewVideo);
721
-
722
- // ホバー時の時間表示設定関数を追加
723
- function setupHoverTime() {
724
- progressContainer.addEventListener("mousemove", (e) => {
725
- if (!video.duration) return;
726
- const rect = progressContainer.getBoundingClientRect();
727
- const pos = (e.clientX - rect.left) / rect.width;
728
- const time = pos * video.duration;
729
 
730
- // 時間表示
731
- const minutes = Math.floor(time / 60);
732
- const seconds = Math.floor(time % 60).toString().padStart(2, '0');
733
- hoverTime.textContent = `${minutes}:${seconds}`;
734
- hoverTime.style.display = 'block';
735
- hoverTime.style.left = `${e.clientX - rect.left}px`;
 
 
 
 
 
736
 
737
- // サムネイルプレビュー
738
- previewVideo.currentTime = time;
739
- previewVideo.style.display = "block";
740
- previewVideo.style.left = `${e.clientX}px`;
741
- previewVideo.style.bottom = `${rect.height + 10}px`;
742
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
 
744
- progressContainer.addEventListener("mouseleave", () => {
745
- hoverTime.style.display = "none";
746
- previewVideo.style.display = "none";
747
- });
748
- }
749
-
750
- // 初期設定
751
- video.controls = false;
752
- let isDragging = false;
753
- let subtitlesEnabled = true;
754
- let normalVideoWidth = videoContainer.clientWidth;
755
- let audioOnlyMode = false;
756
- // プログレスバーのイベントリスナーを更新
757
- progressContainer.addEventListener("mousemove", (e) => {
758
  if (!video.duration) return;
 
759
  const rect = progressContainer.getBoundingClientRect();
760
- const pos = (e.clientX - rect.left) / rect.width;
761
- const time = pos * video.duration;
762
 
763
- // 時間表示
 
 
 
 
 
 
 
 
 
 
 
 
 
764
  const minutes = Math.floor(time / 60);
765
  const seconds = Math.floor(time % 60).toString().padStart(2, '0');
766
- hoverTime.textContent = `${minutes}:${seconds}`;
767
- hoverTime.style.display = 'block';
768
- hoverTime.style.left = `${e.clientX - rect.left}px`;
769
 
770
- // サムネイルプレビュー
771
- previewVideo.currentTime = time;
772
- previewVideo.style.display = "block";
773
- previewVideo.style.left = `${e.clientX}px`;
774
- previewVideo.style.bottom = `${rect.height + 10}px`;
775
- });
 
 
 
 
 
776
 
777
- progressContainer.addEventListener("mouseleave", () => {
778
- hoverTime.style.display = "none";
779
- previewVideo.style.display = "none";
780
- });
781
-
782
  function updatePlaybackRate(value) {
783
  const speed = parseFloat(value);
784
  speedInput.value = speed;
785
  speedRange.value = speed;
786
  video.playbackRate = speed;
787
  }
788
-
789
  function updateVolume(value) {
790
  const volume = parseFloat(value);
791
  volumeInput.value = volume;
@@ -801,10 +882,10 @@
801
  volumeBtn.textContent = '🔊';
802
  }
803
  }
804
-
805
  function handleVideoChange() {
806
  const selected = videoSelect.value;
807
-
808
  if (selected === 'v-2.mp4') {
809
  const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
810
  if (!confirmPlay) {
@@ -812,14 +893,14 @@
812
  return;
813
  }
814
  }
815
-
816
  video.src = selected;
817
  video.load();
818
  video.play().then(() => {
819
  playPauseBtn.textContent = '⏸';
820
  }).catch(e => console.log(e));
821
  }
822
-
823
  function togglePlayPause() {
824
  if (video.paused) {
825
  video.play();
@@ -829,7 +910,7 @@
829
  playPauseBtn.textContent = '▶';
830
  }
831
  }
832
-
833
  function updateProgress() {
834
  const percent = (video.currentTime / video.duration) * 100;
835
  progressBar.style.width = `${percent}%`;
@@ -841,14 +922,14 @@
841
 
842
  timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
843
  }
844
-
845
  function setProgress(e) {
846
  const width = progressContainer.clientWidth;
847
  const clickX = e.offsetX;
848
  const duration = video.duration;
849
  video.currentTime = (clickX / width) * duration;
850
  }
851
-
852
  function toggleMute() {
853
  video.muted = !video.muted;
854
  if (video.muted) {
@@ -858,12 +939,12 @@
858
  updateVolume(video.volume);
859
  }
860
  }
861
-
862
  function handleVolumeChange() {
863
  video.muted = false;
864
  updateVolume(volumeSlider.value);
865
  }
866
-
867
  function goFullscreen() {
868
  if (
869
  document.fullscreenElement ||
@@ -890,37 +971,48 @@
890
  }
891
  }
892
 
893
- // 全画面時の右クリックメニュー
894
- function setupFullscreenContextMenu() {
895
- videoContainer.addEventListener('contextmenu', (e) => {
896
- e.preventDefault();
897
-
898
- // 全画面モードかどうかをチェック
899
- const isFullscreen = document.fullscreenElement ||
900
- document.webkitFullscreenElement ||
901
- document.msFullscreenElement;
902
-
903
- if (isFullscreen) {
904
- // コントロールパネルを表示
905
- controls.style.display = controls.style.display === 'none' ? 'flex' : 'none';
906
- }
907
- });
 
 
 
908
  }
909
-
910
- // 動画非表示モード
911
  function toggleAudioOnlyMode() {
912
- audioOnlyMode = !audioOnlyMode;
913
- audioOnlyCheckbox.checked = audioOnlyMode;
914
 
915
- if (audioOnlyMode) {
916
  videoContainer.classList.add('audio-only-mode');
917
- audioOnlyBtn.textContent = '🎬'; // 動画表示アイコンに変更
 
 
 
918
  } else {
919
  videoContainer.classList.remove('audio-only-mode');
920
- audioOnlyBtn.textContent = '🎵'; // 音声のみアイコンに戻す
 
 
 
921
  }
 
 
 
922
  }
923
-
924
  // 全画面変更時の字幕サイズ調整
925
  function updateSubtitleScaleForFullscreen() {
926
  if (document.fullscreenElement || document.webkitFullscreenElement ||
@@ -934,14 +1026,14 @@
934
  document.documentElement.style.setProperty('--fullscreen-scale', 1);
935
  }
936
  }
937
-
938
  // 字幕関連の関数
939
  function toggleSubtitles() {
940
  subtitlesEnabled = subtitleToggle.checked;
941
  subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
942
  subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
943
  }
944
-
945
  function updateSubtitleSize(value) {
946
  const size = parseFloat(value);
947
  subtitleSizeInput.value = size;
@@ -972,63 +1064,82 @@
972
  video.textTracks[0].mode = subtitlesEnabled ? 'showing' : 'hidden';
973
  }
974
  }
975
-
976
  function toggleSubtitleMenu() {
977
  document.getElementById('subtitleToggle').checked ^= true;
978
  toggleSubtitles();
979
  }
980
-
981
  // イベントリスナー
982
  videoSelect.addEventListener('change', handleVideoChange);
983
-
984
  ['input', 'change', 'mouseup'].forEach(eventName => {
985
  speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
986
  volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
987
  subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
988
  });
989
-
990
  speedInput.addEventListener('input', () => updatePlaybackRate(speedInput.value));
991
  volumeInput.addEventListener('input', () => updateVolume(volumeInput.value));
992
  subtitleSizeInput.addEventListener('input', () => updateSubtitleSize(subtitleSizeInput.value));
993
-
994
  loopCheckbox.addEventListener('change', () => {
995
  video.loop = loopCheckbox.checked;
996
  });
997
-
998
- audioOnlyCheckbox.addEventListener('change', toggleAudioOnlyMode);
999
- audioOnlyBtn.addEventListener('click', toggleAudioOnlyMode);
1000
-
1001
  subtitleToggle.addEventListener('change', toggleSubtitles);
1002
  subtitleTrack.addEventListener('change', changeSubtitleTrack);
1003
  subtitleBtn.addEventListener('click', toggleSubtitleMenu);
1004
-
1005
  playPauseBtn.addEventListener('click', togglePlayPause);
1006
  video.addEventListener('click', togglePlayPause);
1007
  video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
1008
  video.addEventListener('pause', () => playPauseBtn.textContent = '▶');
1009
  video.addEventListener('timeupdate', updateProgress);
1010
  progressContainer.addEventListener('click', setProgress);
1011
- progressContainer.addEventListener('mousedown', () => {
1012
- isDragging = true;
1013
- });
1014
- document.addEventListener('mouseup', () => {
1015
- isDragging = false;
1016
- });
1017
- progressContainer.addEventListener('mousemove', (e) => {
1018
- if (isDragging) {
1019
- setProgress(e);
1020
- }
1021
- });
1022
  volumeBtn.addEventListener('click', toggleMute);
1023
  volumeSlider.addEventListener('input', handleVolumeChange);
1024
  fullscreenBtn.addEventListener('click', goFullscreen);
1025
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
  // 全画面変更イベントを監視
1027
  document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
1028
  document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
1029
  document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
1030
  document.addEventListener('MSFullscreenChange', updateSubtitleScaleForFullscreen);
1031
-
1032
  video.addEventListener('loadedmetadata', () => {
1033
  updatePlaybackRate(speedRange.value);
1034
  updateVolume(volumeRange.value);
@@ -1038,17 +1149,15 @@ progressContainer.addEventListener('mousemove', (e) => {
1038
  updateProgress();
1039
  // 通常時の動画幅を記録
1040
  normalVideoWidth = videoContainer.clientWidth;
 
 
1041
  });
1042
-
1043
  // CSS変数を設定
1044
  document.documentElement.style.setProperty('--subtitle-scale', '1');
1045
  document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
1046
  document.documentElement.style.setProperty('--fullscreen-scale', '1');
1047
-
1048
- // 初期設定
1049
- setupHoverTime();
1050
- setupFullscreenContextMenu();
1051
  </script>
1052
  </body>
1053
 
1054
- </html>
 
105
  box-shadow: 0 0 5px #00ccff;
106
  }
107
 
108
+ /* プレビュー用スタイル */
109
+ .preview-tooltip {
110
  position: absolute;
111
+ bottom: 20px;
112
  transform: translateX(-50%);
113
+ background: rgba(0, 20, 40, 0.9);
114
+ border: 1px solid #0066ff;
115
+ padding: 5px 10px;
116
+ border-radius: 5px;
117
+ display: none;
118
+ z-index: 100;
119
+ pointer-events: none;
120
+ }
121
+
122
+ .preview-time {
123
  color: #00ccff;
 
 
124
  font-size: 12px;
125
+ text-align: center;
126
+ margin-bottom: 5px;
127
+ }
128
+
129
+ .preview-frame {
130
+ width: 160px;
131
+ height: 90px;
132
+ background-color: #000;
133
+ border: 1px solid #0066ff;
134
+ background-size: cover;
135
+ background-position: center;
136
  }
137
 
138
  .buttons-container {
 
446
  transform: rotate(360deg);
447
  }
448
  }
449
+
450
+ /* 全画面コンテキストメニュー */
451
+ .fullscreen-context-menu {
452
+ position: fixed;
453
+ top: 50%;
454
+ left: 50%;
455
+ transform: translate(-50%, -50%);
456
+ background-color: #0f0f1a;
457
+ border: 1px solid #0066ff;
458
+ box-shadow: 0 0 15px rgba(0, 102, 255, 0.5);
459
+ padding: 15px;
460
+ z-index: 1000;
461
+ display: none;
462
+ }
463
+
464
+ .fullscreen-context-menu button {
465
+ display: block;
466
+ width: 100%;
467
+ margin-bottom: 5px;
468
+ }
469
+
470
+ .fullscreen-context-menu button:last-child {
471
+ margin-bottom: 0;
472
+ }
473
+
474
+ /* 音声/字幕のみモード */
475
+ .audio-only-mode {
476
+ position: relative;
477
+ }
478
+
479
+ .audio-only-mode .video-placeholder {
480
+ display: flex;
481
+ justify-content: center;
482
+ align-items: center;
483
+ background-color: #000;
484
+ color: #00ccff;
485
+ font-size: 24px;
486
+ height: 450px; /* 動画の高さに合わせて調整 */
487
+ }
488
+
489
  .audio-only-mode video {
490
  display: none;
491
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  </style>
493
  </head>
494
 
 
505
  </div>
506
  </div>
507
 
508
+ <!-- 全画面コンテキストメニュー -->
509
+ <div class="fullscreen-context-menu" id="fullscreenContextMenu">
510
+ <button id="audioOnlyBtn">音声/字幕のみモード</button>
511
+ <button id="showVideoBtn">動画表示モード</button>
512
+ <button id="exitFullscreenBtn">全画面を終了</button>
513
+ <button id="closeContextMenuBtn">閉じる</button>
514
+ </div>
515
+
516
  <div id="ripple-container">
517
  </div>
518
  <script>
 
667
  <label for="loopCheckbox">ループ再生:</label>
668
  <input type="checkbox" id="loopCheckbox" checked>
669
  </div>
 
 
 
 
670
  <!-- 字幕設定セクション -->
671
  <div class="subtitle-settings">
672
  <div class="control-group">
 
688
  </div>
689
  <button onclick="goFullscreen()">全画面</button>
690
  </div>
691
+ <div class="video-container" id="videoContainer">
692
+ <div class="video-placeholder" id="videoPlaceholder" style="display: none;">
693
+ 音声/字幕のみモード
694
+ </div>
695
  <video id="videoPlayer" src="v.mp4">
696
  <track id="subtitleTrackElement" kind="subtitles" src="v.vtt" srclang="ja" label="日本語" default>
697
  </track>
698
  </video>
699
+ <div class="preview-tooltip" id="previewTooltip">
700
+ <div class="preview-time" id="previewTime">00:00</div>
701
+ <div class="preview-frame" id="previewFrame"></div>
702
+ </div>
703
  <div class="custom-controls">
704
  <div class="progress-container" id="progressContainer">
705
  <div class="progress-bar" id="progressBar">
706
  </div>
 
707
  </div>
708
  <div class="buttons-container">
709
  <div class="left-controls">
 
716
  <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
717
  </div>
718
  <button class="control-btn" id="subtitleBtn" title="字幕">🔤</button>
719
+ <button class="control-btn" id="audioOnlyBtn" title="音声/字幕のみ">🔈</button>
720
  <button class="control-btn" id="fullscreenBtn">⛶</button>
721
  </div>
722
  </div>
 
730
  const volumeRange = document.getElementById('volumeRange');
731
  const volumeInput = document.getElementById('volumeInput');
732
  const loopCheckbox = document.getElementById('loopCheckbox');
 
733
  const playPauseBtn = document.getElementById('playPauseBtn');
734
  const progressBar = document.getElementById('progressBar');
735
  const progressContainer = document.getElementById('progressContainer');
 
736
  const timeDisplay = document.getElementById('timeDisplay');
737
  const volumeBtn = document.getElementById('volumeBtn');
738
  const volumeSlider = document.getElementById('volumeSlider');
739
  const fullscreenBtn = document.getElementById('fullscreenBtn');
740
  const subtitleBtn = document.getElementById('subtitleBtn');
 
741
  const subtitleToggle = document.getElementById('subtitleToggle');
742
  const subtitleSize = document.getElementById('subtitleSize');
743
  const subtitleSizeInput = document.getElementById('subtitleSizeInput');
744
  const subtitleTrack = document.getElementById('subtitleTrack');
745
  const subtitleTrackElement = document.getElementById('subtitleTrackElement');
746
+ const videoContainer = document.getElementById('videoContainer');
747
+ const videoPlaceholder = document.getElementById('videoPlaceholder');
748
+ const previewTooltip = document.getElementById('previewTooltip');
749
+ const previewTime = document.getElementById('previewTime');
750
+ const previewFrame = document.getElementById('previewFrame');
751
+ const audioOnlyBtn = document.getElementById('audioOnlyBtn');
752
+ const fullscreenContextMenu = document.getElementById('fullscreenContextMenu');
753
+ const exitFullscreenBtn = document.getElementById('exitFullscreenBtn');
754
+ const showVideoBtn = document.getElementById('showVideoBtn');
755
+ const closeContextMenuBtn = document.getElementById('closeContextMenuBtn');
756
+ const contextAudioOnlyBtn = document.getElementById('audioOnlyBtn');
757
+
758
  // 初期設定
759
  video.controls = false;
760
+ let isDragging = false;
761
  let subtitlesEnabled = true;
762
  let normalVideoWidth = videoContainer.clientWidth;
763
+ let isAudioOnlyMode = false;
764
+ let canvas = null;
765
+ let previewCanvas = null;
766
+ let previewContext = null;
767
+ let isGeneratingPreview = false;
768
+
769
+ // プレビュー用キャンバスを作成
770
+ function createPreviewCanvas() {
771
+ if (!canvas) {
772
+ canvas = document.createElement('canvas');
773
+ canvas.width = video.videoWidth || 640;
774
+ canvas.height = video.videoHeight || 360;
775
+ }
 
776
 
777
+ if (!previewCanvas) {
778
+ previewCanvas = document.createElement('canvas');
779
+ previewCanvas.width = 160;
780
+ previewCanvas.height = 90;
781
+ previewContext = previewCanvas.getContext('2d');
782
+ }
783
+ }
784
+
785
+ // プレビュー画像を生成
786
+ function generatePreview(time) {
787
+ if (isGeneratingPreview) return;
788
 
789
+ try {
790
+ isGeneratingPreview = true;
791
+ createPreviewCanvas();
792
+
793
+ // 動画の現在の時間を保存
794
+ const currentTime = video.currentTime;
795
+
796
+ // 指定された時間にシーク
797
+ video.currentTime = time;
798
+
799
+ // フレームが更新されるのを待つ
800
+ video.addEventListener('seeked', function onSeeked() {
801
+ video.removeEventListener('seeked', onSeeked);
802
+
803
+ // キャンバスにフレームを描画
804
+ const ctx = canvas.getContext('2d');
805
+ canvas.width = video.videoWidth;
806
+ canvas.height = video.videoHeight;
807
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
808
+
809
+ // プレビュー用に縮小
810
+ previewContext.drawImage(canvas, 0, 0, previewCanvas.width, previewCanvas.height);
811
+
812
+ // プレビューフレームに表示
813
+ previewFrame.style.backgroundImage = `url(${previewCanvas.toDataURL()})`;
814
+
815
+ // 元の時間に戻す
816
+ video.currentTime = currentTime;
817
+ isGeneratingPreview = false;
818
+ });
819
+ } catch (e) {
820
+ console.error('Preview generation error:', e);
821
+ isGeneratingPreview = false;
822
+ }
823
+ }
824
 
825
+ // プレビューツールチップを表示
826
+ function showPreviewTooltip(e) {
 
 
 
 
 
 
 
 
 
 
 
 
827
  if (!video.duration) return;
828
+
829
  const rect = progressContainer.getBoundingClientRect();
830
+ const percent = (e.clientX - rect.left) / rect.width;
831
+ const time = percent * video.duration;
832
 
833
+ // ツールチップの位置を設定
834
+ const tooltipWidth = previewTooltip.offsetWidth;
835
+ let left = e.clientX - rect.left;
836
+
837
+ // ツールチップがコンテナからはみ出ないように調整
838
+ if (left < tooltipWidth / 2) {
839
+ left = tooltipWidth / 2;
840
+ } else if (left > rect.width - tooltipWidth / 2) {
841
+ left = rect.width - tooltipWidth / 2;
842
+ }
843
+
844
+ previewTooltip.style.left = `${left}px`;
845
+
846
+ // 時間を表示
847
  const minutes = Math.floor(time / 60);
848
  const seconds = Math.floor(time % 60).toString().padStart(2, '0');
849
+ previewTime.textContent = `${minutes}:${seconds}`;
 
 
850
 
851
+ // プレビュー画像を生成
852
+ generatePreview(time);
853
+
854
+ // ツールチップを表示
855
+ previewTooltip.style.display = 'block';
856
+ }
857
+
858
+ // プレビューツールチップを非表示
859
+ function hidePreviewTooltip() {
860
+ previewTooltip.style.display = 'none';
861
+ }
862
 
 
 
 
 
 
863
  function updatePlaybackRate(value) {
864
  const speed = parseFloat(value);
865
  speedInput.value = speed;
866
  speedRange.value = speed;
867
  video.playbackRate = speed;
868
  }
869
+
870
  function updateVolume(value) {
871
  const volume = parseFloat(value);
872
  volumeInput.value = volume;
 
882
  volumeBtn.textContent = '🔊';
883
  }
884
  }
885
+
886
  function handleVideoChange() {
887
  const selected = videoSelect.value;
888
+
889
  if (selected === 'v-2.mp4') {
890
  const confirmPlay = confirm("この動画は音量が大きいです。あらかじめ、デバイスの音量をある程度下げてください。また、音割れが起きます。再生してもよろしいですか?");
891
  if (!confirmPlay) {
 
893
  return;
894
  }
895
  }
896
+
897
  video.src = selected;
898
  video.load();
899
  video.play().then(() => {
900
  playPauseBtn.textContent = '⏸';
901
  }).catch(e => console.log(e));
902
  }
903
+
904
  function togglePlayPause() {
905
  if (video.paused) {
906
  video.play();
 
910
  playPauseBtn.textContent = '▶';
911
  }
912
  }
913
+
914
  function updateProgress() {
915
  const percent = (video.currentTime / video.duration) * 100;
916
  progressBar.style.width = `${percent}%`;
 
922
 
923
  timeDisplay.textContent = `${currentMinutes}:${currentSeconds} / ${durationMinutes}:${durationSeconds}`;
924
  }
925
+
926
  function setProgress(e) {
927
  const width = progressContainer.clientWidth;
928
  const clickX = e.offsetX;
929
  const duration = video.duration;
930
  video.currentTime = (clickX / width) * duration;
931
  }
932
+
933
  function toggleMute() {
934
  video.muted = !video.muted;
935
  if (video.muted) {
 
939
  updateVolume(video.volume);
940
  }
941
  }
942
+
943
  function handleVolumeChange() {
944
  video.muted = false;
945
  updateVolume(volumeSlider.value);
946
  }
947
+
948
  function goFullscreen() {
949
  if (
950
  document.fullscreenElement ||
 
971
  }
972
  }
973
 
974
+ // 全画面コンテキストメニューを表示
975
+ function showContextMenu(e) {
976
+ // 全画面時のみコンテキストメニューを表示
977
+ if (!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement)) {
978
+ return;
979
+ }
980
+
981
+ e.preventDefault();
982
+
983
+ // メニューの位置を設定
984
+ fullscreenContextMenu.style.display = 'block';
985
+ fullscreenContextMenu.style.left = `${e.clientX}px`;
986
+ fullscreenContextMenu.style.top = `${e.clientY}px`;
987
+ }
988
+
989
+ // 全画面コンテキストメニューを非表示
990
+ function hideContextMenu() {
991
+ fullscreenContextMenu.style.display = 'none';
992
  }
993
+
994
+ // 音声/字幕のみモードを切り替え
995
  function toggleAudioOnlyMode() {
996
+ isAudioOnlyMode = !isAudioOnlyMode;
 
997
 
998
+ if (isAudioOnlyMode) {
999
  videoContainer.classList.add('audio-only-mode');
1000
+ videoPlaceholder.style.display = 'flex';
1001
+ video.style.display = 'none';
1002
+ audioOnlyBtn.textContent = '🎥';
1003
+ audioOnlyBtn.title = '動画表示モード';
1004
  } else {
1005
  videoContainer.classList.remove('audio-only-mode');
1006
+ videoPlaceholder.style.display = 'none';
1007
+ video.style.display = 'block';
1008
+ audioOnlyBtn.textContent = '🔈';
1009
+ audioOnlyBtn.title = '音声/字幕のみ';
1010
  }
1011
+
1012
+ // コンテキストメニューも更新
1013
+ hideContextMenu();
1014
  }
1015
+
1016
  // 全画面変更時の字幕サイズ調整
1017
  function updateSubtitleScaleForFullscreen() {
1018
  if (document.fullscreenElement || document.webkitFullscreenElement ||
 
1026
  document.documentElement.style.setProperty('--fullscreen-scale', 1);
1027
  }
1028
  }
1029
+
1030
  // 字幕関連の関数
1031
  function toggleSubtitles() {
1032
  subtitlesEnabled = subtitleToggle.checked;
1033
  subtitleTrackElement.track.mode = subtitlesEnabled ? 'showing' : 'hidden';
1034
  subtitleBtn.style.color = subtitlesEnabled ? '#00ccff' : '#666';
1035
  }
1036
+
1037
  function updateSubtitleSize(value) {
1038
  const size = parseFloat(value);
1039
  subtitleSizeInput.value = size;
 
1064
  video.textTracks[0].mode = subtitlesEnabled ? 'showing' : 'hidden';
1065
  }
1066
  }
1067
+
1068
  function toggleSubtitleMenu() {
1069
  document.getElementById('subtitleToggle').checked ^= true;
1070
  toggleSubtitles();
1071
  }
1072
+
1073
  // イベントリスナー
1074
  videoSelect.addEventListener('change', handleVideoChange);
1075
+
1076
  ['input', 'change', 'mouseup'].forEach(eventName => {
1077
  speedRange.addEventListener(eventName, () => updatePlaybackRate(speedRange.value));
1078
  volumeRange.addEventListener(eventName, () => updateVolume(volumeRange.value));
1079
  subtitleSize.addEventListener(eventName, () => updateSubtitleSize(subtitleSize.value));
1080
  });
1081
+
1082
  speedInput.addEventListener('input', () => updatePlaybackRate(speedInput.value));
1083
  volumeInput.addEventListener('input', () => updateVolume(volumeInput.value));
1084
  subtitleSizeInput.addEventListener('input', () => updateSubtitleSize(subtitleSizeInput.value));
1085
+
1086
  loopCheckbox.addEventListener('change', () => {
1087
  video.loop = loopCheckbox.checked;
1088
  });
1089
+
 
 
 
1090
  subtitleToggle.addEventListener('change', toggleSubtitles);
1091
  subtitleTrack.addEventListener('change', changeSubtitleTrack);
1092
  subtitleBtn.addEventListener('click', toggleSubtitleMenu);
1093
+
1094
  playPauseBtn.addEventListener('click', togglePlayPause);
1095
  video.addEventListener('click', togglePlayPause);
1096
  video.addEventListener('play', () => playPauseBtn.textContent = '⏸');
1097
  video.addEventListener('pause', () => playPauseBtn.textContent = '▶');
1098
  video.addEventListener('timeupdate', updateProgress);
1099
  progressContainer.addEventListener('click', setProgress);
1100
+ progressContainer.addEventListener('mousedown', () => isDragging = true);
1101
+ document.addEventListener('mouseup', () => isDragging = false);
1102
+ progressContainer.addEventListener('mousemove', (e) => {
1103
+ if (isDragging) {
1104
+ setProgress(e);
1105
+ }
1106
+ showPreviewTooltip(e);
1107
+ });
1108
+ progressContainer.addEventListener('mouseenter', showPreviewTooltip);
1109
+ progressContainer.addEventListener('mouseleave', hidePreviewTooltip);
 
1110
  volumeBtn.addEventListener('click', toggleMute);
1111
  volumeSlider.addEventListener('input', handleVolumeChange);
1112
  fullscreenBtn.addEventListener('click', goFullscreen);
1113
+ audioOnlyBtn.addEventListener('click', toggleAudioOnlyMode);
1114
+
1115
+ // 全画面コンテキストメニュー関連
1116
+ videoContainer.addEventListener('contextmenu', showContextMenu);
1117
+ exitFullscreenBtn.addEventListener('click', () => {
1118
+ if (document.exitFullscreen) {
1119
+ document.exitFullscreen();
1120
+ } else if (document.webkitExitFullscreen) {
1121
+ document.webkitExitFullscreen();
1122
+ } else if (document.msExitFullscreen) {
1123
+ document.msExitFullscreen();
1124
+ }
1125
+ hideContextMenu();
1126
+ });
1127
+ showVideoBtn.addEventListener('click', () => {
1128
+ if (isAudioOnlyMode) {
1129
+ toggleAudioOnlyMode();
1130
+ }
1131
+ hideContextMenu();
1132
+ });
1133
+ contextAudioOnlyBtn.addEventListener('click', toggleAudioOnlyMode);
1134
+ closeContextMenuBtn.addEventListener('click', hideContextMenu);
1135
+ document.addEventListener('click', hideContextMenu);
1136
+
1137
  // 全画面変更イベントを監視
1138
  document.addEventListener('fullscreenchange', updateSubtitleScaleForFullscreen);
1139
  document.addEventListener('webkitfullscreenchange', updateSubtitleScaleForFullscreen);
1140
  document.addEventListener('mozfullscreenchange', updateSubtitleScaleForFullscreen);
1141
  document.addEventListener('MSFullscreenChange', updateSubtitleScaleForFullscreen);
1142
+
1143
  video.addEventListener('loadedmetadata', () => {
1144
  updatePlaybackRate(speedRange.value);
1145
  updateVolume(volumeRange.value);
 
1149
  updateProgress();
1150
  // 通常時の動画幅を記録
1151
  normalVideoWidth = videoContainer.clientWidth;
1152
+ // プレビュー用キャンバスを作成
1153
+ createPreviewCanvas();
1154
  });
1155
+
1156
  // CSS変数を設定
1157
  document.documentElement.style.setProperty('--subtitle-scale', '1');
1158
  document.documentElement.style.setProperty('--subtitle-border-radius', '10px');
1159
  document.documentElement.style.setProperty('--fullscreen-scale', '1');
 
 
 
 
1160
  </script>
1161
  </body>
1162
 
1163
+ </html>