soiz1 commited on
Commit
1c1f695
·
verified ·
1 Parent(s): 1846b2d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +225 -657
index.html CHANGED
@@ -38,38 +38,7 @@
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,7 +82,6 @@
113
  background: #001133;
114
  margin-bottom: 10px;
115
  cursor: pointer;
116
- position: relative;
117
  }
118
 
119
  .progress-bar {
@@ -136,36 +104,6 @@
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,21 +291,6 @@
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,63 +415,6 @@
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,14 +431,6 @@
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,7 +573,7 @@
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,18 +606,11 @@
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,547 +627,264 @@
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
 
 
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
  background: #001133;
83
  margin-bottom: 10px;
84
  cursor: pointer;
 
85
  }
86
 
87
  .progress-bar {
 
104
  box-shadow: 0 0 5px #00ccff;
105
  }
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  .buttons-container {
108
  display: flex;
109
  align-items: center;
 
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
  transform: rotate(360deg);
416
  }
417
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  </style>
419
  </head>
420
 
 
431
  </div>
432
  </div>
433
 
 
 
 
 
 
 
 
 
434
  <div id="ripple-container">
435
  </div>
436
  <script>
 
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
  </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
  <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