soiz1 commited on
Commit
f287d7a
·
1 Parent(s): f567825

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +576 -629
index.html CHANGED
@@ -636,458 +636,467 @@
636
  </div>
637
  </div>
638
 
639
- <script>
640
- document.addEventListener('DOMContentLoaded', function() {
641
- // テクノロジー風背景を生成
642
- function createTechBackground() {
643
- const bg = document.getElementById('techBg');
644
-
645
- // 回路風の線を生成
646
- for (let i = 0; i < 200; i++) {
647
- const line = document.createElement('div');
648
- line.className = 'circuit-line';
649
-
650
- const isHorizontal = Math.random() > 0.5;
651
- if (isHorizontal) {
652
- line.style.width = `${Math.random() * 300 + 100}px`;
653
- line.style.height = '1px';
654
- } else {
655
- line.style.width = '1px';
656
- line.style.height = `${Math.random() * 300 + 100}px`;
657
- }
658
-
659
- line.style.left = `${Math.random() * 100}%`;
660
- line.style.top = `${Math.random() * 100}%`;
661
- line.style.opacity = Math.random() * 0.5 + 0.1;
662
-
663
- bg.appendChild(line);
664
- }
665
 
666
- // グリッドドットを生成
667
- for (let i = 0; i < 200; i++) {
668
- const dot = document.createElement('div');
669
- dot.className = 'grid-dot';
670
- dot.style.left = `${Math.random() * 100}%`;
671
- dot.style.top = `${Math.random() * 100}%`;
672
- bg.appendChild(dot);
673
  }
674
 
675
- // 六角形を生成
676
- for (let i = 0; i < 15; i++) {
677
- const hex = document.createElement('div');
678
- hex.className = 'hexagon';
679
- hex.style.left = `${Math.random() * 100}%`;
680
- hex.style.top = `${Math.random() * 100}%`;
681
- hex.style.transform = `rotate(${Math.random() * 360}deg)`;
682
- hex.style.animation = `float ${Math.random() * 10 + 5}s infinite ease-in-out`;
683
- bg.appendChild(hex);
684
- }
685
 
686
- // パルスする点を生成
687
- for (let i = 0; i < 8; i++) {
688
- const pulse = document.createElement('div');
689
- pulse.className = 'pulse';
690
- pulse.style.left = `${Math.random() * 100}%`;
691
- pulse.style.top = `${Math.random() * 100}%`;
692
- pulse.style.animationDelay = `${Math.random() * 2}s`;
693
- bg.appendChild(pulse);
694
- }
695
  }
696
 
697
- createTechBackground();
 
 
 
 
 
 
 
698
 
699
- // ローディング状態を管理
700
- let loadingCount = 0;
701
- let totalToLoad = 6; // 動画 + 5音声ファイル
702
- let lastUpdateTime = 0;
703
- const updateInterval = 50; // 50msごとに更新 (20fps)
 
 
 
 
 
704
 
705
- // ローディング完了チェック
706
- function checkLoadingComplete() {
707
- loadingCount++;
708
- if (loadingCount >= totalToLoad) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  setTimeout(function() {
710
- const loadingOverlay = document.getElementById('loadingOverlay');
711
- loadingOverlay.style.opacity = '0';
712
- setTimeout(function() {
713
- loadingOverlay.style.display = 'none';
714
- }, 1000);
715
- }, 500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
716
  }
 
 
717
  }
718
-
719
- // エラーハンドリング関数
720
- function handleError(error, message) {
721
- console.error(message, error);
722
- window.alert(`${message}\n\nエラー詳細: ${error.message}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
  }
724
-
725
- // ピッチ設定関数
726
- function setPreservesPitch(el, value) {
727
- try {
728
- if ('preservesPitch' in el) {
729
- el.preservesPitch = value; // モダンブラウザ
730
- } else if ('webkitPreservesPitch' in el) {
731
- el.webkitPreservesPitch = value; // 古い WebKit (Chrome <86 など)
732
- } else if ('mozPreservesPitch' in el) {
733
- el.mozPreservesPitch = value; // 古い Gecko (Firefox ≤100)
734
- }
735
- } catch (error) {
736
- handleError(error, 'ピッチ設定中にエラーが発生しました');
 
 
737
  }
 
 
 
 
738
  }
 
 
 
 
 
 
 
739
 
740
- // 要素を取得
741
- const video = document.getElementById('video');
742
- const videoContainer = document.getElementById('video-container');
743
- const playPauseBtn = document.getElementById('play-pause-btn');
744
- const timeDisplay = document.getElementById('time-display');
745
- const progressContainer = document.getElementById('progress-container');
746
- const progressBar = document.getElementById('progress-bar');
747
- const progressTime = document.getElementById('progress-time');
748
- const volumeBtn = document.getElementById('volume-btn');
749
- const volumeSlider = document.getElementById('volume-slider');
750
- const speedSlider = document.getElementById('speed-slider');
751
- const speedValue = document.getElementById('speed-value');
752
- const playbackSpeedSlider = document.getElementById('playback-speed');
753
- const playbackSpeedValue = document.getElementById('playback-speed-value');
754
- const fullscreenBtn = document.getElementById('fullscreen-btn');
755
- const startTimeInput = document.getElementById('start-time');
756
- const endTimeInput = document.getElementById('end-time');
757
- const loopCheckbox = document.getElementById('loop');
758
- const globalVolumeSlider = document.getElementById('global-volume');
759
- const globalVolumeValue = document.getElementById('global-volume-value');
760
- const audioSliders = document.querySelectorAll('.audio-slider');
761
- const volumeValues = document.querySelectorAll('.volume-value');
762
- const setStartTimeBtn = document.getElementById('set-start-time');
763
- const setEndTimeBtn = document.getElementById('set-end-time');
764
-
765
- // 音声オブジェクトを作成
766
- const audioElements = {};
767
-
768
- // 音声ファイル名の配列
769
- const audioFiles = ['p', 'a', 't', 's', 'k'];
770
-
771
- // 初期化
772
- let videoDuration = 0;
773
- let isPlaying = false;
774
- let isVideoPlaying = false;
775
- let lastVolume = 1;
776
- let currentPlaybackRate = 1;
777
- let isFullscreen = false;
778
-
779
- // 動画のメタデータが読み込まれたら
780
- video.addEventListener('loadedmetadata', function() {
781
- try {
782
- videoDuration = video.duration;
783
- endTimeInput.value = videoDuration.toFixed(2);
784
- endTimeInput.max = videoDuration;
785
- startTimeInput.max = videoDuration - 0.1;
786
- updateTimeDisplay();
787
- checkLoadingComplete();
788
- } catch (error) {
789
- handleError(error, '動画メタデータ読み込み中にエラーが発生しました');
790
  }
791
- });
792
-
793
- // 動画エラー処理
794
- video.addEventListener('error', function() {
795
- handleError(video.error, '動画読み込み中にエラーが発生しました');
796
- });
797
-
798
- // 再生ボタンクリック
799
- playPauseBtn.addEventListener('click', function() {
800
- try {
801
- // 現在の秒数が終了秒数を超えている場合は開始位置に戻す
802
- const endTime = parseFloat(endTimeInput.value) || videoDuration;
803
- if (video.currentTime >= endTime) {
804
- const startTime = parseFloat(startTimeInput.value) || 0;
805
- seekMedia(startTime);
806
- }
807
-
808
- togglePlayPause();
809
 
810
- // 音声要素の再生を許可
811
  audioFiles.forEach(file => {
812
  if (audioElements[file]) {
 
813
  audioElements[file].play().catch(e => {
814
  if (e.name !== 'AbortError') {
815
- handleError(e, `音声再生中にエラーが発生しました (${file}.mp3)`);
816
  }
817
  });
818
  }
819
  });
820
- } catch (error) {
821
- handleError(error, '再生/一時停止操作中にエラーが発生しました');
822
- }
823
- });
824
-
825
- // 時間表示を更新
826
- function updateTimeDisplay() {
827
- const now = performance.now();
828
- if (now - lastUpdateTime < updateInterval && !isFullscreen) return;
829
- lastUpdateTime = now;
 
 
 
 
830
 
831
- try {
832
- const currentTime = video.currentTime;
833
- const duration = video.duration || videoDuration;
834
-
835
- const currentMinutes = Math.floor(currentTime / 60);
836
- const currentSeconds = Math.floor(currentTime % 60);
837
- const currentMilliseconds = Math.floor((currentTime % 1) * 100);
838
- const durationMinutes = Math.floor(duration / 60);
839
- const durationSeconds = Math.floor(duration % 60);
840
- const durationMilliseconds = Math.floor((duration % 1) * 100);
841
-
842
- timeDisplay.textContent =
843
- `${String(currentMinutes).padStart(2, '0')}:${String(currentSeconds).padStart(2, '0')}.${String(currentMilliseconds).padStart(2, '0')} / ` +
844
- `${String(durationMinutes).padStart(2, '0')}:${String(durationSeconds).padStart(2, '0')}.${String(durationMilliseconds).padStart(2, '0')}`;
845
-
846
- // プログレスバーを更新
847
- const progressPercent = (currentTime / duration) * 100;
848
- progressBar.style.width = `${progressPercent}%`;
849
-
850
- // 現在の秒数が終了秒数を超えている場合
851
- const endTime = parseFloat(endTimeInput.value) || duration;
852
- if (currentTime >= endTime && endTime > 0) {
853
- if (loopCheckbox.checked) {
854
- const startTime = parseFloat(startTimeInput.value) || 0;
855
- seekMedia(startTime);
856
- if (!isPlaying) {
857
- playMedia();
858
- }
859
- } else {
860
- pauseMedia();
861
- }
862
  }
863
- } catch (error) {
864
- handleError(error, '時間表示更新中にエラーが発生しました');
865
- }
866
  }
 
 
 
 
 
 
867
 
868
- // 動画の時間更新イベント
869
- video.addEventListener('timeupdate', function() {
870
- updateTimeDisplay();
871
- });
872
-
873
- // 動画終了時の処理
874
- video.addEventListener('ended', function() {
875
- try {
876
- if (!loopCheckbox.checked) {
877
- const startTime = parseFloat(startTimeInput.value) || 0;
878
- seekMedia(startTime);
879
- pauseMedia();
880
- }
881
- } catch (error) {
882
- handleError(error, '動画終了処理中にエラーが発生しました');
883
- }
884
- });
885
-
886
- // プログレスバークリックでシーク
887
- progressContainer.addEventListener('click', function(e) {
888
- try {
889
- if (!video.duration) return;
890
-
891
- const rect = this.getBoundingClientRect();
892
- const pos = (e.clientX - rect.left) / rect.width;
893
- const seekTime = pos * video.duration;
894
-
895
- seekMedia(seekTime);
896
- } catch (error) {
897
- handleError(error, 'シーク操作中にエラーが発生しました');
898
- }
899
- });
900
-
901
- // 指定した時間にシーク
902
- function seekMedia(time) {
903
- try {
904
- const duration = video.duration || videoDuration;
905
  const startTime = parseFloat(startTimeInput.value) || 0;
906
- const endTime = parseFloat(endTimeInput.value) || duration;
907
-
908
- // 範囲内に制限
909
- const seekTime = Math.max(startTime, Math.min(time, endTime));
910
-
911
- video.currentTime = seekTime;
912
 
913
  // 音声も同期
914
  audioFiles.forEach(file => {
915
  if (audioElements[file]) {
916
- audioElements[file].currentTime = seekTime;
 
 
 
 
 
 
 
917
  }
918
  });
919
- } catch (error) {
920
- handleError(error, 'メディアシーク中にエラーが発生しました');
921
- }
922
- }
923
-
924
- // プログレスバー上でマウス移動時に時間を表示
925
- progressContainer.addEventListener('mousemove', function(e) {
926
- try {
927
- if (!video.duration) return;
928
-
929
- const rect = this.getBoundingClientRect();
930
- const pos = (e.clientX - rect.left) / rect.width;
931
- const hoverTime = pos * video.duration;
932
-
933
- const minutes = Math.floor(hoverTime / 60);
934
- const seconds = Math.floor(hoverTime % 60);
935
- const milliseconds = Math.floor((hoverTime % 1) * 100);
936
-
937
- progressTime.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(milliseconds).padStart(2, '0')}`;
938
- progressTime.style.display = 'block';
939
- progressTime.style.left = `${pos * 100}%`;
940
- } catch (error) {
941
- handleError(error, 'プログレスバー操作中にエラーが発生しました');
942
- }
943
- });
944
-
945
- progressContainer.addEventListener('mouseleave', function() {
946
- progressTime.style.display = 'none';
947
- });
948
-
949
- // 動画クリックで再生/一時停止
950
- video.addEventListener('click', function() {
951
- togglePlayPause();
952
- });
953
-
954
- // 再生/一時停止をトグル
955
- function togglePlayPause() {
956
- if (isVideoPlaying) {
957
- pauseMedia();
958
  } else {
959
- playMedia();
 
960
  }
961
  }
962
 
963
- // 音量コントロール
964
- volumeSlider.addEventListener('input', function() {
965
- try {
966
- video.volume = this.value;
967
- lastVolume = this.value;
968
- updateVolumeIcon();
969
- } catch (error) {
970
- handleError(error, '音量設定中にエラーが発生しました');
971
- }
972
- });
973
-
974
- // 音量ボタン
975
- volumeBtn.addEventListener('click', function() {
976
- try {
977
- if (video.volume > 0) {
978
- lastVolume = video.volume;
979
- video.volume = 0;
980
- volumeSlider.value = 0;
981
- } else {
982
- video.volume = lastVolume;
983
- volumeSlider.value = lastVolume;
 
 
 
 
 
 
 
 
 
 
 
 
 
984
  }
985
- updateVolumeIcon();
986
- } catch (error) {
987
- handleError(error, '音量切り替え中にエラーが発生しました');
988
- }
989
- });
990
-
991
- // 音量アイコンを更新
992
- function updateVolumeIcon() {
993
- if (video.volume === 0) {
994
- volumeBtn.textContent = '🔇';
995
- } else if (video.volume < 0.5) {
996
- volumeBtn.textContent = '🔈';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
997
  } else {
998
- volumeBtn.textContent = '🔊';
 
999
  }
 
 
 
1000
  }
1001
-
1002
- // 再生速度スライダー (動画プレイヤー)
1003
- speedSlider.addEventListener('input', function() {
1004
- try {
1005
- const speed = parseFloat(this.value);
1006
- speedValue.textContent = speed.toFixed(2) + 'x';
1007
- playbackSpeedSlider.value = speed;
1008
- playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
1009
- updatePlaybackRate(speed);
1010
- } catch (error) {
1011
- handleError(error, '再生速度設定中にエラーが発生しました');
1012
- }
1013
- });
1014
-
1015
- // 再生速度スライダー (設定メニュー)
1016
- playbackSpeedSlider.addEventListener('input', function() {
1017
- try {
1018
- const speed = parseFloat(this.value);
1019
- playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
1020
- speedSlider.value = speed;
1021
- speedValue.textContent = speed.toFixed(2) + 'x';
1022
- updatePlaybackRate(speed);
1023
- } catch (error) {
1024
- handleError(error, '再生速度設定中にエラーが発生しました');
1025
- }
1026
- });
1027
-
1028
- function updatePlaybackRate(speed) {
1029
- try {
1030
- currentPlaybackRate = speed;
1031
- video.playbackRate = speed;
1032
-
1033
- // 音声の再生速度を変更
1034
- audioFiles.forEach(file => {
1035
- if (audioElements[file]) {
1036
- audioElements[file].playbackRate = speed;
1037
- setPreservesPitch(audioElements[file], true); // ピッチを保持
1038
- }
1039
- });
1040
- } catch (error) {
1041
- handleError(error, '再生速度更新中にエラーが発生しました');
1042
- }
1043
  }
1044
-
1045
- // 全画面ボタン
1046
- fullscreenBtn.addEventListener('click', function() {
1047
- try {
1048
- if (!isFullscreen) {
1049
- if (videoContainer.requestFullscreen) {
1050
- videoContainer.requestFullscreen();
1051
- } else if (videoContainer.webkitRequestFullscreen) {
1052
- videoContainer.webkitRequestFullscreen();
1053
- } else if (videoContainer.msRequestFullscreen) {
1054
- videoContainer.msRequestFullscreen();
1055
- }
1056
- } else {
1057
- if (document.exitFullscreen) {
1058
- document.exitFullscreen();
1059
- } else if (document.webkitExitFullscreen) {
1060
- document.webkitExitFullscreen();
1061
- } else if (document.msExitFullscreen) {
1062
- document.msExitFullscreen();
1063
- }
1064
- }
1065
- } catch (error) {
1066
- handleError(error, '全画面表示中にエラーが発生しました');
1067
- }
1068
- });
1069
-
1070
- // 全画面変更イベント
1071
- document.addEventListener('fullscreenchange', handleFullscreenChange);
1072
- document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
1073
- document.addEventListener('msfullscreenchange', handleFullscreenChange);
1074
-
1075
- function handleFullscreenChange() {
1076
- isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
1077
- fullscreenBtn.textContent = isFullscreen ? '⛶' : '⛶';
1078
 
1079
- if (isFullscreen) {
1080
- // 全画面時の処理
1081
- video.controls = false;
1082
- } else {
1083
- // 通常表示時の処理
1084
- video.controls = false;
1085
- }
 
 
1086
  }
1087
-
1088
- // キーボードイベント (ESCで全画面終了)
1089
- document.addEventListener('keydown', function(e) {
1090
- if (e.key === 'Escape' && isFullscreen) {
 
 
 
 
 
 
 
 
 
 
1091
  if (document.exitFullscreen) {
1092
  document.exitFullscreen();
1093
  } else if (document.webkitExitFullscreen) {
@@ -1096,246 +1105,165 @@
1096
  document.msExitFullscreen();
1097
  }
1098
  }
1099
- });
1100
-
1101
- // 音声ファイルをロード
1102
- function loadAudioFiles() {
1103
- audioFiles.forEach(file => {
1104
- try {
1105
- const audio = new Audio(`${file}.mp3`);
1106
- audio.preload = 'auto';
1107
- setPreservesPitch(audio, true); // ピッチを保持
1108
- audio.loop = false;
1109
- audioElements[file] = audio;
1110
-
1111
- // 音声が読み込まれたら
1112
- audio.addEventListener('loadedmetadata', function() {
1113
- console.log(`${file}.mp3 loaded`);
1114
- checkLoadingComplete();
1115
- });
1116
-
1117
- // エラー処理
1118
- audio.addEventListener('error', function() {
1119
- handleError(audio.error, `音声ファイル読み込み中にエラーが発生しました (${file}.mp3)`);
1120
- checkLoadingComplete(); // エラー時もカウント
1121
- });
1122
- } catch (error) {
1123
- handleError(error, `音声ファイル初期化中にエラーが発生しました (${file}.mp3)`);
1124
- checkLoadingComplete(); // エラー時もカウント
1125
- }
1126
- });
1127
  }
 
 
 
 
 
 
 
 
 
 
1128
 
1129
- function playAudio(file, startTime) {
1130
- if (!audioElements[file]) return;
1131
-
1132
- try {
1133
- const audio = audioElements[file];
1134
- const duration = video.duration || videoDuration;
1135
- const endTime = parseFloat(endTimeInput.value) || duration;
1136
-
1137
- // ボリューム設定
1138
- const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
1139
- const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value/10);
1140
- audio.volume = volume;
1141
-
1142
- // 再生速度設定
1143
- audio.playbackRate = currentPlaybackRate;
1144
- setPreservesPitch(audio, true);
1145
-
1146
- // 再生時間計算
1147
- const playStartTime = Math.max(startTime, parseFloat(startTimeInput.value) || 0);
1148
- const playEndTime = Math.min(endTime, duration);
1149
-
1150
- audio.currentTime = playStartTime;
1151
-
1152
- // 再生
1153
- audio.play().catch(e => {
1154
- if (e.name !== 'AbortError') {
1155
- handleError(e, `音声再生中にエラーが発生しました (${file}.mp3)`);
1156
- }
1157
- });
1158
-
1159
- // ループ設定
1160
- audio.loop = loopCheckbox.checked;
1161
-
1162
- if (!loopCheckbox.checked) {
1163
- // ループ再生でない場合、終了時間に達したら停止
1164
- setTimeout(() => {
1165
- if (audio.currentTime >= playEndTime) {
1166
- audio.pause();
1167
- }
1168
- }, (playEndTime - playStartTime) * 1000);
1169
- }
1170
- } catch (error) {
1171
- handleError(error, `音声再生中にエラーが発生しました (${file}.mp3)`);
1172
  }
1173
  }
