soiz1 commited on
Commit
8d6ff0a
·
1 Parent(s): 71ac320

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +212 -138
index.html CHANGED
@@ -598,6 +598,30 @@
598
  .disabled-message p {
599
  margin-bottom: 15px;
600
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  </style>
602
  </head>
603
  <body>
@@ -606,11 +630,10 @@
606
  document.addEventListener('DOMContentLoaded', function() {
607
  const urlParams = new URLSearchParams(window.location.search);
608
  const isTMode = urlParams.has('mode') && urlParams.get('mode') === 't';
609
- const videoSource = document.querySelector('#video source'); // source要素を取得
610
 
611
  if (isTMode && videoSource) {
612
  videoSource.src = '/t/v.mp4';
613
- // 動画を再読み込み
614
  const video = document.getElementById('video');
615
  video.load();
616
  }
@@ -635,6 +658,11 @@ document.addEventListener('DOMContentLoaded', function() {
635
 
636
  <div class="container">
637
  <div class="video-container" id="video-container">
 
 
 
 
 
638
  <!-- 無効状態のオーバーレイ -->
639
  <div class="disabled-overlay" id="disabledOverlay">
640
  <div class="disabled-message">
@@ -751,7 +779,19 @@ document.addEventListener('DOMContentLoaded', function() {
751
 
752
  <script>
753
  document.addEventListener('DOMContentLoaded', function() {
754
-
 
 
 
 
 
 
 
 
 
 
 
 
755
  // テクノロジー風背景を生成
756
  function createTechBackground() {
757
  const bg = document.getElementById('techBg');
@@ -833,14 +873,6 @@ document.addEventListener('DOMContentLoaded', function() {
833
  window.alert(`${message}\n\nエラー詳細: ${error.message}`);
834
  }
835
 
836
- // Web Audio Context の初期化
837
- let audioContext;
838
- try {
839
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
840
- } catch (e) {
841
- console.error('Web Audio APIがサポートされていません:', e);
842
- }
843
-
844
  // 要素を取得
845
  const video = document.getElementById('video');
846
  const videoContainer = document.getElementById('video-container');
@@ -871,6 +903,8 @@ document.addEventListener('DOMContentLoaded', function() {
871
  const previewSection = document.getElementById('preview-section');
872
  const previewButton = document.getElementById('preview-button');
873
  const previewTime = document.getElementById('preview-time');
 
 
874
 
875
  // 音声オブジェクトを作成
876
  const audioElements = {};
@@ -887,10 +921,92 @@ document.addEventListener('DOMContentLoaded', function() {
887
  let lastVolume = 1;
888
  let currentPlaybackRate = 1;
889
  let isFullscreen = false;
890
- let lastSyncTime = 0;
891
- let syncDriftLog = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
 
893
- // 音声ファイルをロード (改良版)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
894
  function loadAudioFiles() {
895
  audioFiles.forEach(file => {
896
  try {
@@ -1012,23 +1128,23 @@ document.addEventListener('DOMContentLoaded', function() {
1012
  }
1013
  }
1014
 
1015
- function applyVolume() {
1016
- if (!isAudioCombined) return;
1017
-
1018
- // ベース音量 (0-1)
1019
- const baseVolume = parseFloat(volumeSlider.value);
1020
- // グローバル音量係数 (0-10)
1021
- const globalVolume = parseFloat(globalVolumeSlider.value) / 10; // 0-1に変換
1022
-
1023
- // 最終音量 (0-1)
1024
- const finalVolume = Math.max(0, Math.min(1, baseVolume * globalVolume));
1025
-
1026
- // 動画の音量を設定
1027
- video.volume = finalVolume;
1028
-
1029
- // 音量アイコンを更新
1030
- updateVolumeIcon();
1031
- }
1032
 
1033
  // 再生速度を適用(ピッチ維持)
1034
  function applyPlaybackRate() {
@@ -1041,13 +1157,13 @@ function applyVolume() {
1041
  if (combinedAudioSource) {
1042
  combinedAudioSource.playbackRate.value = speed;
1043
  // ピッチを維持する設定
1044
- if ('preservesPitch' in combinedAudioSource) {
1045
- combinedAudioSource.preservesPitch = value; // モダンブラウザ
1046
- } else if ('webkitPreservesPitch' in combinedAudioSource) {
1047
- combinedAudioSource.webkitPreservesPitch = value; // 古い WebKit (Chrome <86 など)
1048
- } else if ('mozPreservesPitch' in combinedAudioSource) {
1049
- combinedAudioSource.mozPreservesPitch = value; // 古い Gecko (Firefox ≤100)
1050
- }
1051
  }
1052
 
1053
  speedValue.textContent = speed.toFixed(2) + 'x';
@@ -1070,11 +1186,6 @@ function applyVolume() {
1070
  setStartTimeBtn.disabled = false;
1071
  setEndTimeBtn.disabled = false;
1072
  playbackSpeedSlider.disabled = false;
1073
-
1074
- // 合成後に音量と再生速度スライダーを有効化
1075
- volumeSlider.disabled = false;
1076
- speedSlider.disabled = false;
1077
- playbackSpeedSlider.disabled = false;
1078
  }
1079
 
1080
  // プレビュー再生
@@ -1211,7 +1322,6 @@ function applyVolume() {
1211
  video.currentTime = startTime;
1212
  }
1213
 
1214
- // 動画を再生
1215
  const playPromise = video.play();
1216
 
1217
  if (playPromise !== undefined) {
@@ -1219,29 +1329,10 @@ function applyVolume() {
1219
  isPlaying = true;
1220
  playPauseBtn.textContent = '⏸';
1221
 
1222
- // Web Audio APIで合成音声を再生
1223
- if (combinedAudioSource) {
1224
- combinedAudioSource.stop();
1225
- }
1226
-
1227
- combinedAudioSource = audioContext.createBufferSource();
1228
- combinedAudioSource.buffer = combinedAudioBuffer;
1229
- combinedAudioSource.connect(audioContext.destination);
1230
- combinedAudioSource.start(0, video.currentTime);
1231
 
1232
- // 再生速度を設定
1233
  video.playbackRate = currentPlaybackRate;
1234
- combinedAudioSource.playbackRate.value = currentPlaybackRate;
1235
-
1236
- // 動画と音声の同期を維持
1237
- combinedAudioSource.onended = () => {
1238
- if (loopCheckbox.checked) {
1239
- video.currentTime = startTime;
1240
- playMedia();
1241
- } else {
1242
- pauseMedia();
1243
- }
1244
- };
1245
  }).catch(error => {
1246
  console.error('動画再生エラー:', error);
1247
  });
@@ -1257,6 +1348,7 @@ function applyVolume() {
1257
  video.pause();
1258
  isPlaying = false;
1259
  playPauseBtn.textContent = '▶';
 
1260
 
1261
  if (combinedAudioSource) {
1262
  combinedAudioSource.stop();
@@ -1276,15 +1368,7 @@ function applyVolume() {
1276
  if (loopCheckbox.checked) {
1277
  const startTime = parseFloat(startTimeInput.value) || 0;
1278
  video.currentTime = startTime;
1279
-
1280
- if (combinedAudioSource) {
1281
- combinedAudioSource.stop();
1282
- combinedAudioSource = audioContext.createBufferSource();
1283
- combinedAudioSource.buffer = combinedAudioBuffer;
1284
- combinedAudioSource.connect(audioContext.destination);
1285
- combinedAudioSource.start(0, startTime);
1286
- combinedAudioSource.playbackRate.value = currentPlaybackRate;
1287
- }
1288
  } else {
1289
  pauseMedia();
1290
  video.currentTime = endTime;
@@ -1318,7 +1402,7 @@ function applyVolume() {
1318
  if (combinedAudioSource) {
1319
  combinedAudioSource.stop();
1320
  combinedAudioSource = audioContext.createBufferSource();
1321
- combinedAudioBuffer = combinedAudioBuffer;
1322
  combinedAudioSource.connect(audioContext.destination);
1323
 
1324
  if (isPlaying) {
@@ -1356,30 +1440,26 @@ function applyVolume() {
1356
  video.addEventListener('click', function() {
1357
  togglePlayPause();
1358
  });
 
 
 
 
 
 
 
1359
 
1360
- volumeSlider.addEventListener('input', function() {
1361
- if (!isAudioCombined) return;
1362
-
1363
- // ベース音量を保存
1364
- lastVolume = parseFloat(this.value);
1365
-
1366
- // 音量を適用
1367
- applyVolume();
1368
- });
1369
-
1370
- volumeBtn.addEventListener('click', function() {
1371
- if (!isAudioCombined) return;
1372
-
1373
- if (video.volume > 0) {
1374
- lastVolume = parseFloat(volumeSlider.value);
1375
- volumeSlider.value = 0;
1376
- } else {
1377
- volumeSlider.value = lastVolume;
1378
- }
1379
-
1380
- // 音量を適用
1381
- applyVolume();
1382
- });
1383
 
1384
  // 音量アイコンを更新
1385
  function updateVolumeIcon() {
@@ -1476,23 +1556,20 @@ volumeBtn.addEventListener('click', function() {
1476
  const value = parseFloat(this.value);
1477
  volumeValues[index].textContent = value.toFixed(2);
1478
 
1479
- // スライダーの背景を更新
1480
  const percent = value * 100;
1481
  this.style.backgroundSize = `${percent}% 100%`;
1482
  });
1483
  });
1484
 
1485
- globalVolumeSlider.addEventListener('input', function() {
1486
- const value = parseFloat(this.value);
1487
- globalVolumeValue.textContent = value.toFixed(1);
1488
-
1489
- // スライ��ーの背景を更新
1490
- const percent = (value - this.min) / (this.max - this.min) * 100;
1491
- this.style.backgroundSize = `${percent}% 100%`;
1492
-
1493
- // 音量をリアルタイムで適用
1494
- applyVolume();
1495
- });
1496
 
1497
  // ループ設定変更時
1498
  loopCheckbox.addEventListener('change', function() {
@@ -1522,35 +1599,32 @@ globalVolumeSlider.addEventListener('input', function() {
1522
  video.controls = false;
1523
 
1524
  // スライダーの背景を初期化
1525
- // 初期化時にスライダーの背景を設定 (修正版)
1526
- function initSliderBackgrounds() {
1527
- const sliders = [
1528
- volumeSlider,
1529
- speedSlider,
1530
- globalVolumeSlider,
1531
- playbackSpeedSlider,
1532
- ...audioSliders
1533
- ];
1534
-
1535
- sliders.forEach(slider => {
1536
- if (slider) {
1537
- slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)';
1538
- slider.style.backgroundRepeat = 'no-repeat';
1539
-
1540
- if (slider === globalVolumeSlider) {
1541
- // 0-10の範囲で初期化
1542
- const percent = (slider.value - slider.min) / (slider.max - slider.min) * 100;
1543
- slider.style.backgroundSize = `${percent}% 100%`;
1544
- globalVolumeValue.textContent = slider.value;
1545
- } else {
1546
- slider.style.backgroundSize = `${slider.value * 100}% 100%`;
1547
  }
1548
- }
1549
- });
1550
- }
1551
 
1552
  initSliderBackgrounds();
 
1553
  });
1554
- </script>
1555
- </body>
1556
- </html>
 
598
  .disabled-message p {
599
  margin-bottom: 15px;
600
  }
601
+ #buffering-indicator {
602
+ position: absolute;
603
+ top: 50%;
604
+ left: 50%;
605
+ transform: translate(-50%, -50%);
606
+ background-color: rgba(0, 0, 0, 0.7);
607
+ color: white;
608
+ padding: 10px 20px;
609
+ border-radius: 5px;
610
+ z-index: 10;
611
+ display: none;
612
+ }
613
+
614
+ .sync-status {
615
+ position: absolute;
616
+ bottom: 60px;
617
+ left: 10px;
618
+ background-color: rgba(0, 0, 0, 0.7);
619
+ color: #64ffda;
620
+ padding: 5px 10px;
621
+ border-radius: 3px;
622
+ font-size: 12px;
623
+ z-index: 5;
624
+ }
625
  </style>
626
  </head>
627
  <body>
 
630
  document.addEventListener('DOMContentLoaded', function() {
631
  const urlParams = new URLSearchParams(window.location.search);
632
  const isTMode = urlParams.has('mode') && urlParams.get('mode') === 't';
633
+ const videoSource = document.querySelector('#video source');
634
 
635
  if (isTMode && videoSource) {
636
  videoSource.src = '/t/v.mp4';
 
637
  const video = document.getElementById('video');
638
  video.load();
639
  }
 
658
 
659
  <div class="container">
660
  <div class="video-container" id="video-container">
661
+ <!-- バッファリングインジケーター -->
662
+ <div id="buffering-indicator">読み込み中...</div>
663
+ <!-- 同期ステータス -->
664
+ <div class="sync-status" id="sync-status"></div>
665
+
666
  <!-- 無効状態のオーバーレイ -->
667
  <div class="disabled-overlay" id="disabledOverlay">
668
  <div class="disabled-message">
 
779
 
780
  <script>
781
  document.addEventListener('DOMContentLoaded', function() {
782
+ // 同期管理用の変数
783
+ let lastSyncTime = 0;
784
+ let isBuffering = false;
785
+ let syncDriftLog = [];
786
+ let syncCheckInterval;
787
+ let audioContext;
788
+
789
+ try {
790
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
791
+ } catch (e) {
792
+ console.error('Web Audio APIがサポートされていません:', e);
793
+ }
794
+
795
  // テクノロジー風背景を生成
796
  function createTechBackground() {
797
  const bg = document.getElementById('techBg');
 
873
  window.alert(`${message}\n\nエラー詳細: ${error.message}`);
874
  }
875
 
 
 
 
 
 
 
 
 
876
  // 要素を取得
877
  const video = document.getElementById('video');
878
  const videoContainer = document.getElementById('video-container');
 
903
  const previewSection = document.getElementById('preview-section');
904
  const previewButton = document.getElementById('preview-button');
905
  const previewTime = document.getElementById('preview-time');
906
+ const bufferingIndicator = document.getElementById('buffering-indicator');
907
+ const syncStatus = document.getElementById('sync-status');
908
 
909
  // 音声オブジェクトを作成
910
  const audioElements = {};
 
921
  let lastVolume = 1;
922
  let currentPlaybackRate = 1;
923
  let isFullscreen = false;
924
+
925
+ // 動画のバッファリング状態を監視
926
+ video.addEventListener('waiting', function() {
927
+ isBuffering = true;
928
+ bufferingIndicator.style.display = 'block';
929
+ if (combinedAudioSource) {
930
+ combinedAudioSource.stop();
931
+ combinedAudioSource = null;
932
+ }
933
+ });
934
+
935
+ video.addEventListener('playing', function() {
936
+ isBuffering = false;
937
+ bufferingIndicator.style.display = 'none';
938
+ if (!combinedAudioSource && isPlaying) {
939
+ syncAudioWithVideo();
940
+ }
941
+ });
942
+
943
+ video.addEventListener('suspend', function() {
944
+ console.log('動画の読み込みが一時停止しました');
945
+ });
946
 
947
+ video.addEventListener('stalled', function() {
948
+ console.log('動画の読み込みが停滞しました');
949
+ if (isPlaying) {
950
+ pauseMedia();
951
+ }
952
+ });
953
+
954
+ // 動画と音声の同期をチェックする関数
955
+ function startSyncCheck() {
956
+ if (syncCheckInterval) clearInterval(syncCheckInterval);
957
+ syncCheckInterval = setInterval(checkSync, 100); // 100msごとにチェック
958
+ }
959
+
960
+ function stopSyncCheck() {
961
+ if (syncCheckInterval) clearInterval(syncCheckInterval);
962
+ }
963
+
964
+ function checkSync() {
965
+ if (!isAudioCombined || !isPlaying || isBuffering) return;
966
+
967
+ const videoTime = video.currentTime;
968
+ const audioTime = audioContext.currentTime - (combinedAudioSource?.startTime || 0);
969
+ const drift = videoTime - audioTime;
970
+
971
+ // ズレを記録(直近5回分)
972
+ syncDriftLog.push(drift);
973
+ if (syncDriftLog.length > 5) syncDriftLog.shift();
974
+
975
+ // 平均ズレを計算
976
+ const avgDrift = syncDriftLog.reduce((a, b) => a + b, 0) / syncDriftLog.length;
977
+
978
+ // ズレ表示を更新
979
+ syncStatus.textContent = `同期ズレ: ${avgDrift.toFixed(3)}秒`;
980
+
981
+ // ズレが大きい場合(0.1秒以上)に修正
982
+ if (Math.abs(avgDrift) > 0.1) {
983
+ console.log(`同期ズレを修正: ${avgDrift.toFixed(3)}秒`);
984
+ syncAudioWithVideo();
985
+ }
986
+ }
987
+
988
+ // 音声を動画に同期させる関数
989
+ function syncAudioWithVideo() {
990
+ if (!isAudioCombined || !isPlaying) return;
991
+
992
+ const currentTime = video.currentTime;
993
+
994
+ if (combinedAudioSource) {
995
+ combinedAudioSource.stop();
996
+ }
997
+
998
+ combinedAudioSource = audioContext.createBufferSource();
999
+ combinedAudioSource.buffer = combinedAudioBuffer;
1000
+ combinedAudioSource.connect(audioContext.destination);
1001
+ combinedAudioSource.start(0, currentTime);
1002
+ combinedAudioSource.playbackRate.value = currentPlaybackRate;
1003
+
1004
+ // ズレ記録をリセット
1005
+ syncDriftLog = [];
1006
+ lastSyncTime = performance.now();
1007
+ }
1008
+
1009
+ // 音声ファイルをロード
1010
  function loadAudioFiles() {
1011
  audioFiles.forEach(file => {
1012
  try {
 
1128
  }
1129
  }
1130
 
1131
+ function applyVolume() {
1132
+ if (!isAudioCombined) return;
1133
+
1134
+ // ベース音量 (0-1)
1135
+ const baseVolume = parseFloat(volumeSlider.value);
1136
+ // グローバル音量係数 (0-10)
1137
+ const globalVolume = parseFloat(globalVolumeSlider.value) / 10; // 0-1に変換
1138
+
1139
+ // 最終音量 (0-1)
1140
+ const finalVolume = Math.max(0, Math.min(1, baseVolume * globalVolume));
1141
+
1142
+ // 動画の音量を設定
1143
+ video.volume = finalVolume;
1144
+
1145
+ // 音量アイコンを更新
1146
+ updateVolumeIcon();
1147
+ }
1148
 
1149
  // 再生速度を適用(ピッチ維持)
1150
  function applyPlaybackRate() {
 
1157
  if (combinedAudioSource) {
1158
  combinedAudioSource.playbackRate.value = speed;
1159
  // ピッチを維持する設定
1160
+ if ('preservesPitch' in combinedAudioSource) {
1161
+ combinedAudioSource.preservesPitch = true;
1162
+ } else if ('webkitPreservesPitch' in combinedAudioSource) {
1163
+ combinedAudioSource.webkitPreservesPitch = true;
1164
+ } else if ('mozPreservesPitch' in combinedAudioSource) {
1165
+ combinedAudioSource.mozPreservesPitch = true;
1166
+ }
1167
  }
1168
 
1169
  speedValue.textContent = speed.toFixed(2) + 'x';
 
1186
  setStartTimeBtn.disabled = false;
1187
  setEndTimeBtn.disabled = false;
1188
  playbackSpeedSlider.disabled = false;
 
 
 
 
 
1189
  }
1190
 
1191
  // プレビュー再生
 
1322
  video.currentTime = startTime;
1323
  }
1324
 
 
1325
  const playPromise = video.play();
1326
 
1327
  if (playPromise !== undefined) {
 
1329
  isPlaying = true;
1330
  playPauseBtn.textContent = '⏸';
1331
 
1332
+ syncAudioWithVideo();
1333
+ startSyncCheck();
 
 
 
 
 
 
 
1334
 
 
1335
  video.playbackRate = currentPlaybackRate;
 
 
 
 
 
 
 
 
 
 
 
1336
  }).catch(error => {
1337
  console.error('動画再生エラー:', error);
1338
  });
 
1348
  video.pause();
1349
  isPlaying = false;
1350
  playPauseBtn.textContent = '▶';
1351
+ stopSyncCheck();
1352
 
1353
  if (combinedAudioSource) {
1354
  combinedAudioSource.stop();
 
1368
  if (loopCheckbox.checked) {
1369
  const startTime = parseFloat(startTimeInput.value) || 0;
1370
  video.currentTime = startTime;
1371
+ syncAudioWithVideo();
 
 
 
 
 
 
 
 
1372
  } else {
1373
  pauseMedia();
1374
  video.currentTime = endTime;
 
1402
  if (combinedAudioSource) {
1403
  combinedAudioSource.stop();
1404
  combinedAudioSource = audioContext.createBufferSource();
1405
+ combinedAudioSource.buffer = combinedAudioBuffer;
1406
  combinedAudioSource.connect(audioContext.destination);
1407
 
1408
  if (isPlaying) {
 
1440
  video.addEventListener('click', function() {
1441
  togglePlayPause();
1442
  });
1443
+
1444
+ volumeSlider.addEventListener('input', function() {
1445
+ if (!isAudioCombined) return;
1446
+
1447
+ lastVolume = parseFloat(this.value);
1448
+ applyVolume();
1449
+ });
1450
 
1451
+ volumeBtn.addEventListener('click', function() {
1452
+ if (!isAudioCombined) return;
1453
+
1454
+ if (video.volume > 0) {
1455
+ lastVolume = parseFloat(volumeSlider.value);
1456
+ volumeSlider.value = 0;
1457
+ } else {
1458
+ volumeSlider.value = lastVolume;
1459
+ }
1460
+
1461
+ applyVolume();
1462
+ });
 
 
 
 
 
 
 
 
 
 
 
1463
 
1464
  // 音量アイコンを更新
1465
  function updateVolumeIcon() {
 
1556
  const value = parseFloat(this.value);
1557
  volumeValues[index].textContent = value.toFixed(2);
1558
 
 
1559
  const percent = value * 100;
1560
  this.style.backgroundSize = `${percent}% 100%`;
1561
  });
1562
  });
1563
 
1564
+ globalVolumeSlider.addEventListener('input', function() {
1565
+ const value = parseFloat(this.value);
1566
+ globalVolumeValue.textContent = value.toFixed(1);
1567
+
1568
+ const percent = (value - this.min) / (this.max - this.min) * 100;
1569
+ this.style.backgroundSize = `${percent}% 100%`;
1570
+
1571
+ applyVolume();
1572
+ });
 
 
1573
 
1574
  // ループ設定変更時
1575
  loopCheckbox.addEventListener('change', function() {
 
1599
  video.controls = false;
1600
 
1601
  // スライダーの背景を初期化
1602
+ function initSliderBackgrounds() {
1603
+ const sliders = [
1604
+ volumeSlider,
1605
+ speedSlider,
1606
+ globalVolumeSlider,
1607
+ playbackSpeedSlider,
1608
+ ...audioSliders
1609
+ ];
1610
+
1611
+ sliders.forEach(slider => {
1612
+ if (slider) {
1613
+ slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)';
1614
+ slider.style.backgroundRepeat = 'no-repeat';
1615
+
1616
+ if (slider === globalVolumeSlider) {
1617
+ const percent = (slider.value - slider.min) / (slider.max - slider.min) * 100;
1618
+ slider.style.backgroundSize = `${percent}% 100%`;
1619
+ globalVolumeValue.textContent = slider.value;
1620
+ } else {
1621
+ slider.style.backgroundSize = `${slider.value * 100}% 100%`;
1622
+ }
 
1623
  }
1624
+ });
1625
+ }
 
1626
 
1627
  initSliderBackgrounds();
1628
+ startSyncCheck(); // 同期チェックを開始
1629
  });
1630
+ </script>