Update index.html
Browse files- index.html +19 -8
index.html
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>高度な音声動画プレイヤー</title>
|
|
|
7 |
<style>
|
8 |
body {
|
9 |
font-family: 'Arial', sans-serif;
|
@@ -444,6 +445,7 @@
|
|
444 |
const audioBuffers = {};
|
445 |
const audioSources = {};
|
446 |
const gainNodes = {};
|
|
|
447 |
|
448 |
// 音声ファイル名の配列
|
449 |
const audioFiles = ['p', 'a', 't', 's', 'k'];
|
@@ -453,6 +455,7 @@
|
|
453 |
let isPlaying = false;
|
454 |
let isVideoPlaying = false;
|
455 |
let lastVolume = 1;
|
|
|
456 |
|
457 |
// 動画のメタデータが読み込まれたら
|
458 |
video.addEventListener('loadedmetadata', function() {
|
@@ -627,15 +630,15 @@
|
|
627 |
updatePlaybackRate(speed);
|
628 |
});
|
629 |
|
630 |
-
// 再生速度を更新 (
|
631 |
function updatePlaybackRate(speed) {
|
|
|
632 |
video.playbackRate = speed;
|
633 |
|
634 |
-
//
|
635 |
audioFiles.forEach(file => {
|
636 |
-
if (
|
637 |
-
|
638 |
-
audioSources[file].playbackRate.value = speed;
|
639 |
}
|
640 |
});
|
641 |
}
|
@@ -677,6 +680,11 @@
|
|
677 |
audioBuffers[file] = audioBuffer;
|
678 |
gainNodes[file] = audioContext.createGain();
|
679 |
gainNodes[file].gain.value = 1;
|
|
|
|
|
|
|
|
|
|
|
680 |
})
|
681 |
.catch(error => console.error(`Error loading ${file}.mp3:`, error));
|
682 |
});
|
@@ -695,9 +703,12 @@
|
|
695 |
}
|
696 |
}
|
697 |
|
|
|
698 |
const source = audioContext.createBufferSource();
|
699 |
source.buffer = audioBuffers[file];
|
700 |
|
|
|
|
|
701 |
// ボリュームスライダーの値を取得
|
702 |
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
|
703 |
const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value);
|
@@ -706,7 +717,7 @@
|
|
706 |
gainNodes[file].gain.value = volume;
|
707 |
|
708 |
// 接続
|
709 |
-
|
710 |
gainNodes[file].connect(audioContext.destination);
|
711 |
|
712 |
// 再生
|
@@ -732,7 +743,7 @@
|
|
732 |
}
|
733 |
|
734 |
// 再生速度を同期
|
735 |
-
|
736 |
|
737 |
audioSources[file] = source;
|
738 |
}
|
@@ -791,7 +802,7 @@
|
|
791 |
const value = parseFloat(this.value);
|
792 |
volumeValues[index].textContent = value.toFixed(2);
|
793 |
|
794 |
-
if (isPlaying &&
|
795 |
const globalVolume = parseFloat(globalVolumeSlider.value);
|
796 |
gainNodes[this.dataset.audio].gain.value = value * globalVolume;
|
797 |
}
|
|
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>高度な音声動画プレイヤー</title>
|
7 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/soundtouch.min.js"></script>
|
8 |
<style>
|
9 |
body {
|
10 |
font-family: 'Arial', sans-serif;
|
|
|
445 |
const audioBuffers = {};
|
446 |
const audioSources = {};
|
447 |
const gainNodes = {};
|
448 |
+
const soundTouchNodes = {};
|
449 |
|
450 |
// 音声ファイル名の配列
|
451 |
const audioFiles = ['p', 'a', 't', 's', 'k'];
|
|
|
455 |
let isPlaying = false;
|
456 |
let isVideoPlaying = false;
|
457 |
let lastVolume = 1;
|
458 |
+
let currentPlaybackRate = 1;
|
459 |
|
460 |
// 動画のメタデータが読み込まれたら
|
461 |
video.addEventListener('loadedmetadata', function() {
|
|
|
630 |
updatePlaybackRate(speed);
|
631 |
});
|
632 |
|
633 |
+
// 再生速度を更新 (音の高さを保持)
|
634 |
function updatePlaybackRate(speed) {
|
635 |
+
currentPlaybackRate = speed;
|
636 |
video.playbackRate = speed;
|
637 |
|
638 |
+
// 音声の再生速度を変更 (SoundTouchJSが音の高さを保持)
|
639 |
audioFiles.forEach(file => {
|
640 |
+
if (soundTouchNodes[file]) {
|
641 |
+
soundTouchNodes[file].tempo = speed;
|
|
|
642 |
}
|
643 |
});
|
644 |
}
|
|
|
680 |
audioBuffers[file] = audioBuffer;
|
681 |
gainNodes[file] = audioContext.createGain();
|
682 |
gainNodes[file].gain.value = 1;
|
683 |
+
|
684 |
+
// SoundTouchノードを作成
|
685 |
+
soundTouchNodes[file] = new soundtouch.SoundTouch(audioBuffer.sampleRate);
|
686 |
+
soundTouchNodes[file].tempo = currentPlaybackRate;
|
687 |
+
soundTouchNodes[file].pitch = 1; // ピッチ変更なし
|
688 |
})
|
689 |
.catch(error => console.error(`Error loading ${file}.mp3:`, error));
|
690 |
});
|
|
|
703 |
}
|
704 |
}
|
705 |
|
706 |
+
// SoundTouchプロセッサーを作成
|
707 |
const source = audioContext.createBufferSource();
|
708 |
source.buffer = audioBuffers[file];
|
709 |
|
710 |
+
const soundTouchProcessor = new soundtouch.SimpleFilter(audioContext, source, soundTouchNodes[file]);
|
711 |
+
|
712 |
// ボリュームスライダーの値を取得
|
713 |
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
|
714 |
const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value);
|
|
|
717 |
gainNodes[file].gain.value = volume;
|
718 |
|
719 |
// 接続
|
720 |
+
soundTouchProcessor.connect(gainNodes[file]);
|
721 |
gainNodes[file].connect(audioContext.destination);
|
722 |
|
723 |
// 再生
|
|
|
743 |
}
|
744 |
|
745 |
// 再生速度を同期
|
746 |
+
soundTouchNodes[file].tempo = currentPlaybackRate;
|
747 |
|
748 |
audioSources[file] = source;
|
749 |
}
|
|
|
802 |
const value = parseFloat(this.value);
|
803 |
volumeValues[index].textContent = value.toFixed(2);
|
804 |
|
805 |
+
if (isPlaying && gainNodes[this.dataset.audio]) {
|
806 |
const globalVolume = parseFloat(globalVolumeSlider.value);
|
807 |
gainNodes[this.dataset.audio].gain.value = value * globalVolume;
|
808 |
}
|