1174
-
1175
- // 再生関数
1176
- function playMedia() {
 
 
1177
  try {
1178
- const startTime = video.currentTime;
1179
- const endTime = parseFloat(endTimeInput.value) || videoDuration;
 
 
 
1180
 
1181
- // 終了時間が動画の長さを超えないように
1182
- const actualEndTime = Math.min(endTime, videoDuration);
1183
-
1184
- // 現在位置が終了時間を超えている場合、開始位置に戻る
1185
- if (startTime >= actualEndTime) {
1186
- video.currentTime = parseFloat(startTimeInput.value) || 0;
1187
- }
1188
-
1189
- // 動画を再生
1190
- video.play().then(() => {
1191
- isPlaying = true;
1192
- isVideoPlaying = true;
1193
- playPauseBtn.textContent = '⏸';
1194
- }).catch(error => {
1195
- handleError(error, '動画再生中にエラーが発生しました');
1196
  });
1197
 
1198
- // 音声を再生
1199
- audioFiles.forEach(file => {
1200
- playAudio(file, video.currentTime);
1201
- });
1202
- } catch (error) {
1203
- handleError(error, 'メディア再生中にエラーが発生しました');
1204
- }
1205
- }
1206
-
1207
- // 一時停止関数
1208
- function pauseMedia() {
1209
- try {
1210
- video.pause();
1211
- isPlaying = false;
1212
- isVideoPlaying = false;
1213
- playPauseBtn.textContent = '▶';
1214
-
1215
- audioFiles.forEach(file => {
1216
- if (audioElements[file]) {
1217
- audioElements[file].pause();
1218
- }
1219
  });
1220
  } catch (error) {
1221
- handleError(error, 'メディア一時停止中にエラーが発生しました');
 
1222
  }
1223
- }
1224
-
1225
- // ボリュームスライダーのイベント
1226
- audioSliders.forEach((slider, index) => {
1227
- slider.addEventListener('input', function() {
1228
- try {
1229
- const value = parseFloat(this.value);
1230
- volumeValues[index].textContent = value.toFixed(2);
1231
-
1232
- if (audioElements[this.dataset.audio]) {
1233
- const globalVolume = parseFloat(globalVolumeSlider.value/10);
1234
- audioElements[this.dataset.audio].volume = value * globalVolume;
1235
- }
1236
- } catch (error) {
1237
- handleError(error, '音声ボリューム設定中にエラーが発生しました');
1238
- }
1239
- });
1240
  });
