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 |
// 初期化
|