Update index.html
Browse files- index.html +89 -144
index.html
CHANGED
@@ -413,12 +413,8 @@
|
|
413 |
</div>
|
414 |
</div>
|
415 |
</div>
|
416 |
-
|
417 |
-
|
418 |
-
import { SoundTouch, SimpleFilter, PitchShifter } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/soundtouch.min.js';
|
419 |
-
|
420 |
-
// グローバルにsoundtouchを設定
|
421 |
-
window.soundtouch = { SoundTouch, SimpleFilter, PitchShifter };
|
422 |
document.addEventListener('DOMContentLoaded', function() {
|
423 |
// 要素を取得
|
424 |
const video = document.getElementById('video');
|
@@ -444,11 +440,7 @@
|
|
444 |
const volumeValues = document.querySelectorAll('.volume-value');
|
445 |
|
446 |
// 音声オブジェクトを作成
|
447 |
-
const
|
448 |
-
const audioBuffers = {};
|
449 |
-
const audioSources = {};
|
450 |
-
const gainNodes = {};
|
451 |
-
const soundTouchNodes = {};
|
452 |
|
453 |
// 音声ファイル名の配列
|
454 |
const audioFiles = ['p', 'a', 't', 's', 'k'];
|
@@ -468,11 +460,16 @@
|
|
468 |
startTimeInput.max = videoDuration - 0.1;
|
469 |
updateTimeDisplay();
|
470 |
});
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
|
|
|
|
|
|
|
|
|
|
476 |
// 時間表示を更新
|
477 |
function updateTimeDisplay() {
|
478 |
const currentTime = video.currentTime;
|
@@ -539,11 +536,8 @@
|
|
539 |
|
540 |
// 音声も同期
|
541 |
audioFiles.forEach(file => {
|
542 |
-
if (
|
543 |
-
|
544 |
-
if (isPlaying) {
|
545 |
-
playAudio(file, seekTime);
|
546 |
-
}
|
547 |
}
|
548 |
});
|
549 |
}
|
@@ -637,15 +631,15 @@
|
|
637 |
updatePlaybackRate(speed);
|
638 |
});
|
639 |
|
640 |
-
|
641 |
function updatePlaybackRate(speed) {
|
642 |
currentPlaybackRate = speed;
|
643 |
video.playbackRate = speed;
|
644 |
|
645 |
// 音声の再生速度を変更
|
646 |
audioFiles.forEach(file => {
|
647 |
-
if (
|
648 |
-
|
|
|
649 |
}
|
650 |
});
|
651 |
}
|
@@ -677,96 +671,65 @@
|
|
677 |
}
|
678 |
}
|
679 |
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
692 |
}
|
693 |
|
694 |
-
function playAudio(file, startTime) {
|
695 |
-
|
696 |
-
|
697 |
-
// 既存のソースを止める
|
698 |
-
if (audioSources[file]) {
|
699 |
-
try {
|
700 |
-
audioSources[file].stop();
|
701 |
-
audioSources[file].disconnect();
|
702 |
-
} catch (e) {}
|
703 |
-
}
|
704 |
-
|
705 |
-
// 音声ソース
|
706 |
-
const sourceNode = audioContext.createBufferSource();
|
707 |
-
sourceNode.buffer = audioBuffers[file];
|
708 |
-
|
709 |
-
// SoundTouch PitchShifterのAudioNodeを作成
|
710 |
-
const filter = new soundtouch.SimpleFilter(
|
711 |
-
new soundtouch.SoundTouch(audioBuffers[file].sampleRate),
|
712 |
-
new soundtouch.BufferSource(audioBuffers[file])
|
713 |
-
);
|
714 |
-
filter.tempo = currentPlaybackRate;
|
715 |
-
filter.pitch = 1.0;
|
716 |
-
|
717 |
-
const filterNode = soundtouch.getWebAudioNode(audioContext, filter);
|
718 |
-
|
719 |
-
// ボリューム
|
720 |
-
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
|
721 |
-
const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value);
|
722 |
-
gainNodes[file] = audioContext.createGain();
|
723 |
-
gainNodes[file].gain.value = volume;
|
724 |
-
|
725 |
-
// 接続
|
726 |
-
sourceNode.connect(filterNode);
|
727 |
-
filterNode.connect(gainNodes[file]);
|
728 |
-
gainNodes[file].connect(audioContext.destination);
|
729 |
-
|
730 |
-
// 再生時間計算
|
731 |
-
const duration = video.duration || videoDuration;
|
732 |
-
const endTime = parseFloat(endTimeInput.value) || duration;
|
733 |
-
const playStartTime = Math.max(startTime, parseFloat(startTimeInput.value) || 0);
|
734 |
-
const playEndTime = Math.min(endTime, duration);
|
735 |
-
const playDuration = playEndTime - playStartTime;
|
736 |
-
|
737 |
-
if (playDuration > 0) {
|
738 |
-
sourceNode.start(audioContext.currentTime, playStartTime);
|
739 |
-
if (!loopCheckbox.checked) {
|
740 |
-
setTimeout(() => {
|
741 |
-
sourceNode.stop();
|
742 |
-
}, playDuration * 1000);
|
743 |
-
}
|
744 |
-
}
|
745 |
-
audioSources[file] = sourceNode;
|
746 |
-
}
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
function pauseMedia() {
|
751 |
-
if (!isPlaying) return;
|
752 |
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
try {
|
757 |
-
audioSources[file].source.stop();
|
758 |
-
audioSources[file].processor.disconnect();
|
759 |
-
} catch (e) {
|
760 |
-
console.log("Audio source already stopped");
|
761 |
-
}
|
762 |
-
audioSources[file] = null;
|
763 |
-
}
|
764 |
-
});
|
765 |
|
766 |
-
|
767 |
-
|
768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
769 |
}
|
|
|
770 |
// 再生関数
|
771 |
function playMedia() {
|
772 |
const startTime = video.currentTime;
|
@@ -788,9 +751,7 @@ function playAudio(file, startTime) {
|
|
788 |
|
789 |
// 音声を再生
|
790 |
audioFiles.forEach(file => {
|
791 |
-
|
792 |
-
playAudio(file, video.currentTime);
|
793 |
-
}
|
794 |
});
|
795 |
}
|
796 |
|
@@ -800,13 +761,8 @@ function playAudio(file, startTime) {
|
|
800 |
|
801 |
video.pause();
|
802 |
audioFiles.forEach(file => {
|
803 |
-
if (
|
804 |
-
|
805 |
-
audioSources[file].stop();
|
806 |
-
} catch(e) {
|
807 |
-
console.log("Audio source already stopped");
|
808 |
-
}
|
809 |
-
audioSources[file] = null;
|
810 |
}
|
811 |
});
|
812 |
|
@@ -821,9 +777,9 @@ function playAudio(file, startTime) {
|
|
821 |
const value = parseFloat(this.value);
|
822 |
volumeValues[index].textContent = value.toFixed(2);
|
823 |
|
824 |
-
if (
|
825 |
const globalVolume = parseFloat(globalVolumeSlider.value);
|
826 |
-
|
827 |
}
|
828 |
});
|
829 |
});
|
@@ -833,33 +789,22 @@ function playAudio(file, startTime) {
|
|
833 |
const value = parseFloat(this.value);
|
834 |
globalVolumeValue.textContent = value.toFixed(1);
|
835 |
|
836 |
-
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
});
|
844 |
-
}
|
845 |
});
|
846 |
|
847 |
// ループ設定変更時
|
848 |
loopCheckbox.addEventListener('change', function() {
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
854 |
-
try {
|
855 |
-
audioSources[file].stop();
|
856 |
-
} catch(e) {
|
857 |
-
console.log("Audio source already stopped");
|
858 |
-
}
|
859 |
-
playAudio(file, currentTime);
|
860 |
-
}
|
861 |
-
});
|
862 |
-
}
|
863 |
});
|
864 |
|
865 |
// 初期化
|
|
|
413 |
</div>
|
414 |
</div>
|
415 |
</div>
|
416 |
+
|
417 |
+
<script>
|
|
|
|
|
|
|
|
|
418 |
document.addEventListener('DOMContentLoaded', function() {
|
419 |
// 要素を取得
|
420 |
const video = document.getElementById('video');
|
|
|
440 |
const volumeValues = document.querySelectorAll('.volume-value');
|
441 |
|
442 |
// 音声オブジェクトを作成
|
443 |
+
const audioElements = {};
|
|
|
|
|
|
|
|
|
444 |
|
445 |
// 音声ファイル名の配列
|
446 |
const audioFiles = ['p', 'a', 't', 's', 'k'];
|
|
|
460 |
startTimeInput.max = videoDuration - 0.1;
|
461 |
updateTimeDisplay();
|
462 |
});
|
463 |
+
|
464 |
+
document.getElementById('play-pause-btn').addEventListener('click', function() {
|
465 |
+
// 音声要素の再生を許可
|
466 |
+
audioFiles.forEach(file => {
|
467 |
+
if (audioElements[file]) {
|
468 |
+
audioElements[file].play().catch(e => console.log("Audio play failed:", e));
|
469 |
+
}
|
470 |
+
});
|
471 |
+
});
|
472 |
+
|
473 |
// 時間表示を更新
|
474 |
function updateTimeDisplay() {
|
475 |
const currentTime = video.currentTime;
|
|
|
536 |
|
537 |
// 音声も同期
|
538 |
audioFiles.forEach(file => {
|
539 |
+
if (audioElements[file]) {
|
540 |
+
audioElements[file].currentTime = seekTime;
|
|
|
|
|
|
|
541 |
}
|
542 |
});
|
543 |
}
|
|
|
631 |
updatePlaybackRate(speed);
|
632 |
});
|
633 |
|
|
|
634 |
function updatePlaybackRate(speed) {
|
635 |
currentPlaybackRate = speed;
|
636 |
video.playbackRate = speed;
|
637 |
|
638 |
// 音声の再生速度を変更
|
639 |
audioFiles.forEach(file => {
|
640 |
+
if (audioElements[file]) {
|
641 |
+
audioElements[file].playbackRate = speed;
|
642 |
+
audioElements[file].preservesPitch = true; // ピッチを保持
|
643 |
}
|
644 |
});
|
645 |
}
|
|
|
671 |
}
|
672 |
}
|
673 |
|
674 |
+
// 音声ファイルをロード
|
675 |
+
function loadAudioFiles() {
|
676 |
+
audioFiles.forEach(file => {
|
677 |
+
const audio = new Audio(`${file}.mp3`);
|
678 |
+
audio.preload = 'auto';
|
679 |
+
audio.preservesPitch = true; // ピッチを保持
|
680 |
+
audio.loop = false;
|
681 |
+
audioElements[file] = audio;
|
682 |
+
|
683 |
+
// 音声が読み込まれたら
|
684 |
+
audio.addEventListener('loadedmetadata', function() {
|
685 |
+
console.log(`${file}.mp3 loaded`);
|
686 |
+
});
|
687 |
+
|
688 |
+
// エラー処理
|
689 |
+
audio.addEventListener('error', function() {
|
690 |
+
console.error(`Error loading ${file}.mp3`);
|
691 |
+
});
|
692 |
+
});
|
693 |
}
|
694 |
|
695 |
+
function playAudio(file, startTime) {
|
696 |
+
if (!audioElements[file]) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
697 |
|
698 |
+
const audio = audioElements[file];
|
699 |
+
const duration = video.duration || videoDuration;
|
700 |
+
const endTime = parseFloat(endTimeInput.value) || duration;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
701 |
|
702 |
+
// ボリューム設定
|
703 |
+
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
|
704 |
+
const volume = parseFloat(volumeSlider.value) * parseFloat(globalVolumeSlider.value);
|
705 |
+
audio.volume = volume;
|
706 |
+
|
707 |
+
// 再生速度設定
|
708 |
+
audio.playbackRate = currentPlaybackRate;
|
709 |
+
audio.preservesPitch = true;
|
710 |
+
|
711 |
+
// 再生時間計算
|
712 |
+
const playStartTime = Math.max(startTime, parseFloat(startTimeInput.value) || 0);
|
713 |
+
const playEndTime = Math.min(endTime, duration);
|
714 |
+
|
715 |
+
audio.currentTime = playStartTime;
|
716 |
+
|
717 |
+
// 再生
|
718 |
+
audio.play().catch(e => console.log("Audio play failed:", e));
|
719 |
+
|
720 |
+
// ループ設定
|
721 |
+
audio.loop = loopCheckbox.checked;
|
722 |
+
|
723 |
+
if (!loopCheckbox.checked) {
|
724 |
+
// ループ再生でない場合、終了時間に達したら停止
|
725 |
+
setTimeout(() => {
|
726 |
+
if (audio.currentTime >= playEndTime) {
|
727 |
+
audio.pause();
|
728 |
+
}
|
729 |
+
}, (playEndTime - playStartTime) * 1000);
|
730 |
+
}
|
731 |
}
|
732 |
+
|
733 |
// 再生関数
|
734 |
function playMedia() {
|
735 |
const startTime = video.currentTime;
|
|
|
751 |
|
752 |
// 音声を再生
|
753 |
audioFiles.forEach(file => {
|
754 |
+
playAudio(file, video.currentTime);
|
|
|
|
|
755 |
});
|
756 |
}
|
757 |
|
|
|
761 |
|
762 |
video.pause();
|
763 |
audioFiles.forEach(file => {
|
764 |
+
if (audioElements[file]) {
|
765 |
+
audioElements[file].pause();
|
|
|
|
|
|
|
|
|
|
|
766 |
}
|
767 |
});
|
768 |
|
|
|
777 |
const value = parseFloat(this.value);
|
778 |
volumeValues[index].textContent = value.toFixed(2);
|
779 |
|
780 |
+
if (audioElements[this.dataset.audio]) {
|
781 |
const globalVolume = parseFloat(globalVolumeSlider.value);
|
782 |
+
audioElements[this.dataset.audio].volume = value * globalVolume;
|
783 |
}
|
784 |
});
|
785 |
});
|
|
|
789 |
const value = parseFloat(this.value);
|
790 |
globalVolumeValue.textContent = value.toFixed(1);
|
791 |
|
792 |
+
audioFiles.forEach(file => {
|
793 |
+
if (audioElements[file]) {
|
794 |
+
const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
|
795 |
+
const volume = parseFloat(volumeSlider.value) * value;
|
796 |
+
audioElements[file].volume = volume;
|
797 |
+
}
|
798 |
+
});
|
|
|
|
|
799 |
});
|
800 |
|
801 |
// ループ設定変更時
|
802 |
loopCheckbox.addEventListener('change', function() {
|
803 |
+
audioFiles.forEach(file => {
|
804 |
+
if (audioElements[file]) {
|
805 |
+
audioElements[file].loop = this.checked;
|
806 |
+
}
|
807 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
808 |
});
|
809 |
|
810 |
// 初期化
|