1241
-
1242
- // 全体音量スライダーのイベント
1243
- globalVolumeSlider.addEventListener('input', function() {
 
 
1244
  try {
1245
  const value = parseFloat(this.value);
1246
- globalVolumeValue.textContent = value.toFixed(1);
1247
 
1248
- audioFiles.forEach(file => {
1249
- if (audioElements[file]) {
1250
- const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
1251
- const volume = parseFloat(volumeSlider.value) * (value/10);
1252
- audioElements[file].volume = volume;
1253
- }
1254
- });
1255
- } catch (error) {
1256
- handleError(error, '全体音量設定中にエラーが発生しました');
1257
- }
1258
- });
1259
-
1260
- // ループ設定変更時
1261
- loopCheckbox.addEventListener('change', function() {
1262
- try {
1263
- audioFiles.forEach(file => {
1264
- if (audioElements[file]) {
1265
- audioElements[file].loop = this.checked;
1266
- }
1267
- });
1268
- } catch (error) {
1269
- handleError(error, 'ループ設定変更中にエラーが発生しました');
1270
- }
1271
- });
1272
-
1273
- // 現在の秒数を開始時間に設定
1274
- setStartTimeBtn.addEventListener('click', function() {
1275
- try {
1276
- startTimeInput.value = video.currentTime.toFixed(2);
1277
- } catch (error) {
1278
- handleError(error, '開始時間設定中にエラーが発生しました');
1279
- }
1280
- });
1281
-
1282
- // 現在の秒数を終了時間に設定
1283
- setEndTimeBtn.addEventListener('click', function() {
1284
- try {
1285
- endTimeInput.value = video.currentTime.toFixed(2);
1286
  } catch (error) {
1287
- handleError(error, '終了時間設定中にエラーが発生しました');
1288
  }
1289
  });
1290
-
1291
- // スライダーの背景を更新
1292
- function updateSliderBackgrounds() {
1293
- // ボリュームスライダー
1294
- const volumePercent = volumeSlider.value * 100;
1295
- volumeSlider.style.backgroundSize = `${volumePercent}% 100%`;
1296
-
1297
- // 再生速度スライダー
1298
- const speedPercent = (speedSlider.value - speedSlider.min) / (speedSlider.max - speedSlider.min) * 100;
1299
- speedSlider.style.backgroundSize = `${speedPercent}% 100%`;
1300
-
1301
- // 全体音量スライダー
1302
- const globalVolumePercent = (globalVolumeSlider.value - globalVolumeSlider.min) / (globalVolumeSlider.max - globalVolumeSlider.min) * 100;
1303
- globalVolumeSlider.style.backgroundSize = `${globalVolumePercent}% 100%`;
1304
-
1305
- // 再生速度スライダー (設定)
1306
- const playbackSpeedPercent = (playbackSpeedSlider.value - playbackSpeedSlider.min) / (playbackSpeedSlider.max - playbackSpeedSlider.min) * 100;
1307
- playbackSpeedSlider.style.backgroundSize = `${playbackSpeedPercent}% 100%`;
1308
 
1309
- // 各音声スライダー
1310
- audioSliders.forEach(slider => {
1311
- const percent = slider.value * 100;
1312
- slider.style.backgroundSize = `${percent}% 100%`;
 
 
1313
  });
 
 
1314
  }
1315
-
1316
- // スライダーの初期背景を設定
1317
- function initSliderBackgrounds() {
1318
- // すべてのスライダーに背景を設定
1319
- const sliders = [
1320
- volumeSlider,
1321
- speedSlider,
1322
- globalVolumeSlider,
1323
- playbackSpeedSlider,
1324
- ...audioSliders
1325
- ];
1326
-
1327
- sliders.forEach(slider => {
1328
- if (slider) {
1329
- slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)';
1330
- slider.style.backgroundRepeat = 'no-repeat';
1331
  }
1332
  });
1333
-
1334
- updateSliderBackgrounds();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1335
  }
 
 
 
 
 
 
 
 
 
 
 
1336
 
1337
- // スライダー変更時に背景を更新
1338
- const allSliders = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1339
  volumeSlider,
1340
  speedSlider,
1341
  globalVolumeSlider,
@@ -1343,19 +1271,38 @@
1343
  ...audioSliders
1344
  ];
1345
 
1346
- allSliders.forEach(slider => {
1347
  if (slider) {
1348
- slider.addEventListener('input', updateSliderBackgrounds);
 
1349
  }
1350
  });
1351
 
1352
- // 初期化
1353
- loadAudioFiles();
1354
- updateVolumeIcon();
1355
- volumeSlider.value = video.volume;
1356
- video.controls = false;
1357
- initSliderBackgrounds();
 
 
 
 
 
 
 
 
 
 
1358
  });
1359
- </script>
 
 
 
 
 
 
 
 
1360
  </body>
1361
  </html>
 
636
  </div>
637
  </div>
638
 
639
+ <script>
640
+ document.addEventListener('DOMContentLoaded', function() {
641
+ // テクノロジー風背景を生成
642
+ function createTechBackground() {
643
+ const bg = document.getElementById('techBg');
644
+
645
+ // 回路風の線を生成
646
+ for (let i = 0; i < 200; i++) {
647
+ const line = document.createElement('div');
648
+ line.className = 'circuit-line';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
650
+ const isHorizontal = Math.random() > 0.5;
651
+ if (isHorizontal) {
652
+ line.style.width = `${Math.random() * 300 + 100}px`;
653
+ line.style.height = '1px';
654
+ } else {
655
+ line.style.width = '1px';
656
+ line.style.height = `${Math.random() * 300 + 100}px`;
657
  }
658
 
659
+ line.style.left = `${Math.random() * 100}%`;
660
+ line.style.top = `${Math.random() * 100}%`;
661
+ line.style.opacity = Math.random() * 0.5 + 0.1;
 
 
 
 
 
 
 
662
 
663
+ bg.appendChild(line);
 
 
 
 
 
 
 
 
664
  }
665
 
666
+ // グリッドドットを生成
667
+ for (let i = 0; i < 200; i++) {
668
+ const dot = document.createElement('div');
669
+ dot.className = 'grid-dot';
670
+ dot.style.left = `${Math.random() * 100}%`;
671
+ dot.style.top = `${Math.random() * 100}%`;
672
+ bg.appendChild(dot);
673
+ }
674
 
675
+ // 六角形を生成
676
+ for (let i = 0; i < 15; i++) {
677
+ const hex = document.createElement('div');
678
+ hex.className = 'hexagon';
679
+ hex.style.left = `${Math.random() * 100}%`;
680
+ hex.style.top = `${Math.random() * 100}%`;
681
+ hex.style.transform = `rotate(${Math.random() * 360}deg)`;
682
+ hex.style.animation = `float ${Math.random() * 10 + 5}s infinite ease-in-out`;
683
+ bg.appendChild(hex);
684
+ }
685
 
686
+ // パルスする点を生成
687
+ for (let i = 0; i < 8; i++) {
688
+ const pulse = document.createElement('div');
689
+ pulse.className = 'pulse';
690
+ pulse.style.left = `${Math.random() * 100}%`;
691
+ pulse.style.top = `${Math.random() * 100}%`;
692
+ pulse.style.animationDelay = `${Math.random() * 2}s`;
693
+ bg.appendChild(pulse);
694
+ }
695
+ }
696
+
697
+ createTechBackground();
698
+
699
+ // ローディング状態を管理
700
+ let loadingCount = 0;
701
+ let totalToLoad = 6; // 動画 + 5音声ファイル
702
+ let lastUpdateTime = 0;
703
+ const updateInterval = 50; // 50msごとに更新 (20fps)
704
+
705
+ // ローディング完了チェック
706
+ function checkLoadingComplete() {
707
+ loadingCount++;
708
+ if (loadingCount >= totalToLoad) {
709
+ setTimeout(function() {
710
+ const loadingOverlay = document.getElementById('loadingOverlay');
711
+ loadingOverlay.style.opacity = '0';
712
  setTimeout(function() {
713
+ loadingOverlay.style.display = 'none';
714
+ }, 1000);
715
+ }, 500);
716
+ }
717
+ }
718
+
719
+ // エラーハンドリング関数
720
+ function handleError(error, message) {
721
+ console.error(message, error);
722
+ window.alert(`${message}\n\nエラー詳細: ${error.message}`);
723
+ }
724
+
725
+ // ピッチ設定関数
726
+ function setPreservesPitch(el, value) {
727
+ try {
728
+ if ('preservesPitch' in el) {
729
+ el.preservesPitch = value; // モダンブラウザ
730
+ } else if ('webkitPreservesPitch' in el) {
731
+ el.webkitPreservesPitch = value; // 古い WebKit (Chrome <86 など)
732
+ } else if ('mozPreservesPitch' in el) {
733
+ el.mozPreservesPitch = value; // 古い Gecko (Firefox ≤100)
734
  }
735
+ } catch (error) {
736
+ handleError(error, 'ピッチ設定中にエラーが発生しました');
737
  }
738
+ }
739
+
740
+ // 要素を取得
741
+ const video = document.getElementById('video');
742
+ const videoContainer = document.getElementById('video-container');
743
+ const playPauseBtn = document.getElementById('play-pause-btn');
744
+ const timeDisplay = document.getElementById('time-display');
745
+ const progressContainer = document.getElementById('progress-container');
746
+ const progressBar = document.getElementById('progress-bar');
747
+ const progressTime = document.getElementById('progress-time');
748
+ const volumeBtn = document.getElementById('volume-btn');
749
+ const volumeSlider = document.getElementById('volume-slider');
750
+ const speedSlider = document.getElementById('speed-slider');
751
+ const speedValue = document.getElementById('speed-value');
752
+ const playbackSpeedSlider = document.getElementById('playback-speed');
753
+ const playbackSpeedValue = document.getElementById('playback-speed-value');
754
+ const fullscreenBtn = document.getElementById('fullscreen-btn');
755
+ const startTimeInput = document.getElementById('start-time');
756
+ const endTimeInput = document.getElementById('end-time');
757
+ const loopCheckbox = document.getElementById('loop');
758
+ const globalVolumeSlider = document.getElementById('global-volume');
759
+ const globalVolumeValue = document.getElementById('global-volume-value');
760
+ const audioSliders = document.querySelectorAll('.audio-slider');
761
+ const volumeValues = document.querySelectorAll('.volume-value');
762
+ const setStartTimeBtn = document.getElementById('set-start-time');
763
+ const setEndTimeBtn = document.getElementById('set-end-time');
764
+
765
+ // 音声オブジェクトを作成
766
+ const audioElements = {};
767
+
768
+ // 音声ファイル名の配列
769
+ const audioFiles = ['p', 'a', 't', 's', 'k'];
770
+
771
+ // 初期化
772
+ let videoDuration = 0;
773
+ let isPlaying = false;
774
+ let lastVolume = 1;
775
+ let currentPlaybackRate = 1;
776
+ let isFullscreen = false;
777
+
778
+ // 動画のメタデータが読み込まれた���
779
+ video.addEventListener('loadedmetadata', function() {
780
+ try {
781
+ videoDuration = video.duration;
782
+ endTimeInput.value = videoDuration.toFixed(2);
783
+ endTimeInput.max = videoDuration;
784
+ startTimeInput.max = videoDuration - 0.1;
785
+ updateTimeDisplay();
786
+ checkLoadingComplete();
787
+ } catch (error) {
788
+ handleError(error, '動画メタデータ読み込み中にエラーが発生しました');
789
  }
790
+ });
791
+
792
+ // 動画エラー処理
793
+ video.addEventListener('error', function() {
794
+ handleError(video.error, '動画読み込み中にエラーが発生しました');
795
+ });
796
+
797
+ // 再生ボタンクリック
798
+ playPauseBtn.addEventListener('click', function() {
799
+ try {
800
+ // 現在の秒数が終了秒数を超えている場合は開始位置に戻す
801
+ const endTime = parseFloat(endTimeInput.value) || videoDuration;
802
+ if (video.currentTime >= endTime) {
803
+ const startTime = parseFloat(startTimeInput.value) || 0;
804
+ seekMedia(startTime);
805
  }
806
+
807
+ togglePlayPause();
808
+ } catch (error) {
809
+ handleError(error, '再生/一時停止操作中にエラーが発生しました');
810
  }
811
+ });
812
+
813
+ // 時間表示を更新
814
+ function updateTimeDisplay() {
815
+ const now = performance.now();
816
+ if (now - lastUpdateTime < updateInterval && !isFullscreen) return;
817
+ lastUpdateTime = now;
818
 
819
+ try {
820
+ const currentTime = video.currentTime;
821
+ const duration = video.duration || videoDuration;
822
+
823
+ const currentMinutes = Math.floor(currentTime / 60);
824
+ const currentSeconds = Math.floor(currentTime % 60);
825
+ const currentMilliseconds = Math.floor((currentTime % 1) * 100);
826
+ const durationMinutes = Math.floor(duration / 60);
827
+ const durationSeconds = Math.floor(duration % 60);
828
+ const durationMilliseconds = Math.floor((duration % 1) * 100);
829
+
830
+ timeDisplay.textContent =
831
+ `${String(currentMinutes).padStart(2, '0')}:${String(currentSeconds).padStart(2, '0')}.${String(currentMilliseconds).padStart(2, '0')} / ` +
832
+ `${String(durationMinutes).padStart(2, '0')}:${String(durationSeconds).padStart(2, '0')}.${String(durationMilliseconds).padStart(2, '0')}`;
833
+
834
+ // プログレスバーを更新
835
+ const progressPercent = (currentTime / duration) * 100;
836
+ progressBar.style.width = `${progressPercent}%`;
837
+ } catch (error) {
838
+ handleError(error, '時間表示更新中にエラーが発生しました');
839
+ }
840
+ }
841
+
842
+ // 再生/一時停止をトグル
843
+ function togglePlayPause() {
844
+ if (isPlaying) {
845
+ pauseMedia();
846
+ } else {
847
+ playMedia();
848
+ }
849
+ }
850
+
851
+ // 再生関数
852
+ function playMedia() {
853
+ try {
854
+ const duration = video.duration || videoDuration;
855
+ const startTime = parseFloat(startTimeInput.value) || 0;
856
+ const endTime = parseFloat(endTimeInput.value) || duration;
857
+
858
+ // 現在位置が終了時間を超えている場合、開始位置に戻る
859
+ if (video.currentTime >= endTime) {
860
+ video.currentTime = startTime;
 
 
 
 
 
 
 
 
861
  }
862
+
863
+ // 動画を再生
864
+ video.play().then(() => {
865
+ isPlaying = true;
866
+ playPauseBtn.textContent = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
867
 
868
+ // 音声を再生
869
  audioFiles.forEach(file => {
870
  if (audioElements[file]) {
871
+ audioElements[file].currentTime = video.currentTime;
872
  audioElements[file].play().catch(e => {
873
  if (e.name !== 'AbortError') {
874
+ console.error(`音声再生エラー (${file}.mp3):`, e);
875
  }
876
  });
877
  }
878
  });
879
+ }).catch(error => {
880
+ console.error('動画再生エラー:', error);
881
+ });
882
+ } catch (error) {
883
+ console.error('メディア再生エラー:', error);
884
+ }
885
+ }
886
+
887
+ // 一時停止関数
888
+ function pauseMedia() {
889
+ try {
890
+ video.pause();
891
+ isPlaying = false;
892
+ playPauseBtn.textContent = '▶';
893
 
894
+ // 音声を一時停止
895
+ audioFiles.forEach(file => {
896
+ if (audioElements[file]) {
897
+ audioElements[file].pause();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
  }
899
+ });
900
+ } catch (error) {
901
+ console.error('メディア一時停止エラー:', error);
902
  }
903
+ }
904
+
905
+ // 時間更新時の処理
906
+ video.addEventListener('timeupdate', function() {
907
+ const duration = video.duration || videoDuration;
908
+ const endTime = parseFloat(endTimeInput.value) || duration;
909
 
910
+ // 終了時間に達した場合の処理
911
+ if (video.currentTime >= endTime && endTime > 0) {
912
+ if (loopCheckbox.checked) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  const startTime = parseFloat(startTimeInput.value) || 0;
914
+ video.currentTime = startTime;
 
 
 
 
 
915
 
916
  // 音声も同期
917
  audioFiles.forEach(file => {
918
  if (audioElements[file]) {
919
+ audioElements[file].currentTime = startTime;
920
+ if (isPlaying) {
921
+ audioElements[file].play().catch(e => {
922
+ if (e.name !== 'AbortError') {
923
+ console.error(`音声再生エラー (${file}.mp3):`, e);
924
+ }
925
+ });
926
+ }
927
  }
928
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
  } else {
930
+ pauseMedia();
931
+ video.currentTime = endTime;
932
  }
933
  }
934
 
935
+ updateTimeDisplay();
936
+ });
937
+
938
+ // プログレスバークリックでシーク
939
+ progressContainer.addEventListener('click', function(e) {
940
+ try {
941
+ if (!video.duration) return;
942
+
943
+ const rect = this.getBoundingClientRect();
944
+ const pos = (e.clientX - rect.left) / rect.width;
945
+ const seekTime = pos * video.duration;
946
+
947
+ seekMedia(seekTime);
948
+ } catch (error) {
949
+ handleError(error, 'シーク操作中にエラーが発生しました');
950
+ }
951
+ });
952
+
953
+ // 指定した時間にシーク
954
+ function seekMedia(time) {
955
+ try {
956
+ const duration = video.duration || videoDuration;
957
+ const startTime = parseFloat(startTimeInput.value) || 0;
958
+ const endTime = parseFloat(endTimeInput.value) || duration;
959
+
960
+ // 範囲内に制限
961
+ const seekTime = Math.max(startTime, Math.min(time, endTime));
962
+
963
+ video.currentTime = seekTime;
964
+
965
+ // 音声も同期
966
+ audioFiles.forEach(file => {
967
+ if (audioElements[file]) {
968
+ audioElements[file].currentTime = seekTime;
969
  }
970
+ });
971
+ } catch (error) {
972
+ handleError(error, 'メディアシーク中にエラーが発生しました');
973
+ }
974
+ }
975
+
976
+ // プログレスバー上でマウス移動時に時間を表示
977
+ progressContainer.addEventListener('mousemove', function(e) {
978
+ try {
979
+ if (!video.duration) return;
980
+
981
+ const rect = this.getBoundingClientRect();
982
+ const pos = (e.clientX - rect.left) / rect.width;
983
+ const hoverTime = pos * video.duration;
984
+
985
+ const minutes = Math.floor(hoverTime / 60);
986
+ const seconds = Math.floor(hoverTime % 60);
987
+ const milliseconds = Math.floor((hoverTime % 1) * 100);
988
+
989
+ progressTime.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(milliseconds).padStart(2, '0')}`;
990
+ progressTime.style.display = 'block';
991
+ progressTime.style.left = `${pos * 100}%`;
992
+ } catch (error) {
993
+ handleError(error, 'プログレスバー操作中にエラーが発生しました');
994
+ }
995
+ });
996
+
997
+ progressContainer.addEventListener('mouseleave', function() {
998
+ progressTime.style.display = 'none';
999
+ });
1000
+
1001
+ // 動画クリックで再生/一時停止
1002
+ video.addEventListener('click', function() {
1003
+ togglePlayPause();
1004
+ });
1005
+
1006
+ // 音量コントロール
1007
+ volumeSlider.addEventListener('input', function() {
1008
+ try {
1009
+ video.volume = this.value;
1010
+ lastVolume = this.value;
1011
+ updateVolumeIcon();
1012
+ } catch (error) {
1013
+ handleError(error, '音量設定中にエラーが発生しました');
1014
+ }
1015
+ });
1016
+
1017
+ // 音量ボタン
1018
+ volumeBtn.addEventListener('click', function() {
1019
+ try {
1020
+ if (video.volume > 0) {
1021
+ lastVolume = video.volume;
1022
+ video.volume = 0;
1023
+ volumeSlider.value = 0;
1024
  } else {
1025
+ video.volume = lastVolume;
1026
+ volumeSlider.value = lastVolume;
1027
  }
1028
+ updateVolumeIcon();
1029
+ } catch (error) {
1030
+ handleError(error, '音量切り替え中にエラーが発生しました');
1031
  }
1032
+ });
1033
+
1034
+ // 音量アイコンを更新
1035
+ function updateVolumeIcon() {
1036
+ if (video.volume === 0) {
1037
+ volumeBtn.textContent = '🔇';
1038
+ } else if (video.volume < 0.5) {
1039
+ volumeBtn.textContent = '🔈';
1040
+ } else {
1041
+ volumeBtn.textContent = '🔊';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
  }
1043
+ }
1044
+
1045
+ // 再生速度スライダー (動画プレイヤー)
1046
+ speedSlider.addEventListener('input', function() {
1047
+ try {
1048
+ const speed = parseFloat(this.value);
1049
+ speedValue.textContent = speed.toFixed(2) + 'x';
1050
+ playbackSpeedSlider.value = speed;
1051
+ playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
1052
+ updatePlaybackRate(speed);
1053
+ } catch (error) {
1054
+ handleError(error, '再生速度設定中にエラーが発生しました');
1055
+ }
1056
+ });
1057
+
1058
+ // 再生速度スライダー (設定メニュー)
1059
+ playbackSpeedSlider.addEventListener('input', function() {
1060
+ try {
1061
+ const speed = parseFloat(this.value);
1062
+ playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
1063
+ speedSlider.value = speed;
1064
+ speedValue.textContent = speed.toFixed(2) + 'x';
1065
+ updatePlaybackRate(speed);
1066
+ } catch (error) {
1067
+ handleError(error, '再生速度設定中にエラーが発生しました');
1068
+ }
1069
+ });
1070
+
1071
+ function updatePlaybackRate(speed) {
1072
+ try {
1073
+ currentPlaybackRate = speed;
1074
+ video.playbackRate = speed;
 
 
1075
 
1076
+ // 音声の再生速度を変更
1077
+ audioFiles.forEach(file => {
1078
+ if (audioElements[file]) {
1079
+ audioElements[file].playbackRate = speed;
1080
+ setPreservesPitch(audioElements[file], true); // ピッチを保持
1081
+ }
1082
+ });
1083
+ } catch (error) {
1084
+ handleError(error, '再生速度更新中にエラーが発生しました');
1085
  }
1086
+ }
1087
+
1088
+ // 全画面ボタン
1089
+ fullscreenBtn.addEventListener('click', function() {
1090
+ try {
1091
+ if (!isFullscreen) {
1092
+ if (videoContainer.requestFullscreen) {
1093
+ videoContainer.requestFullscreen();
1094
+ } else if (videoContainer.webkitRequestFullscreen) {
1095
+ videoContainer.webkitRequestFullscreen();
1096
+ } else if (videoContainer.msRequestFullscreen) {
1097
+ videoContainer.msRequestFullscreen();
1098
+ }
1099
+ } else {
1100
  if (document.exitFullscreen) {
1101
  document.exitFullscreen();
1102
  } else if (document.webkitExitFullscreen) {
 
1105
  document.msExitFullscreen();
1106
  }
1107
  }
1108
+ } catch (error) {
1109
+ handleError(error, '全画面表示中にエラーが発生しました');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1110
  }
1111
+ });
1112
+
1113
+ // 全画面変更イベント
1114
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
1115
+ document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
1116
+ document.addEventListener('msfullscreenchange', handleFullscreenChange);
1117
+
1118
+ function handleFullscreenChange() {
1119
+ isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
1120
+ fullscreenBtn.textContent = isFullscreen ? '⛶' : '⛶';
1121
 
1122
+ if (isFullscreen) {
1123
+ // 全画面時の処理
1124
+ video.controls = false;
1125
+ } else {
1126
+ // 通常表示時の処理
1127
+ video.controls = false;
1128
+ }
1129
+ }
1130
+
1131
+ // キーボードイベント (ESCで全画面終了)
1132
+ document.addEventListener('keydown', function(e) {
1133
+ if (e.key === 'Escape' && isFullscreen) {
1134
+ if (document.exitFullscreen) {
1135
+ document.exitFullscreen();
1136
+ } else if (document.webkitExitFullscreen) {
1137
+ document.webkitExitFullscreen();
1138
+ } else if (document.msExitFullscreen) {
1139
+ document.msExitFullscreen();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1140
  }
1141
  }
1142
+ });
1143
+
1144
+ // 音声ファイルをロード
1145
+ function loadAudioFiles() {
1146
+ audioFiles.forEach(file => {
1147
  try {
1148
+ const audio = new Audio(`${file}.mp3`);
1149
+ audio.preload = 'auto';
1150
+ setPreservesPitch(audio, true); // ピッチを保持
1151
+ audio.loop = false;
1152
+ audioElements[file] = audio;
1153
 
1154
+ // 音声が読み込まれたら
1155
+ audio.addEventListener('loadedmetadata', function() {
1156
+ console.log(`${file}.mp3 loaded`);
1157
+ checkLoadingComplete();
 
 
 
 
 
 
 
 
 
 
 
1158
  });
1159
 
1160
+ // エラー処理
1161
+ audio.addEventListener('error', function() {
1162
+ handleError(audio.error, `音声ファイル読み込み中にエラーが発生しました (${file}.mp3)`);
1163
+ checkLoadingComplete(); // エラー時もカウント
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1164
  });
1165
  } catch (error) {
1166
+ handleError(error, `音声ファイル初期化中にエラーが発生しました (${file}.mp3)`);
1167
+ checkLoadingComplete(); // エラー時もカウント
1168
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1169
  });
1170
+ }
1171
+
1172
+ // ボリュームスライダーのイベント
1173
+ audioSliders.forEach((slider, index) => {
1174
+ slider.addEventListener('input', function() {
1175
  try {
1176
  const value = parseFloat(this.value);
1177
+ volumeValues[index].textContent = value.toFixed(2);
1178
 
1179
+ if (audioElements[this.dataset.audio]) {
1180
+ const globalVolume = parseFloat(globalVolumeSlider.value/10);
1181
+ audioElements[this.dataset.audio].volume = value * globalVolume;
1182
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1183
  } catch (error) {
1184
+ handleError(error, '音声ボリューム設定中にエラーが発生しました');
1185
  }
1186
  });
1187
+ });
1188
+
1189
+ // 全体音量スライダーのイベント
1190
+ globalVolumeSlider.addEventListener('input', function() {
1191
+ try {
1192
+ const value = parseFloat(this.value);
1193
+ globalVolumeValue.textContent = value.toFixed(1);
 
 
 
 
 
 
 
 
 
 
 
1194
 
1195
+ audioFiles.forEach(file => {
1196
+ if (audioElements[file]) {
1197
+ const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
1198
+ const volume = parseFloat(volumeSlider.value) * (value/10);
1199
+ audioElements[file].volume = volume;
1200
+ }
1201
  });
1202
+ } catch (error) {
1203
+ handleError(error, '全体音量設定中にエラーが発生しました');
1204
  }
1205
+ });
1206
+
1207
+ // ループ設定変更時
1208
+ loopCheckbox.addEventListener('change', function() {
1209
+ try {
1210
+ audioFiles.forEach(file => {
1211
+ if (audioElements[file]) {
1212
+ audioElements[file].loop = this.checked;
 
 
 
 
 
 
 
 
1213
  }
1214
  });
1215
+ } catch (error) {
1216
+ handleError(error, 'ループ設定変更中にエラーが発生しました');
1217
+ }
1218
+ });
1219
+
1220
+ // 現在の秒数を開始時間に設定
1221
+ setStartTimeBtn.addEventListener('click', function() {
1222
+ try {
1223
+ startTimeInput.value = video.currentTime.toFixed(2);
1224
+ } catch (error) {
1225
+ handleError(error, '開始時間設定中にエラーが発生しました');
1226
+ }
1227
+ });
1228
+
1229
+ // 現在の秒数を終了時間に設定
1230
+ setEndTimeBtn.addEventListener('click', function() {
1231
+ try {
1232
+ endTimeInput.value = video.currentTime.toFixed(2);
1233
+ } catch (error) {
1234
+ handleError(error, '終了時間設定中にエラーが発生しました');
1235
  }
1236
+ });
1237
+
1238
+ // スライダーの背景を更新
1239
+ function updateSliderBackgrounds() {
1240
+ // ボリュームスライダー
1241
+ const volumePercent = volumeSlider.value * 100;
1242
+ volumeSlider.style.backgroundSize = `${volumePercent}% 100%`;
1243
+
1244
+ // 再生速度スライダー
1245
+ const speedPercent = (speedSlider.value - speedSlider.min) / (speedSlider.max - speedSlider.min) * 100;
1246
+ speedSlider.style.backgroundSize = `${speedPercent}% 100%`;
1247
 
1248
+ // 全体音量スライダー
1249
+ const globalVolumePercent = (globalVolumeSlider.value - globalVolumeSlider.min) / (globalVolumeSlider.max - globalVolumeSlider.min) * 100;
1250
+ globalVolumeSlider.style.backgroundSize = `${globalVolumePercent}% 100%`;
1251
+
1252
+ // 再生速度スライダー (設定)
1253
+ const playbackSpeedPercent = (playbackSpeedSlider.value - playbackSpeedSlider.min) / (playbackSpeedSlider.max - playbackSpeedSlider.min) * 100;
1254
+ playbackSpeedSlider.style.backgroundSize = `${playbackSpeedPercent}% 100%`;
1255
+
1256
+ // 各音声スライダー
1257
+ audioSliders.forEach(slider => {
1258
+ const percent = slider.value * 100;
1259
+ slider.style.backgroundSize = `${percent}% 100%`;
1260
+ });
1261
+ }
1262
+
1263
+ // スライダーの初期背景を設定
1264
+ function initSliderBackgrounds() {
1265
+ // すべてのスライダーに背景を設定
1266
+ const sliders = [
1267
  volumeSlider,
1268
  speedSlider,
1269
  globalVolumeSlider,
 
1271
  ...audioSliders
1272
  ];
1273
 
1274
+ sliders.forEach(slider => {
1275
  if (slider) {
1276
+ slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)';
1277
+ slider.style.backgroundRepeat = 'no-repeat';
1278
  }
1279
  });
1280
 
1281
+ updateSliderBackgrounds();
1282
+ }
1283
+
1284
+ // スライダー変更時に背景を更新
1285
+ const allSliders = [
1286
+ volumeSlider,
1287
+ speedSlider,
1288
+ globalVolumeSlider,
1289
+ playbackSpeedSlider,
1290
+ ...audioSliders
1291
+ ];
1292
+
1293
+ allSliders.forEach(slider => {
1294
+ if (slider) {
1295
+ slider.addEventListener('input', updateSliderBackgrounds);
1296
+ }
1297
  });
1298
+
1299
+ // 初期化
1300
+ loadAudioFiles();
1301
+ updateVolumeIcon();
1302
+ volumeSlider.value = video.volume;
1303
+ video.controls = false;
1304
+ initSliderBackgrounds();
1305
+ });
1306
+ </script>
1307
  </body>
1308
  </html>