Update index.html
Browse files- index.html +717 -660
    	
        index.html
    CHANGED
    
    | @@ -735,704 +735,771 @@ | |
| 735 | 
             
                </div>
         | 
| 736 |  | 
| 737 | 
             
            <script>
         | 
| 738 | 
            -
             | 
| 739 | 
            -
             | 
| 740 | 
            -
             | 
| 741 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
| 742 |  | 
| 743 | 
            -
                         | 
| 744 | 
            -
                         | 
| 745 | 
            -
                             | 
| 746 | 
            -
                            line. | 
| 747 | 
            -
             | 
| 748 | 
            -
                             | 
| 749 | 
            -
                             | 
| 750 | 
            -
                                line.style.width = `${Math.random() * 300 + 100}px`;
         | 
| 751 | 
            -
                                line.style.height = '1px';
         | 
| 752 | 
            -
                            } else {
         | 
| 753 | 
            -
                                line.style.width = '1px';
         | 
| 754 | 
            -
                                line.style.height = `${Math.random() * 300 + 100}px`;
         | 
| 755 | 
            -
                            }
         | 
| 756 | 
            -
                            
         | 
| 757 | 
            -
                            line.style.left = `${Math.random() * 100}%`;
         | 
| 758 | 
            -
                            line.style.top = `${Math.random() * 100}%`;
         | 
| 759 | 
            -
                            line.style.opacity = Math.random() * 0.5 + 0.1;
         | 
| 760 | 
            -
                            
         | 
| 761 | 
            -
                            bg.appendChild(line);
         | 
| 762 | 
            -
                        }
         | 
| 763 | 
            -
                        
         | 
| 764 | 
            -
                        // グリッドドットを生成
         | 
| 765 | 
            -
                        for (let i = 0; i < 200; i++) {
         | 
| 766 | 
            -
                            const dot = document.createElement('div');
         | 
| 767 | 
            -
                            dot.className = 'grid-dot';
         | 
| 768 | 
            -
                            dot.style.left = `${Math.random() * 100}%`;
         | 
| 769 | 
            -
                            dot.style.top = `${Math.random() * 100}%`;
         | 
| 770 | 
            -
                            bg.appendChild(dot);
         | 
| 771 | 
            -
                        }
         | 
| 772 | 
            -
                        
         | 
| 773 | 
            -
                        // 六角形を生成
         | 
| 774 | 
            -
                        for (let i = 0; i < 15; i++) {
         | 
| 775 | 
            -
                            const hex = document.createElement('div');
         | 
| 776 | 
            -
                            hex.className = 'hexagon';
         | 
| 777 | 
            -
                            hex.style.left = `${Math.random() * 100}%`;
         | 
| 778 | 
            -
                            hex.style.top = `${Math.random() * 100}%`;
         | 
| 779 | 
            -
                            hex.style.transform = `rotate(${Math.random() * 360}deg)`;
         | 
| 780 | 
            -
                            hex.style.animation = `float ${Math.random() * 10 + 5}s infinite ease-in-out`;
         | 
| 781 | 
            -
                            bg.appendChild(hex);
         | 
| 782 | 
             
                        }
         | 
| 783 |  | 
| 784 | 
            -
                         | 
| 785 | 
            -
                         | 
| 786 | 
            -
             | 
| 787 | 
            -
             | 
| 788 | 
            -
                            pulse.style.left = `${Math.random() * 100}%`;
         | 
| 789 | 
            -
                            pulse.style.top = `${Math.random() * 100}%`;
         | 
| 790 | 
            -
                            pulse.style.animationDelay = `${Math.random() * 2}s`;
         | 
| 791 | 
            -
                            bg.appendChild(pulse);
         | 
| 792 | 
            -
                        }
         | 
| 793 | 
             
                    }
         | 
| 794 | 
            -
             | 
| 795 | 
            -
                     | 
| 796 | 
            -
             | 
| 797 | 
            -
             | 
| 798 | 
            -
             | 
| 799 | 
            -
             | 
| 800 | 
            -
             | 
| 801 | 
            -
                    const updateInterval = 1; // 50msごとに更新 (20fps)
         | 
| 802 | 
            -
                    
         | 
| 803 | 
            -
                    // ローディング完了チェック
         | 
| 804 | 
            -
                    function checkLoadingComplete() {
         | 
| 805 | 
            -
                        loadingCount++;
         | 
| 806 | 
            -
                        if (loadingCount >= totalToLoad) {
         | 
| 807 | 
            -
                            setTimeout(function() {
         | 
| 808 | 
            -
                                const loadingOverlay = document.getElementById('loadingOverlay');
         | 
| 809 | 
            -
                                loadingOverlay.style.opacity = '0';
         | 
| 810 | 
            -
                                setTimeout(function() {
         | 
| 811 | 
            -
                                    loadingOverlay.style.display = 'none';
         | 
| 812 | 
            -
                                }, 1000);
         | 
| 813 | 
            -
                            }, 500);
         | 
| 814 | 
            -
                        }
         | 
| 815 | 
             
                    }
         | 
| 816 | 
            -
             | 
| 817 | 
            -
                     | 
| 818 | 
            -
             | 
| 819 | 
            -
                         | 
| 820 | 
            -
                         | 
|  | |
|  | |
|  | |
|  | |
| 821 | 
             
                    }
         | 
| 822 | 
            -
             | 
| 823 | 
            -
                     | 
| 824 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 825 | 
             
                        try {
         | 
| 826 | 
            -
                             | 
| 827 | 
            -
             | 
| 828 | 
            -
                             | 
| 829 | 
            -
             | 
| 830 | 
            -
                            } else if (el.mozPreservesPitch !== undefined) {
         | 
| 831 | 
            -
                                el.mozPreservesPitch = value;
         | 
| 832 | 
            -
                            }
         | 
| 833 |  | 
| 834 | 
            -
                             | 
| 835 | 
            -
             | 
| 836 | 
            -
                                 | 
| 837 | 
            -
             | 
| 838 | 
            -
             | 
| 839 | 
            -
             | 
| 840 | 
            -
             | 
|  | |
|  | |
| 841 | 
             
                        } catch (error) {
         | 
| 842 | 
            -
                             | 
| 843 | 
            -
                        }
         | 
| 844 | 
            -
                    }
         | 
| 845 | 
            -
                    
         | 
| 846 | 
            -
                    // 要素を取得
         | 
| 847 | 
            -
                    const video = document.getElementById('video');
         | 
| 848 | 
            -
                    const videoContainer = document.getElementById('video-container');
         | 
| 849 | 
            -
                    const playPauseBtn = document.getElementById('play-pause-btn');
         | 
| 850 | 
            -
                    const timeDisplay = document.getElementById('time-display');
         | 
| 851 | 
            -
                    const progressContainer = document.getElementById('progress-container');
         | 
| 852 | 
            -
                    const progressBar = document.getElementById('progress-bar');
         | 
| 853 | 
            -
                    const progressTime = document.getElementById('progress-time');
         | 
| 854 | 
            -
                    const volumeBtn = document.getElementById('volume-btn');
         | 
| 855 | 
            -
                    const volumeSlider = document.getElementById('volume-slider');
         | 
| 856 | 
            -
                    const speedSlider = document.getElementById('speed-slider');
         | 
| 857 | 
            -
                    const speedValue = document.getElementById('speed-value');
         | 
| 858 | 
            -
                    const playbackSpeedSlider = document.getElementById('playback-speed');
         | 
| 859 | 
            -
                    const playbackSpeedValue = document.getElementById('playback-speed-value');
         | 
| 860 | 
            -
                    const fullscreenBtn = document.getElementById('fullscreen-btn');
         | 
| 861 | 
            -
                    const startTimeInput = document.getElementById('start-time');
         | 
| 862 | 
            -
                    const endTimeInput = document.getElementById('end-time');
         | 
| 863 | 
            -
                    const loopCheckbox = document.getElementById('loop');
         | 
| 864 | 
            -
                    const globalVolumeSlider = document.getElementById('global-volume');
         | 
| 865 | 
            -
                    const globalVolumeValue = document.getElementById('global-volume-value');
         | 
| 866 | 
            -
                    const audioSliders = document.querySelectorAll('.audio-slider');
         | 
| 867 | 
            -
                    const volumeValues = document.querySelectorAll('.volume-value');
         | 
| 868 | 
            -
                    const setStartTimeBtn = document.getElementById('set-start-time');
         | 
| 869 | 
            -
                    const setEndTimeBtn = document.getElementById('set-end-time');
         | 
| 870 | 
            -
                    
         | 
| 871 | 
            -
                    // 音声オブジェクトを作成
         | 
| 872 | 
            -
                    const audioElements = {};
         | 
| 873 | 
            -
                    
         | 
| 874 | 
            -
                    // 音声ファイル名の配列
         | 
| 875 | 
            -
                    const audioFiles = ['p', 'a', 't', 's', 'k'];
         | 
| 876 | 
            -
                    
         | 
| 877 | 
            -
                    // 初期化
         | 
| 878 | 
            -
                    let videoDuration = 0;
         | 
| 879 | 
            -
                    let isPlaying = false;
         | 
| 880 | 
            -
                    let lastVolume = 1;
         | 
| 881 | 
            -
                    let currentPlaybackRate = 1;
         | 
| 882 | 
            -
                    let isFullscreen = false;
         | 
| 883 | 
            -
                    
         | 
| 884 | 
            -
                    // 動画のメタデータが読み込まれたら
         | 
| 885 | 
            -
                    video.addEventListener('loadedmetadata', function() {
         | 
| 886 | 
            -
                        try {
         | 
| 887 | 
            -
                            videoDuration = video.duration;
         | 
| 888 | 
            -
                            endTimeInput.value = videoDuration.toFixed(2);
         | 
| 889 | 
            -
                            endTimeInput.max = videoDuration;
         | 
| 890 | 
            -
                            startTimeInput.max = videoDuration - 0.1;
         | 
| 891 | 
            -
                            updateTimeDisplay();
         | 
| 892 | 
             
                            checkLoadingComplete();
         | 
| 893 | 
            -
                        } catch (error) {
         | 
| 894 | 
            -
                            handleError(error, '動画メタデータ読み込み中にエラーが発生しました');
         | 
| 895 | 
             
                        }
         | 
| 896 | 
             
                    });
         | 
| 897 | 
            -
             | 
| 898 | 
            -
             | 
| 899 | 
            -
             | 
| 900 | 
            -
             | 
| 901 | 
            -
                     | 
| 902 | 
            -
             | 
| 903 | 
            -
             | 
| 904 | 
            -
                     | 
| 905 | 
            -
             | 
| 906 | 
            -
             | 
| 907 | 
            -
             | 
| 908 | 
            -
             | 
| 909 | 
            -
             | 
| 910 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 911 | 
             
                            }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 912 |  | 
| 913 | 
            -
                             | 
| 914 | 
            -
             | 
| 915 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 916 | 
             
                        }
         | 
| 917 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 918 |  | 
| 919 | 
            -
                     | 
| 920 | 
            -
                     | 
| 921 | 
            -
             | 
| 922 | 
            -
             | 
| 923 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 924 |  | 
| 925 | 
            -
                         | 
| 926 | 
            -
             | 
| 927 | 
            -
                             | 
| 928 |  | 
| 929 | 
            -
                            const  | 
| 930 | 
            -
                            const  | 
| 931 | 
            -
                            const currentMilliseconds = Math.floor((currentTime % 1) * 100);
         | 
| 932 | 
            -
                            const durationMinutes = Math.floor(duration / 60);
         | 
| 933 | 
            -
                            const durationSeconds = Math.floor(duration % 60);
         | 
| 934 | 
            -
                            const durationMilliseconds = Math.floor((duration % 1) * 100);
         | 
| 935 |  | 
| 936 | 
            -
                             | 
| 937 | 
            -
                                 | 
| 938 | 
            -
                                 | 
|  | |
|  | |
| 939 |  | 
| 940 | 
            -
                             | 
| 941 | 
            -
                             | 
| 942 | 
            -
             | 
| 943 | 
            -
                         | 
| 944 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 945 | 
             
                        }
         | 
|  | |
| 946 | 
             
                    }
         | 
| 947 | 
            -
             | 
| 948 | 
            -
             | 
| 949 | 
            -
             | 
| 950 | 
            -
             | 
| 951 | 
            -
             | 
| 952 | 
            -
             | 
| 953 | 
            -
             | 
| 954 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 955 | 
             
                    }
         | 
| 956 | 
            -
             | 
| 957 | 
            -
             | 
| 958 | 
            -
             | 
| 959 | 
            -
             | 
| 960 | 
            -
             | 
| 961 | 
            -
             | 
| 962 | 
            -
             | 
| 963 | 
            -
             | 
| 964 | 
            -
             | 
| 965 | 
            -
             | 
| 966 | 
            -
             | 
| 967 | 
            -
             | 
| 968 | 
            -
             | 
| 969 | 
            -
             | 
| 970 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 971 | 
             
                                isPlaying = true;
         | 
| 972 | 
             
                                playPauseBtn.textContent = '⏸';
         | 
| 973 |  | 
| 974 | 
            -
                                //  | 
| 975 | 
            -
                                 | 
| 976 | 
            -
                                     | 
| 977 | 
            -
             | 
| 978 | 
            -
             | 
| 979 | 
            -
             | 
| 980 | 
            -
             | 
| 981 | 
            -
             | 
| 982 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 983 | 
             
                                    }
         | 
| 984 | 
            -
                                } | 
| 985 | 
             
                            }).catch(error => {
         | 
| 986 | 
             
                                console.error('動画再生エラー:', error);
         | 
| 987 | 
             
                            });
         | 
| 988 | 
            -
                        } catch (error) {
         | 
| 989 | 
            -
                            console.error('メディア再生エラー:', error);
         | 
| 990 | 
             
                        }
         | 
|  | |
|  | |
| 991 | 
             
                    }
         | 
| 992 | 
            -
             | 
| 993 | 
            -
             | 
| 994 | 
            -
             | 
| 995 | 
            -
             | 
| 996 | 
            -
             | 
| 997 | 
            -
             | 
| 998 | 
            -
             | 
| 999 | 
            -
             | 
| 1000 | 
            -
                            // 音声を一時停止
         | 
| 1001 | 
            -
                            audioFiles.forEach(file => {
         | 
| 1002 | 
            -
                                if (audioElements[file]) {
         | 
| 1003 | 
            -
                                    audioElements[file].pause();
         | 
| 1004 | 
            -
                                }
         | 
| 1005 | 
            -
                            });
         | 
| 1006 | 
            -
                        } catch (error) {
         | 
| 1007 | 
            -
                            console.error('メディア一時停止エラー:', error);
         | 
| 1008 | 
            -
                        }
         | 
| 1009 | 
            -
                    }
         | 
| 1010 | 
            -
                    
         | 
| 1011 | 
            -
                    // 時間更新時の処理
         | 
| 1012 | 
            -
                    video.addEventListener('timeupdate', function() {
         | 
| 1013 | 
            -
                        const duration = video.duration || videoDuration;
         | 
| 1014 | 
            -
                        const endTime = parseFloat(endTimeInput.value) || duration;
         | 
| 1015 | 
            -
                        
         | 
| 1016 | 
            -
                        // 終了時間に達した場合の処理
         | 
| 1017 | 
            -
                        if (video.currentTime >= endTime && endTime > 0) {
         | 
| 1018 | 
            -
                            if (loopCheckbox.checked) {
         | 
| 1019 | 
            -
                                const startTime = parseFloat(startTimeInput.value) || 0;
         | 
| 1020 | 
            -
                                video.currentTime = startTime;
         | 
| 1021 | 
            -
                                
         | 
| 1022 | 
            -
                                // 音声も同期
         | 
| 1023 | 
            -
                                audioFiles.forEach(file => {
         | 
| 1024 | 
            -
                                    if (audioElements[file]) {
         | 
| 1025 | 
            -
                                        audioElements[file].currentTime = startTime;
         | 
| 1026 | 
            -
                                        if (isPlaying) {
         | 
| 1027 | 
            -
                                            audioElements[file].play().catch(e => {
         | 
| 1028 | 
            -
                                                if (e.name !== 'AbortError') {
         | 
| 1029 | 
            -
                                                    console.error(`音声再生エラー (${file}.mp3):`, e);
         | 
| 1030 | 
            -
                                                }
         | 
| 1031 | 
            -
                                            });
         | 
| 1032 | 
            -
                                        }
         | 
| 1033 | 
            -
                                    }
         | 
| 1034 | 
            -
                                });
         | 
| 1035 | 
            -
                            } else {
         | 
| 1036 | 
            -
                                pauseMedia();
         | 
| 1037 | 
            -
                                video.currentTime = endTime;
         | 
| 1038 | 
            -
                            }
         | 
| 1039 | 
            -
                        }
         | 
| 1040 |  | 
| 1041 | 
            -
                         | 
| 1042 | 
            -
             | 
| 1043 | 
            -
             | 
| 1044 | 
            -
                    // プログレスバークリックでシーク
         | 
| 1045 | 
            -
                    progressContainer.addEventListener('click', function(e) {
         | 
| 1046 | 
            -
                        try {
         | 
| 1047 | 
            -
                            if (!video.duration) return;
         | 
| 1048 | 
            -
                            
         | 
| 1049 | 
            -
                            const rect = this.getBoundingClientRect();
         | 
| 1050 | 
            -
                            const pos = (e.clientX - rect.left) / rect.width;
         | 
| 1051 | 
            -
                            const seekTime = pos * video.duration;
         | 
| 1052 | 
            -
                            
         | 
| 1053 | 
            -
                            seekMedia(seekTime);
         | 
| 1054 | 
            -
                        } catch (error) {
         | 
| 1055 | 
            -
                            handleError(error, 'シーク操作中にエラーが発生しました');
         | 
| 1056 | 
            -
                        }
         | 
| 1057 | 
            -
                    });
         | 
| 1058 | 
            -
                    
         | 
| 1059 | 
            -
                    // 指定した時間にシーク
         | 
| 1060 | 
            -
                    function seekMedia(time) {
         | 
| 1061 | 
            -
                        try {
         | 
| 1062 | 
            -
                            const duration = video.duration || videoDuration;
         | 
| 1063 | 
            -
                            const startTime = parseFloat(startTimeInput.value) || 0;
         | 
| 1064 | 
            -
                            const endTime = parseFloat(endTimeInput.value) || duration;
         | 
| 1065 | 
            -
                            
         | 
| 1066 | 
            -
                            // 範囲内に制限
         | 
| 1067 | 
            -
                            const seekTime = Math.max(startTime, Math.min(time, endTime));
         | 
| 1068 | 
            -
                            
         | 
| 1069 | 
            -
                            video.currentTime = seekTime;
         | 
| 1070 | 
            -
                            
         | 
| 1071 | 
            -
                            // 音声も同期
         | 
| 1072 | 
            -
                            audioFiles.forEach(file => {
         | 
| 1073 | 
            -
                                if (audioElements[file]) {
         | 
| 1074 | 
            -
                                    audioElements[file].currentTime = seekTime;
         | 
| 1075 | 
            -
                                }
         | 
| 1076 | 
            -
                            });
         | 
| 1077 | 
            -
                        } catch (error) {
         | 
| 1078 | 
            -
                            handleError(error, 'メディアシーク中にエラーが発生しました');
         | 
| 1079 | 
             
                        }
         | 
|  | |
|  | |
| 1080 | 
             
                    }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1081 |  | 
| 1082 | 
            -
                     | 
| 1083 | 
            -
             | 
| 1084 | 
            -
             | 
| 1085 | 
            -
                             | 
| 1086 | 
            -
                            
         | 
| 1087 | 
            -
                            const rect = this.getBoundingClientRect();
         | 
| 1088 | 
            -
                            const pos = (e.clientX - rect.left) / rect.width;
         | 
| 1089 | 
            -
                            const hoverTime = pos * video.duration;
         | 
| 1090 | 
            -
                            
         | 
| 1091 | 
            -
                            const minutes = Math.floor(hoverTime / 60);
         | 
| 1092 | 
            -
                            const seconds = Math.floor(hoverTime % 60);
         | 
| 1093 | 
            -
                            const milliseconds = Math.floor((hoverTime % 1) * 100);
         | 
| 1094 |  | 
| 1095 | 
            -
                             | 
| 1096 | 
            -
             | 
| 1097 | 
            -
             | 
| 1098 | 
            -
             | 
| 1099 | 
            -
             | 
| 1100 | 
            -
             | 
| 1101 | 
            -
             | 
| 1102 | 
            -
                    
         | 
| 1103 | 
            -
                    progressContainer.addEventListener('mouseleave', function() {
         | 
| 1104 | 
            -
                        progressTime.style.display = 'none';
         | 
| 1105 | 
            -
                    });
         | 
| 1106 | 
            -
                    
         | 
| 1107 | 
            -
                    // 動画クリックで再生/一時停止
         | 
| 1108 | 
            -
                    video.addEventListener('click', function() {
         | 
| 1109 | 
            -
                        togglePlayPause();
         | 
| 1110 | 
            -
                    });
         | 
| 1111 | 
            -
                    
         | 
| 1112 | 
            -
                    // 音量コントロール
         | 
| 1113 | 
            -
                    volumeSlider.addEventListener('input', function() {
         | 
| 1114 | 
            -
                        try {
         | 
| 1115 | 
            -
                            video.volume = this.value;
         | 
| 1116 | 
            -
                            lastVolume = this.value;
         | 
| 1117 | 
            -
                            updateVolumeIcon();
         | 
| 1118 | 
            -
                        } catch (error) {
         | 
| 1119 | 
            -
                            handleError(error, '音量設定中にエラーが発生しました');
         | 
| 1120 | 
            -
                        }
         | 
| 1121 | 
            -
                    });
         | 
| 1122 | 
            -
                    
         | 
| 1123 | 
            -
                    // 音量ボタン
         | 
| 1124 | 
            -
                    volumeBtn.addEventListener('click', function() {
         | 
| 1125 | 
            -
                        try {
         | 
| 1126 | 
            -
                            if (video.volume > 0) {
         | 
| 1127 | 
            -
                                lastVolume = video.volume;
         | 
| 1128 | 
            -
                                video.volume = 0;
         | 
| 1129 | 
            -
                                volumeSlider.value = 0;
         | 
| 1130 | 
            -
                            } else {
         | 
| 1131 | 
            -
                                video.volume = lastVolume;
         | 
| 1132 | 
            -
                                volumeSlider.value = lastVolume;
         | 
| 1133 | 
             
                            }
         | 
| 1134 | 
            -
                            updateVolumeIcon();
         | 
| 1135 | 
            -
                        } catch (error) {
         | 
| 1136 | 
            -
                            handleError(error, '音量切り替え中にエラーが発生しました');
         | 
| 1137 | 
            -
                        }
         | 
| 1138 | 
            -
                    });
         | 
| 1139 | 
            -
                    
         | 
| 1140 | 
            -
                    // 音量アイコンを更新
         | 
| 1141 | 
            -
                    function updateVolumeIcon() {
         | 
| 1142 | 
            -
                        if (video.volume === 0) {
         | 
| 1143 | 
            -
                            volumeBtn.textContent = '🔇';
         | 
| 1144 | 
            -
                        } else if (video.volume < 0.5) {
         | 
| 1145 | 
            -
                            volumeBtn.textContent = '🔈';
         | 
| 1146 | 
             
                        } else {
         | 
| 1147 | 
            -
                             | 
|  | |
| 1148 | 
             
                        }
         | 
| 1149 | 
             
                    }
         | 
| 1150 |  | 
| 1151 | 
            -
                     | 
| 1152 | 
            -
             | 
| 1153 | 
            -
             | 
| 1154 | 
            -
             | 
| 1155 | 
            -
             | 
| 1156 | 
            -
             | 
| 1157 | 
            -
                            playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1158 | 
            -
                            updatePlaybackRate(speed);
         | 
| 1159 | 
            -
                        } catch (error) {
         | 
| 1160 | 
            -
                            handleError(error, '再生速度設定中にエラーが発生しました');
         | 
| 1161 | 
            -
                        }
         | 
| 1162 | 
            -
                    });
         | 
| 1163 |  | 
| 1164 | 
            -
                     | 
| 1165 | 
            -
                     | 
| 1166 | 
            -
             | 
| 1167 | 
            -
                            const speed = parseFloat(this.value);
         | 
| 1168 | 
            -
                            playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1169 | 
            -
                            speedSlider.value = speed;
         | 
| 1170 | 
            -
                            speedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1171 | 
            -
                            updatePlaybackRate(speed);
         | 
| 1172 | 
            -
                        } catch (error) {
         | 
| 1173 | 
            -
                            handleError(error, '再生速度設定中にエラーが発生しました');
         | 
| 1174 | 
            -
                        }
         | 
| 1175 | 
            -
                    });
         | 
| 1176 |  | 
| 1177 | 
            -
                     | 
| 1178 | 
            -
             | 
| 1179 | 
            -
             | 
| 1180 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1181 |  | 
| 1182 | 
            -
                             | 
| 1183 | 
            -
             | 
| 1184 | 
            -
                                 | 
| 1185 | 
            -
             | 
| 1186 | 
            -
                                    setPreservesPitch(audioElements[file], true);
         | 
| 1187 | 
            -
                                    audioElements[file].playbackRate = speed;
         | 
| 1188 | 
            -
                                    
         | 
| 1189 | 
            -
                                    // 再生速度変更後に再度ピッチ保持を確認
         | 
| 1190 | 
            -
                                    setTimeout(() => {
         | 
| 1191 | 
            -
                                        setPreservesPitch(audioElements[file], true);
         | 
| 1192 | 
            -
                                    }, 100);
         | 
| 1193 | 
            -
                                }
         | 
| 1194 | 
            -
                            });
         | 
| 1195 | 
            -
                        } catch (error) {
         | 
| 1196 | 
            -
                            handleError(error, '再生速度更新中にエラーが発生しました');
         | 
| 1197 | 
             
                        }
         | 
|  | |
|  | |
| 1198 | 
             
                    }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 1199 |  | 
| 1200 | 
            -
                     | 
| 1201 | 
            -
                     | 
| 1202 | 
            -
             | 
| 1203 | 
            -
                            if (!isFullscreen) {
         | 
| 1204 | 
            -
                                if (videoContainer.requestFullscreen) {
         | 
| 1205 | 
            -
                                    videoContainer.requestFullscreen();
         | 
| 1206 | 
            -
                                } else if (videoContainer.webkitRequestFullscreen) {
         | 
| 1207 | 
            -
                                    videoContainer.webkitRequestFullscreen();
         | 
| 1208 | 
            -
                                } else if (videoContainer.msRequestFullscreen) {
         | 
| 1209 | 
            -
                                    videoContainer.msRequestFullscreen();
         | 
| 1210 | 
            -
                                }
         | 
| 1211 | 
            -
                            } else {
         | 
| 1212 | 
            -
                                if (document.exitFullscreen) {
         | 
| 1213 | 
            -
                                    document.exitFullscreen();
         | 
| 1214 | 
            -
                                } else if (document.webkitExitFullscreen) {
         | 
| 1215 | 
            -
                                    document.webkitExitFullscreen();
         | 
| 1216 | 
            -
                                } else if (document.msExitFullscreen) {
         | 
| 1217 | 
            -
                                    document.msExitFullscreen();
         | 
| 1218 | 
            -
                                }
         | 
| 1219 | 
            -
                            }
         | 
| 1220 | 
            -
                        } catch (error) {
         | 
| 1221 | 
            -
                            handleError(error, '全画面表示中にエラーが発生しました');
         | 
| 1222 | 
            -
                        }
         | 
| 1223 | 
            -
                    });
         | 
| 1224 |  | 
| 1225 | 
            -
                     | 
| 1226 | 
            -
                     | 
| 1227 | 
            -
                     | 
| 1228 | 
            -
                    document.addEventListener('msfullscreenchange', handleFullscreenChange);
         | 
| 1229 |  | 
| 1230 | 
            -
                     | 
| 1231 | 
            -
             | 
| 1232 | 
            -
             | 
| 1233 | 
            -
             | 
| 1234 | 
            -
             | 
| 1235 | 
            -
             | 
| 1236 | 
            -
             | 
| 1237 | 
            -
             | 
| 1238 | 
            -
             | 
| 1239 | 
            -
             | 
| 1240 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1241 | 
             
                    }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1242 |  | 
| 1243 | 
            -
                     | 
| 1244 | 
            -
                     | 
| 1245 | 
            -
                        if (e.key === 'Escape' && isFullscreen) {
         | 
| 1246 | 
            -
                            if (document.exitFullscreen) {
         | 
| 1247 | 
            -
                                document.exitFullscreen();
         | 
| 1248 | 
            -
                            } else if (document.webkitExitFullscreen) {
         | 
| 1249 | 
            -
                                document.webkitExitFullscreen();
         | 
| 1250 | 
            -
                            } else if (document.msExitFullscreen) {
         | 
| 1251 | 
            -
                                document.msExitFullscreen();
         | 
| 1252 | 
            -
                            }
         | 
| 1253 | 
            -
                        }
         | 
| 1254 | 
            -
                    });
         | 
| 1255 |  | 
| 1256 | 
            -
                     | 
| 1257 | 
            -
             | 
| 1258 | 
            -
                        audioFiles.forEach(file => {
         | 
| 1259 | 
            -
                            try {
         | 
| 1260 | 
            -
                                const audio = new Audio(`${file}.mp3`);
         | 
| 1261 | 
            -
                                audio.preload = 'auto';
         | 
| 1262 | 
            -
                                audio.loop = false;
         | 
| 1263 | 
            -
                                
         | 
| 1264 | 
            -
                                // 初期ピッチ設定
         | 
| 1265 | 
            -
                                setPreservesPitch(audio, true);
         | 
| 1266 | 
            -
                                
         | 
| 1267 | 
            -
                                // 初期音量設定 (0.5)
         | 
| 1268 | 
            -
                                audio.volume = 0.5;
         | 
| 1269 | 
            -
                                
         | 
| 1270 | 
            -
                                audioElements[file] = audio;
         | 
| 1271 | 
            -
                                
         | 
| 1272 | 
            -
                                // 音声が読み込まれたら
         | 
| 1273 | 
            -
                                const onLoaded = function() {
         | 
| 1274 | 
            -
                                    console.log(`${file}.mp3 loaded`);
         | 
| 1275 | 
            -
                                    setPreservesPitch(audio, true); // 再度ピッチ保持を設定
         | 
| 1276 | 
            -
                                    checkLoadingComplete();
         | 
| 1277 | 
            -
                                    audio.removeEventListener('loadedmetadata', onLoaded);
         | 
| 1278 | 
            -
                                };
         | 
| 1279 | 
            -
                                
         | 
| 1280 | 
            -
                                audio.addEventListener('loadedmetadata', onLoaded);
         | 
| 1281 | 
            -
                                
         | 
| 1282 | 
            -
                                // エラー処理
         | 
| 1283 | 
            -
                                const onError = function() {
         | 
| 1284 | 
            -
                                    handleError(audio.error, `音声ファイル読み込み中にエラーが発生しました (${file}.mp3)`);
         | 
| 1285 | 
            -
                                    checkLoadingComplete(); // エラー時もカウント
         | 
| 1286 | 
            -
                                    audio.removeEventListener('error', onError);
         | 
| 1287 | 
            -
                                };
         | 
| 1288 | 
            -
                                
         | 
| 1289 | 
            -
                                audio.addEventListener('error', onError);
         | 
| 1290 | 
            -
                                
         | 
| 1291 | 
            -
                                // 音声ファイルの読み込みを開始
         | 
| 1292 | 
            -
                                try {
         | 
| 1293 | 
            -
                                    audio.load();
         | 
| 1294 | 
            -
                                } catch (e) {
         | 
| 1295 | 
            -
                                    console.error(`音声ファイル読み込みエラー (${file}.mp3):`, e);
         | 
| 1296 | 
            -
                                }
         | 
| 1297 | 
            -
                            } catch (error) {
         | 
| 1298 | 
            -
                                handleError(error, `音声ファイル初期化中にエラーが発生しました (${file}.mp3)`);
         | 
| 1299 | 
            -
                                checkLoadingComplete(); // エラー時もカウント
         | 
| 1300 | 
            -
                            }
         | 
| 1301 | 
            -
                        });
         | 
| 1302 | 
             
                    }
         | 
| 1303 | 
            -
             | 
| 1304 | 
            -
             | 
| 1305 | 
            -
             | 
| 1306 | 
            -
             | 
| 1307 | 
            -
             | 
| 1308 | 
            -
             | 
| 1309 | 
            -
             | 
| 1310 | 
            -
             | 
| 1311 | 
            -
             | 
| 1312 | 
            -
             | 
| 1313 | 
            -
             | 
| 1314 | 
            -
                                    audioElements[this.dataset.audio].volume = calculatedVolume;
         | 
| 1315 | 
            -
                                    
         | 
| 1316 | 
            -
                                    // スライダーの背景を更新
         | 
| 1317 | 
            -
                                    updateSliderBackgrounds();
         | 
| 1318 | 
            -
                                }
         | 
| 1319 | 
            -
                            } catch (error) {
         | 
| 1320 | 
            -
                                handleError(error, '音声ボリューム設定中にエラーが発生しました');
         | 
| 1321 | 
            -
                            }
         | 
| 1322 | 
            -
                        });
         | 
| 1323 | 
            -
                        
         | 
| 1324 | 
            -
                        // 初期値を1.00に設定
         | 
| 1325 | 
            -
                        slider.value = 1;
         | 
| 1326 | 
            -
                        volumeValues[index].textContent = '1.00';
         | 
| 1327 | 
            -
                    });
         | 
| 1328 | 
            -
                    
         | 
| 1329 | 
            -
                    // 全体音量スライダーのイベント (修正版)
         | 
| 1330 | 
            -
                    globalVolumeSlider.addEventListener('input', function() {
         | 
| 1331 | 
            -
                        try {
         | 
| 1332 | 
            -
                            const value = parseFloat(this.value);
         | 
| 1333 | 
            -
                            globalVolumeValue.textContent = value.toFixed(1);
         | 
| 1334 | 
            -
                            
         | 
| 1335 | 
            -
                            audioFiles.forEach(file => {
         | 
| 1336 | 
            -
                                if (audioElements[file]) {
         | 
| 1337 | 
            -
                                    const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
         | 
| 1338 | 
            -
                                    if (volumeSlider) {
         | 
| 1339 | 
            -
                                        const sliderValue = parseFloat(volumeSlider.value);
         | 
| 1340 | 
            -
                                        const calculatedVolume = sliderValue * (value / 10);
         | 
| 1341 | 
            -
                                        audioElements[file].volume = calculatedVolume;
         | 
| 1342 | 
            -
                                    }
         | 
| 1343 | 
            -
                                }
         | 
| 1344 | 
            -
                            });
         | 
| 1345 | 
            -
                            
         | 
| 1346 | 
            -
                            // スライダーの背景を更新
         | 
| 1347 | 
            -
                            updateSliderBackgrounds();
         | 
| 1348 | 
            -
                        } catch (error) {
         | 
| 1349 | 
            -
                            handleError(error, '全体音量設定中にエラーが発生しました');
         | 
| 1350 | 
            -
                        }
         | 
| 1351 | 
            -
                    });
         | 
| 1352 | 
            -
                    
         | 
| 1353 | 
            -
                    // 初期全体音量を5.0に設定
         | 
| 1354 | 
            -
                    globalVolumeSlider.value = 5;
         | 
| 1355 | 
            -
                    globalVolumeValue.textContent = '5.0';
         | 
| 1356 | 
            -
                    
         | 
| 1357 | 
            -
                    // ループ設定変更時
         | 
| 1358 | 
            -
                    loopCheckbox.addEventListener('change', function() {
         | 
| 1359 | 
            -
                        try {
         | 
| 1360 | 
            -
                            audioFiles.forEach(file => {
         | 
| 1361 | 
            -
                                if (audioElements[file]) {
         | 
| 1362 | 
            -
                                    audioElements[file].loop = this.checked;
         | 
| 1363 | 
            -
                                }
         | 
| 1364 | 
            -
                            });
         | 
| 1365 | 
            -
                        } catch (error) {
         | 
| 1366 | 
            -
                            handleError(error, 'ループ設定変更中にエラーが発生しました');
         | 
| 1367 | 
             
                        }
         | 
| 1368 | 
            -
                    } | 
| 1369 | 
            -
             | 
| 1370 | 
            -
             | 
| 1371 | 
            -
             | 
| 1372 | 
            -
             | 
| 1373 | 
            -
             | 
| 1374 | 
            -
             | 
| 1375 | 
            -
                            handleError(error, '開始時間設定中にエラーが発生しました');
         | 
| 1376 | 
             
                        }
         | 
| 1377 | 
            -
                    } | 
| 1378 | 
            -
             | 
| 1379 | 
            -
             | 
| 1380 | 
            -
             | 
| 1381 | 
            -
             | 
| 1382 | 
            -
             | 
| 1383 | 
            -
             | 
| 1384 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1385 | 
             
                        }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1386 | 
             
                    });
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1387 |  | 
| 1388 | 
             
                    // スライダーの背景を更新
         | 
| 1389 | 
            -
                     | 
| 1390 | 
            -
             | 
| 1391 | 
            -
                        const volumePercent = volumeSlider.value * 100;
         | 
| 1392 | 
            -
                        volumeSlider.style.backgroundSize = `${volumePercent}% 100%`;
         | 
| 1393 | 
            -
                        
         | 
| 1394 | 
            -
                        // 再生速度スライダー
         | 
| 1395 | 
            -
                        const speedPercent = (speedSlider.value - speedSlider.min) / (speedSlider.max - speedSlider.min) * 100;
         | 
| 1396 | 
            -
                        speedSlider.style.backgroundSize = `${speedPercent}% 100%`;
         | 
| 1397 | 
            -
                        
         | 
| 1398 | 
            -
                        // 全体音量スライダー
         | 
| 1399 | 
            -
                        const globalVolumePercent = (globalVolumeSlider.value - globalVolumeSlider.min) / (globalVolumeSlider.max - globalVolumeSlider.min) * 100;
         | 
| 1400 | 
            -
                        globalVolumeSlider.style.backgroundSize = `${globalVolumePercent}% 100%`;
         | 
| 1401 | 
            -
                        
         | 
| 1402 | 
            -
                        // 再生速度スライダー (設定)
         | 
| 1403 | 
            -
                        const playbackSpeedPercent = (playbackSpeedSlider.value - playbackSpeedSlider.min) / (playbackSpeedSlider.max - playbackSpeedSlider.min) * 100;
         | 
| 1404 | 
            -
                        playbackSpeedSlider.style.backgroundSize = `${playbackSpeedPercent}% 100%`;
         | 
| 1405 | 
            -
                        
         | 
| 1406 | 
            -
                        // 各音声スライダー
         | 
| 1407 | 
            -
                        audioSliders.forEach(slider => {
         | 
| 1408 | 
            -
                            const percent = slider.value * 100;
         | 
| 1409 | 
            -
                            slider.style.backgroundSize = `${percent}% 100%`;
         | 
| 1410 | 
            -
                        });
         | 
| 1411 | 
            -
                    }
         | 
| 1412 |  | 
| 1413 | 
            -
                    //  | 
| 1414 | 
            -
                     | 
| 1415 | 
            -
                         | 
| 1416 | 
            -
                        const sliders = [
         | 
| 1417 | 
            -
                            volumeSlider, 
         | 
| 1418 | 
            -
                            speedSlider, 
         | 
| 1419 | 
            -
                            globalVolumeSlider, 
         | 
| 1420 | 
            -
                            playbackSpeedSlider, 
         | 
| 1421 | 
            -
                            ...audioSliders
         | 
| 1422 | 
            -
                        ];
         | 
| 1423 | 
            -
                        
         | 
| 1424 | 
            -
                        sliders.forEach(slider => {
         | 
| 1425 | 
            -
                            if (slider) {
         | 
| 1426 | 
            -
                                slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)';
         | 
| 1427 | 
            -
                                slider.style.backgroundRepeat = 'no-repeat';
         | 
| 1428 | 
            -
                            }
         | 
| 1429 | 
            -
                        });
         | 
| 1430 | 
            -
                        
         | 
| 1431 | 
            -
                        updateSliderBackgrounds();
         | 
| 1432 | 
             
                    }
         | 
| 1433 | 
            -
             | 
| 1434 | 
            -
             | 
| 1435 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1436 | 
             
                        volumeSlider, 
         | 
| 1437 | 
             
                        speedSlider, 
         | 
| 1438 | 
             
                        globalVolumeSlider, 
         | 
| @@ -1440,33 +1507,23 @@ | |
| 1440 | 
             
                        ...audioSliders
         | 
| 1441 | 
             
                    ];
         | 
| 1442 |  | 
| 1443 | 
            -
                     | 
| 1444 | 
             
                        if (slider) {
         | 
| 1445 | 
            -
                            slider. | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1446 | 
             
                        }
         | 
| 1447 | 
             
                    });
         | 
| 1448 | 
            -
             | 
| 1449 | 
            -
             | 
| 1450 | 
            -
             | 
| 1451 | 
            -
             | 
| 1452 | 
            -
                    volumeSlider.value = video.volume;
         | 
| 1453 | 
            -
                    video.controls = false;
         | 
| 1454 | 
            -
                    initSliderBackgrounds();
         | 
| 1455 | 
            -
                    
         | 
| 1456 | 
            -
                    // 初期音量設定を適用
         | 
| 1457 | 
            -
                    setTimeout(() => {
         | 
| 1458 | 
            -
                        audioFiles.forEach(file => {
         | 
| 1459 | 
            -
                            if (audioElements[file]) {
         | 
| 1460 | 
            -
                                const volumeSlider = document.querySelector(`.audio-slider[data-audio="${file}"]`);
         | 
| 1461 | 
            -
                                if (volumeSlider) {
         | 
| 1462 | 
            -
                                    const value = parseFloat(volumeSlider.value);
         | 
| 1463 | 
            -
                                    const globalVolume = parseFloat(globalVolumeSlider.value) / 10;
         | 
| 1464 | 
            -
                                    audioElements[file].volume = value * globalVolume;
         | 
| 1465 | 
            -
                                }
         | 
| 1466 | 
            -
                            }
         | 
| 1467 | 
            -
                        });
         | 
| 1468 | 
            -
                    }, 1000);
         | 
| 1469 | 
            -
                });
         | 
| 1470 | 
             
            </script>
         | 
| 1471 | 
             
            </body>
         | 
| 1472 | 
             
            </html>
         | 
|  | |
| 735 | 
             
                </div>
         | 
| 736 |  | 
| 737 | 
             
            <script>
         | 
| 738 | 
            +
            document.addEventListener('DOMContentLoaded', function() {
         | 
| 739 | 
            +
                // テクノロジー風背景を生成
         | 
| 740 | 
            +
                function createTechBackground() {
         | 
| 741 | 
            +
                    const bg = document.getElementById('techBg');
         | 
| 742 | 
            +
                    
         | 
| 743 | 
            +
                    for (let i = 0; i < 200; i++) {
         | 
| 744 | 
            +
                        const line = document.createElement('div');
         | 
| 745 | 
            +
                        line.className = 'circuit-line';
         | 
| 746 |  | 
| 747 | 
            +
                        const isHorizontal = Math.random() > 0.5;
         | 
| 748 | 
            +
                        if (isHorizontal) {
         | 
| 749 | 
            +
                            line.style.width = `${Math.random() * 300 + 100}px`;
         | 
| 750 | 
            +
                            line.style.height = '1px';
         | 
| 751 | 
            +
                        } else {
         | 
| 752 | 
            +
                            line.style.width = '1px';
         | 
| 753 | 
            +
                            line.style.height = `${Math.random() * 300 + 100}px`;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 754 | 
             
                        }
         | 
| 755 |  | 
| 756 | 
            +
                        line.style.left = `${Math.random() * 100}%`;
         | 
| 757 | 
            +
                        line.style.top = `${Math.random() * 100}%`;
         | 
| 758 | 
            +
                        line.style.opacity = Math.random() * 0.5 + 0.1;
         | 
| 759 | 
            +
                        bg.appendChild(line);
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 760 | 
             
                    }
         | 
| 761 | 
            +
             | 
| 762 | 
            +
                    for (let i = 0; i < 200; i++) {
         | 
| 763 | 
            +
                        const dot = document.createElement('div');
         | 
| 764 | 
            +
                        dot.className = 'grid-dot';
         | 
| 765 | 
            +
                        dot.style.left = `${Math.random() * 100}%`;
         | 
| 766 | 
            +
                        dot.style.top = `${Math.random() * 100}%`;
         | 
| 767 | 
            +
                        bg.appendChild(dot);
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 768 | 
             
                    }
         | 
| 769 | 
            +
             | 
| 770 | 
            +
                    for (let i = 0; i < 15; i++) {
         | 
| 771 | 
            +
                        const hex = document.createElement('div');
         | 
| 772 | 
            +
                        hex.className = 'hexagon';
         | 
| 773 | 
            +
                        hex.style.left = `${Math.random() * 100}%`;
         | 
| 774 | 
            +
                        hex.style.top = `${Math.random() * 100}%`;
         | 
| 775 | 
            +
                        hex.style.transform = `rotate(${Math.random() * 360}deg)`;
         | 
| 776 | 
            +
                        hex.style.animation = `float ${Math.random() * 10 + 5}s infinite ease-in-out`;
         | 
| 777 | 
            +
                        bg.appendChild(hex);
         | 
| 778 | 
             
                    }
         | 
| 779 | 
            +
             | 
| 780 | 
            +
                    for (let i = 0; i < 8; i++) {
         | 
| 781 | 
            +
                        const pulse = document.createElement('div');
         | 
| 782 | 
            +
                        pulse.className = 'pulse';
         | 
| 783 | 
            +
                        pulse.style.left = `${Math.random() * 100}%`;
         | 
| 784 | 
            +
                        pulse.style.top = `${Math.random() * 100}%`;
         | 
| 785 | 
            +
                        pulse.style.animationDelay = `${Math.random() * 2}s`;
         | 
| 786 | 
            +
                        bg.appendChild(pulse);
         | 
| 787 | 
            +
                    }
         | 
| 788 | 
            +
                }
         | 
| 789 | 
            +
                
         | 
| 790 | 
            +
                createTechBackground();
         | 
| 791 | 
            +
                
         | 
| 792 | 
            +
                // ローディング状態を管理
         | 
| 793 | 
            +
                let loadingCount = 0;
         | 
| 794 | 
            +
                let totalToLoad = 6; // 動画 + 5つの音声ファイル
         | 
| 795 | 
            +
                let lastUpdateTime = 0;
         | 
| 796 | 
            +
                const updateInterval = 1;
         | 
| 797 | 
            +
                
         | 
| 798 | 
            +
                function checkLoadingComplete() {
         | 
| 799 | 
            +
                    loadingCount++;
         | 
| 800 | 
            +
                    if (loadingCount >= totalToLoad) {
         | 
| 801 | 
            +
                        setTimeout(function() {
         | 
| 802 | 
            +
                            const loadingOverlay = document.getElementById('loadingOverlay');
         | 
| 803 | 
            +
                            loadingOverlay.style.opacity = '0';
         | 
| 804 | 
            +
                            setTimeout(function() {
         | 
| 805 | 
            +
                                loadingOverlay.style.display = 'none';
         | 
| 806 | 
            +
                            }, 1000);
         | 
| 807 | 
            +
                        }, 500);
         | 
| 808 | 
            +
                    }
         | 
| 809 | 
            +
                }
         | 
| 810 | 
            +
                
         | 
| 811 | 
            +
                function handleError(error, message) {
         | 
| 812 | 
            +
                    console.error(message, error);
         | 
| 813 | 
            +
                    window.alert(`${message}\n\nエラー詳細: ${error.message}`);
         | 
| 814 | 
            +
                }
         | 
| 815 | 
            +
                
         | 
| 816 | 
            +
                // Web Audio Context の初期化
         | 
| 817 | 
            +
                let audioContext;
         | 
| 818 | 
            +
                try {
         | 
| 819 | 
            +
                    audioContext = new (window.AudioContext || window.webkitAudioContext)();
         | 
| 820 | 
            +
                } catch (e) {
         | 
| 821 | 
            +
                    console.error('Web Audio APIがサポートされていません:', e);
         | 
| 822 | 
            +
                }
         | 
| 823 | 
            +
             | 
| 824 | 
            +
                // 要素を取得
         | 
| 825 | 
            +
                const video = document.getElementById('video');
         | 
| 826 | 
            +
                const videoContainer = document.getElementById('video-container');
         | 
| 827 | 
            +
                const playPauseBtn = document.getElementById('play-pause-btn');
         | 
| 828 | 
            +
                const timeDisplay = document.getElementById('time-display');
         | 
| 829 | 
            +
                const progressContainer = document.getElementById('progress-container');
         | 
| 830 | 
            +
                const progressBar = document.getElementById('progress-bar');
         | 
| 831 | 
            +
                const progressTime = document.getElementById('progress-time');
         | 
| 832 | 
            +
                const volumeBtn = document.getElementById('volume-btn');
         | 
| 833 | 
            +
                const volumeSlider = document.getElementById('volume-slider');
         | 
| 834 | 
            +
                const speedSlider = document.getElementById('speed-slider');
         | 
| 835 | 
            +
                const speedValue = document.getElementById('speed-value');
         | 
| 836 | 
            +
                const playbackSpeedSlider = document.getElementById('playback-speed');
         | 
| 837 | 
            +
                const playbackSpeedValue = document.getElementById('playback-speed-value');
         | 
| 838 | 
            +
                const fullscreenBtn = document.getElementById('fullscreen-btn');
         | 
| 839 | 
            +
                const startTimeInput = document.getElementById('start-time');
         | 
| 840 | 
            +
                const endTimeInput = document.getElementById('end-time');
         | 
| 841 | 
            +
                const loopCheckbox = document.getElementById('loop');
         | 
| 842 | 
            +
                const globalVolumeSlider = document.getElementById('global-volume');
         | 
| 843 | 
            +
                const globalVolumeValue = document.getElementById('global-volume-value');
         | 
| 844 | 
            +
                const audioSliders = document.querySelectorAll('.audio-slider');
         | 
| 845 | 
            +
                const volumeValues = document.querySelectorAll('.volume-value');
         | 
| 846 | 
            +
                const setStartTimeBtn = document.getElementById('set-start-time');
         | 
| 847 | 
            +
                const setEndTimeBtn = document.getElementById('set-end-time');
         | 
| 848 | 
            +
                const disabledOverlay = document.getElementById('disabledOverlay');
         | 
| 849 | 
            +
                const combineButton = document.getElementById('combine-button');
         | 
| 850 | 
            +
                const combineStatus = document.getElementById('combine-status');
         | 
| 851 | 
            +
                const previewSection = document.getElementById('preview-section');
         | 
| 852 | 
            +
                const previewButton = document.getElementById('preview-button');
         | 
| 853 | 
            +
                const previewTime = document.getElementById('preview-time');
         | 
| 854 | 
            +
                
         | 
| 855 | 
            +
                // 音声オブジェクトを作成
         | 
| 856 | 
            +
                const audioElements = {};
         | 
| 857 | 
            +
                const audioBuffers = {};
         | 
| 858 | 
            +
                const audioFiles = ['p', 'a', 't', 's', 'k'];
         | 
| 859 | 
            +
                let combinedAudioBuffer = null;
         | 
| 860 | 
            +
                let combinedAudioSource = null;
         | 
| 861 | 
            +
                let isAudioCombined = false;
         | 
| 862 | 
            +
                let currentVolumes = { p: 0, a: 1, t: 1, s: 1, k: 0 };
         | 
| 863 | 
            +
                
         | 
| 864 | 
            +
                // 初期化
         | 
| 865 | 
            +
                let videoDuration = 0;
         | 
| 866 | 
            +
                let isPlaying = false;
         | 
| 867 | 
            +
                let lastVolume = 1;
         | 
| 868 | 
            +
                let currentPlaybackRate = 1;
         | 
| 869 | 
            +
                let isFullscreen = false;
         | 
| 870 | 
            +
                let lastSyncTime = 0;
         | 
| 871 | 
            +
                let syncDriftLog = [];
         | 
| 872 | 
            +
                
         | 
| 873 | 
            +
                // 音声ファイルをロード (改良版)
         | 
| 874 | 
            +
                function loadAudioFiles() {
         | 
| 875 | 
            +
                    audioFiles.forEach(file => {
         | 
| 876 | 
             
                        try {
         | 
| 877 | 
            +
                            const audio = new Audio(`${file}.mp3`);
         | 
| 878 | 
            +
                            audio.preload = 'auto';
         | 
| 879 | 
            +
                            audio.loop = false;
         | 
| 880 | 
            +
                            audioElements[file] = audio;
         | 
|  | |
|  | |
|  | |
| 881 |  | 
| 882 | 
            +
                            audio.addEventListener('loadedmetadata', function() {
         | 
| 883 | 
            +
                                console.log(`${file}.mp3 loaded`);
         | 
| 884 | 
            +
                                checkLoadingComplete();
         | 
| 885 | 
            +
                            });
         | 
| 886 | 
            +
                            
         | 
| 887 | 
            +
                            audio.addEventListener('error', function() {
         | 
| 888 | 
            +
                                console.error(`音声ファイル読み込みエラー (${file}.mp3):`, audio.error);
         | 
| 889 | 
            +
                                checkLoadingComplete();
         | 
| 890 | 
            +
                            });
         | 
| 891 | 
             
                        } catch (error) {
         | 
| 892 | 
            +
                            console.error(`音声ファイル初期化エラー (${file}.mp3):`, error);
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 893 | 
             
                            checkLoadingComplete();
         | 
|  | |
|  | |
| 894 | 
             
                        }
         | 
| 895 | 
             
                    });
         | 
| 896 | 
            +
                }
         | 
| 897 | 
            +
             | 
| 898 | 
            +
                // 音声を結合する関数
         | 
| 899 | 
            +
                async function combineAudio() {
         | 
| 900 | 
            +
                    if (!audioContext) {
         | 
| 901 | 
            +
                        combineStatus.textContent = "Web Audio APIが利用できません";
         | 
| 902 | 
            +
                        return;
         | 
| 903 | 
            +
                    }
         | 
| 904 | 
            +
             | 
| 905 | 
            +
                    combineButton.disabled = true;
         | 
| 906 | 
            +
                    combineStatus.textContent = "音声を合成中...";
         | 
| 907 | 
            +
             | 
| 908 | 
            +
                    try {
         | 
| 909 | 
            +
                        // 現在の音量設定を保存
         | 
| 910 | 
            +
                        audioFiles.forEach(file => {
         | 
| 911 | 
            +
                            currentVolumes[file] = parseFloat(document.querySelector(`.audio-slider[data-audio="${file}"]`).value);
         | 
| 912 | 
            +
                        });
         | 
| 913 | 
            +
             | 
| 914 | 
            +
                        // 各音声ファイルをデコード
         | 
| 915 | 
            +
                        const audioBufferPromises = audioFiles.map(async file => {
         | 
| 916 | 
            +
                            const audio = audioElements[file];
         | 
| 917 | 
            +
                            if (!audio) return null;
         | 
| 918 | 
            +
             | 
| 919 | 
            +
                            const response = await fetch(`${file}.mp3`);
         | 
| 920 | 
            +
                            const arrayBuffer = await response.arrayBuffer();
         | 
| 921 | 
            +
                            return await audioContext.decodeAudioData(arrayBuffer);
         | 
| 922 | 
            +
                        });
         | 
| 923 | 
            +
             | 
| 924 | 
            +
                        // すべての音声バッファを取得
         | 
| 925 | 
            +
                        const buffers = await Promise.all(audioBufferPromises);
         | 
| 926 | 
            +
                        audioFiles.forEach((file, index) => {
         | 
| 927 | 
            +
                            audioBuffers[file] = buffers[index];
         | 
| 928 | 
            +
                        });
         | 
| 929 | 
            +
             | 
| 930 | 
            +
                        // 最長の音声バッファの長さを取得
         | 
| 931 | 
            +
                        const maxDuration = Math.max(...buffers.filter(b => b).map(b => b.duration));
         | 
| 932 | 
            +
             | 
| 933 | 
            +
                        // 新しい音声バッファを作成
         | 
| 934 | 
            +
                        combinedAudioBuffer = audioContext.createBuffer(
         | 
| 935 | 
            +
                            2, // ステレオ
         | 
| 936 | 
            +
                            audioContext.sampleRate * maxDuration,
         | 
| 937 | 
            +
                            audioContext.sampleRate
         | 
| 938 | 
            +
                        );
         | 
| 939 | 
            +
             | 
| 940 | 
            +
                        // 各音声バッファを結合
         | 
| 941 | 
            +
                        for (let file of audioFiles) {
         | 
| 942 | 
            +
                            if (!audioBuffers[file]) continue;
         | 
| 943 | 
            +
             | 
| 944 | 
            +
                            const buffer = audioBuffers[file];
         | 
| 945 | 
            +
                            const volume = currentVolumes[file];
         | 
| 946 | 
            +
             | 
| 947 | 
            +
                            // 音量が0の場合はスキップ
         | 
| 948 | 
            +
                            if (volume === 0) continue;
         | 
| 949 | 
            +
             | 
| 950 | 
            +
                            // 各チャンネルに音声を加算
         | 
| 951 | 
            +
                            for (let channel = 0; channel < 2; channel++) {
         | 
| 952 | 
            +
                                const inputData = buffer.getChannelData(channel % buffer.numberOfChannels);
         | 
| 953 | 
            +
                                const outputData = combinedAudioBuffer.getChannelData(channel);
         | 
| 954 | 
            +
             | 
| 955 | 
            +
                                for (let i = 0; i < inputData.length; i++) {
         | 
| 956 | 
            +
                                    outputData[i] += inputData[i] * volume;
         | 
| 957 | 
            +
                                }
         | 
| 958 | 
             
                            }
         | 
| 959 | 
            +
                        }
         | 
| 960 | 
            +
             | 
| 961 | 
            +
                        // 音量を正規化 (クリッピング防止)
         | 
| 962 | 
            +
                        for (let channel = 0; channel < 2; channel++) {
         | 
| 963 | 
            +
                            const outputData = combinedAudioBuffer.getChannelData(channel);
         | 
| 964 | 
            +
                            let max = 0;
         | 
| 965 |  | 
| 966 | 
            +
                            for (let i = 0; i < outputData.length; i++) {
         | 
| 967 | 
            +
                                if (Math.abs(outputData[i]) > max) {
         | 
| 968 | 
            +
                                    max = Math.abs(outputData[i]);
         | 
| 969 | 
            +
                                }
         | 
| 970 | 
            +
                            }
         | 
| 971 | 
            +
             | 
| 972 | 
            +
                            if (max > 1) {
         | 
| 973 | 
            +
                                for (let i = 0; i < outputData.length; i++) {
         | 
| 974 | 
            +
                                    outputData[i] /= max;
         | 
| 975 | 
            +
                                }
         | 
| 976 | 
            +
                            }
         | 
| 977 | 
             
                        }
         | 
| 978 | 
            +
             | 
| 979 | 
            +
                        isAudioCombined = true;
         | 
| 980 | 
            +
                        combineStatus.textContent = "音声の合成が完了しました";
         | 
| 981 | 
            +
                        enablePlayerControls();
         | 
| 982 | 
            +
                        previewSection.style.display = 'block';
         | 
| 983 | 
            +
             | 
| 984 | 
            +
                        // 合成後に音量と再生速度を適用
         | 
| 985 | 
            +
                        applyVolume();
         | 
| 986 | 
            +
                        applyPlaybackRate();
         | 
| 987 | 
            +
             | 
| 988 | 
            +
                    } catch (error) {
         | 
| 989 | 
            +
                        console.error('音声合成エラー:', error);
         | 
| 990 | 
            +
                        combineStatus.textContent = "音声の合成に失敗しました";
         | 
| 991 | 
            +
                        combineButton.disabled = false;
         | 
| 992 | 
            +
                    }
         | 
| 993 | 
            +
                }
         | 
| 994 | 
            +
             | 
| 995 | 
            +
                // 音量を適用
         | 
| 996 | 
            +
                function applyVolume() {
         | 
| 997 | 
            +
                    if (!isAudioCombined) return;
         | 
| 998 | 
            +
                    
         | 
| 999 | 
            +
                    const globalVolume = parseFloat(globalVolumeSlider.value);
         | 
| 1000 | 
            +
                    // 音量を0-1の範囲に制限
         | 
| 1001 | 
            +
                    const clampedVolume = Math.max(0, Math.min(1, globalVolume));
         | 
| 1002 | 
            +
                    video.volume = clampedVolume;
         | 
| 1003 | 
            +
                    volumeSlider.value = clampedVolume;
         | 
| 1004 | 
            +
                    updateVolumeIcon();
         | 
| 1005 | 
            +
                }
         | 
| 1006 | 
            +
             | 
| 1007 | 
            +
                // 再生速度を適用(ピッチ維持)
         | 
| 1008 | 
            +
                function applyPlaybackRate() {
         | 
| 1009 | 
            +
                    if (!isAudioCombined) return;
         | 
| 1010 | 
            +
                    
         | 
| 1011 | 
            +
                    const speed = parseFloat(playbackSpeedSlider.value);
         | 
| 1012 | 
            +
                    currentPlaybackRate = speed;
         | 
| 1013 | 
            +
                    video.playbackRate = speed;
         | 
| 1014 | 
            +
                    
         | 
| 1015 | 
            +
                    if (combinedAudioSource) {
         | 
| 1016 | 
            +
                        combinedAudioSource.playbackRate.value = speed;
         | 
| 1017 | 
            +
                        // ピッチを維持する設定
         | 
| 1018 | 
            +
                            if ('preservesPitch' in combinedAudioSource) {
         | 
| 1019 | 
            +
                                combinedAudioSource.preservesPitch = value; // モダンブラウザ
         | 
| 1020 | 
            +
                            } else if ('webkitPreservesPitch' in combinedAudioSource) {
         | 
| 1021 | 
            +
                                combinedAudioSource.webkitPreservesPitch = value; // 古い WebKit (Chrome <86 など)
         | 
| 1022 | 
            +
                            } else if ('mozPreservesPitch' in combinedAudioSource) {
         | 
| 1023 | 
            +
                                combinedAudioSource.mozPreservesPitch = value; // 古い Gecko (Firefox ≤100)
         | 
| 1024 | 
            +
                            }
         | 
| 1025 | 
            +
                    }
         | 
| 1026 |  | 
| 1027 | 
            +
                    speedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1028 | 
            +
                    playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1029 | 
            +
                    speedSlider.value = speed;
         | 
| 1030 | 
            +
                }
         | 
| 1031 | 
            +
             | 
| 1032 | 
            +
                // プレイヤーコントロールを有効化
         | 
| 1033 | 
            +
                function enablePlayerControls() {
         | 
| 1034 | 
            +
                    disabledOverlay.style.display = 'none';
         | 
| 1035 | 
            +
                    playPauseBtn.disabled = false;
         | 
| 1036 | 
            +
                    volumeBtn.disabled = false;
         | 
| 1037 | 
            +
                    volumeSlider.disabled = false;
         | 
| 1038 | 
            +
                    speedSlider.disabled = false;
         | 
| 1039 | 
            +
                    fullscreenBtn.disabled = false;
         | 
| 1040 | 
            +
                    startTimeInput.disabled = false;
         | 
| 1041 | 
            +
                    endTimeInput.disabled = false;
         | 
| 1042 | 
            +
                    loopCheckbox.disabled = false;
         | 
| 1043 | 
            +
                    globalVolumeSlider.disabled = false;
         | 
| 1044 | 
            +
                    setStartTimeBtn.disabled = false;
         | 
| 1045 | 
            +
                    setEndTimeBtn.disabled = false;
         | 
| 1046 | 
            +
                    playbackSpeedSlider.disabled = false;
         | 
| 1047 | 
            +
                    
         | 
| 1048 | 
            +
                    // 合成後に音量と再生速度スライダーを有効化
         | 
| 1049 | 
            +
                    volumeSlider.disabled = false;
         | 
| 1050 | 
            +
                    speedSlider.disabled = false;
         | 
| 1051 | 
            +
                    playbackSpeedSlider.disabled = false;
         | 
| 1052 | 
            +
                }
         | 
| 1053 | 
            +
             | 
| 1054 | 
            +
                // プレビュー再生
         | 
| 1055 | 
            +
                function togglePreview() {
         | 
| 1056 | 
            +
                    if (!isAudioCombined || !combinedAudioBuffer) return;
         | 
| 1057 | 
            +
             | 
| 1058 | 
            +
                    if (previewButton.textContent === '▶') {
         | 
| 1059 | 
            +
                        // 再生
         | 
| 1060 | 
            +
                        if (combinedAudioSource) {
         | 
| 1061 | 
            +
                            combinedAudioSource.stop();
         | 
| 1062 | 
            +
                        }
         | 
| 1063 | 
            +
             | 
| 1064 | 
            +
                        combinedAudioSource = audioContext.createBufferSource();
         | 
| 1065 | 
            +
                        combinedAudioSource.buffer = combinedAudioBuffer;
         | 
| 1066 | 
            +
                        combinedAudioSource.connect(audioContext.destination);
         | 
| 1067 | 
            +
                        combinedAudioSource.start(0);
         | 
| 1068 | 
            +
             | 
| 1069 | 
            +
                        previewButton.textContent = '⏸';
         | 
| 1070 |  | 
| 1071 | 
            +
                        // プレビューの時間表示を更新
         | 
| 1072 | 
            +
                        const updatePreviewTime = () => {
         | 
| 1073 | 
            +
                            if (!combinedAudioSource || !isAudioCombined) return;
         | 
| 1074 |  | 
| 1075 | 
            +
                            const currentTime = audioContext.currentTime - combinedAudioSource.startTime;
         | 
| 1076 | 
            +
                            const duration = combinedAudioBuffer.duration;
         | 
|  | |
|  | |
|  | |
|  | |
| 1077 |  | 
| 1078 | 
            +
                            if (currentTime >= duration) {
         | 
| 1079 | 
            +
                                previewButton.textContent = '▶';
         | 
| 1080 | 
            +
                                previewTime.textContent = `00:00 / ${formatTime(duration)}`;
         | 
| 1081 | 
            +
                                return;
         | 
| 1082 | 
            +
                            }
         | 
| 1083 |  | 
| 1084 | 
            +
                            previewTime.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
         | 
| 1085 | 
            +
                            requestAnimationFrame(updatePreviewTime);
         | 
| 1086 | 
            +
                        };
         | 
| 1087 | 
            +
                        
         | 
| 1088 | 
            +
                        updatePreviewTime();
         | 
| 1089 | 
            +
                        
         | 
| 1090 | 
            +
                        combinedAudioSource.onended = () => {
         | 
| 1091 | 
            +
                            previewButton.textContent = '▶';
         | 
| 1092 | 
            +
                            previewTime.textContent = `00:00 / ${formatTime(combinedAudioBuffer.duration)}`;
         | 
| 1093 | 
            +
                        };
         | 
| 1094 | 
            +
                    } else {
         | 
| 1095 | 
            +
                        // 一時停止
         | 
| 1096 | 
            +
                        if (combinedAudioSource) {
         | 
| 1097 | 
            +
                            combinedAudioSource.stop();
         | 
| 1098 | 
            +
                            combinedAudioSource = null;
         | 
| 1099 | 
             
                        }
         | 
| 1100 | 
            +
                        previewButton.textContent = '▶';
         | 
| 1101 | 
             
                    }
         | 
| 1102 | 
            +
                }
         | 
| 1103 | 
            +
             | 
| 1104 | 
            +
                // 時間をフォーマットするヘルパー関数
         | 
| 1105 | 
            +
                function formatTime(seconds) {
         | 
| 1106 | 
            +
                    const mins = Math.floor(seconds / 60);
         | 
| 1107 | 
            +
                    const secs = Math.floor(seconds % 60);
         | 
| 1108 | 
            +
                    return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
         | 
| 1109 | 
            +
                }
         | 
| 1110 | 
            +
             | 
| 1111 | 
            +
                // 動画のメタデータが読み込まれたら
         | 
| 1112 | 
            +
                video.addEventListener('loadedmetadata', function() {
         | 
| 1113 | 
            +
                    try {
         | 
| 1114 | 
            +
                        videoDuration = video.duration;
         | 
| 1115 | 
            +
                        endTimeInput.value = videoDuration.toFixed(2);
         | 
| 1116 | 
            +
                        endTimeInput.max = videoDuration;
         | 
| 1117 | 
            +
                        startTimeInput.max = videoDuration - 0.1;
         | 
| 1118 | 
            +
                        updateTimeDisplay();
         | 
| 1119 | 
            +
                        checkLoadingComplete();
         | 
| 1120 | 
            +
                    } catch (error) {
         | 
| 1121 | 
            +
                        handleError(error, '動画メタデータ読み込み中にエラーが発生しました');
         | 
| 1122 | 
             
                    }
         | 
| 1123 | 
            +
                });
         | 
| 1124 | 
            +
                
         | 
| 1125 | 
            +
                // 動画エラー処理
         | 
| 1126 | 
            +
                video.addEventListener('error', function() {
         | 
| 1127 | 
            +
                    handleError(video.error, '動画読み込み中にエラーが発生しました');
         | 
| 1128 | 
            +
                });
         | 
| 1129 | 
            +
                
         | 
| 1130 | 
            +
                // 再生ボタンクリック
         | 
| 1131 | 
            +
                playPauseBtn.addEventListener('click', function() {
         | 
| 1132 | 
            +
                    const endTime = parseFloat(endTimeInput.value) || videoDuration;
         | 
| 1133 | 
            +
                    if (video.currentTime >= endTime) {
         | 
| 1134 | 
            +
                        const startTime = parseFloat(startTimeInput.value) || 0;
         | 
| 1135 | 
            +
                        seekMedia(startTime);
         | 
| 1136 | 
            +
                    }
         | 
| 1137 | 
            +
                    togglePlayPause();
         | 
| 1138 | 
            +
                });
         | 
| 1139 | 
            +
                
         | 
| 1140 | 
            +
                // 時間表示を更新
         | 
| 1141 | 
            +
                function updateTimeDisplay() {
         | 
| 1142 | 
            +
                    const now = performance.now();
         | 
| 1143 | 
            +
                    if (now - lastUpdateTime < updateInterval && !isFullscreen) return;
         | 
| 1144 | 
            +
                    lastUpdateTime = now;
         | 
| 1145 | 
            +
                    
         | 
| 1146 | 
            +
                    try {
         | 
| 1147 | 
            +
                        const currentTime = video.currentTime;
         | 
| 1148 | 
            +
                        const duration = video.duration || videoDuration;
         | 
| 1149 | 
            +
                        
         | 
| 1150 | 
            +
                        const currentMinutes = Math.floor(currentTime / 60);
         | 
| 1151 | 
            +
                        const currentSeconds = Math.floor(currentTime % 60);
         | 
| 1152 | 
            +
                        const currentMilliseconds = Math.floor((currentTime % 1) * 100);
         | 
| 1153 | 
            +
                        const durationMinutes = Math.floor(duration / 60);
         | 
| 1154 | 
            +
                        const durationSeconds = Math.floor(duration % 60);
         | 
| 1155 | 
            +
                        const durationMilliseconds = Math.floor((duration % 1) * 100);
         | 
| 1156 | 
            +
                        
         | 
| 1157 | 
            +
                        timeDisplay.textContent = 
         | 
| 1158 | 
            +
                            `${String(currentMinutes).padStart(2, '0')}:${String(currentSeconds).padStart(2, '0')}.${String(currentMilliseconds).padStart(2, '0')} / ` +
         | 
| 1159 | 
            +
                            `${String(durationMinutes).padStart(2, '0')}:${String(durationSeconds).padStart(2, '0')}.${String(durationMilliseconds).padStart(2, '0')}`;
         | 
| 1160 | 
            +
                        
         | 
| 1161 | 
            +
                        const progressPercent = (currentTime / duration) * 100;
         | 
| 1162 | 
            +
                        progressBar.style.width = `${progressPercent}%`;
         | 
| 1163 | 
            +
                    } catch (error) {
         | 
| 1164 | 
            +
                        console.error('時間表示更新エラー:', error);
         | 
| 1165 | 
            +
                    }
         | 
| 1166 | 
            +
                }
         | 
| 1167 | 
            +
                
         | 
| 1168 | 
            +
                // 再生/一時停止をトグル
         | 
| 1169 | 
            +
                function togglePlayPause() {
         | 
| 1170 | 
            +
                    if (isPlaying) {
         | 
| 1171 | 
            +
                        pauseMedia();
         | 
| 1172 | 
            +
                    } else {
         | 
| 1173 | 
            +
                        playMedia();
         | 
| 1174 | 
            +
                    }
         | 
| 1175 | 
            +
                }
         | 
| 1176 | 
            +
                
         | 
| 1177 | 
            +
                // 再生関数 (改良版)
         | 
| 1178 | 
            +
                function playMedia() {
         | 
| 1179 | 
            +
                    try {
         | 
| 1180 | 
            +
                        const duration = video.duration || videoDuration;
         | 
| 1181 | 
            +
                        const startTime = parseFloat(startTimeInput.value) || 0;
         | 
| 1182 | 
            +
                        const endTime = parseFloat(endTimeInput.value) || duration;
         | 
| 1183 | 
            +
                        
         | 
| 1184 | 
            +
                        if (video.currentTime >= endTime) {
         | 
| 1185 | 
            +
                            video.currentTime = startTime;
         | 
| 1186 | 
            +
                        }
         | 
| 1187 | 
            +
                        
         | 
| 1188 | 
            +
                        // 動画を再生
         | 
| 1189 | 
            +
                        const playPromise = video.play();
         | 
| 1190 | 
            +
                        
         | 
| 1191 | 
            +
                        if (playPromise !== undefined) {
         | 
| 1192 | 
            +
                            playPromise.then(() => {
         | 
| 1193 | 
             
                                isPlaying = true;
         | 
| 1194 | 
             
                                playPauseBtn.textContent = '⏸';
         | 
| 1195 |  | 
| 1196 | 
            +
                                // Web Audio APIで合成音声を再生
         | 
| 1197 | 
            +
                                if (combinedAudioSource) {
         | 
| 1198 | 
            +
                                    combinedAudioSource.stop();
         | 
| 1199 | 
            +
                                }
         | 
| 1200 | 
            +
                                
         | 
| 1201 | 
            +
                                combinedAudioSource = audioContext.createBufferSource();
         | 
| 1202 | 
            +
                                combinedAudioSource.buffer = combinedAudioBuffer;
         | 
| 1203 | 
            +
                                combinedAudioSource.connect(audioContext.destination);
         | 
| 1204 | 
            +
                                combinedAudioSource.start(0, video.currentTime);
         | 
| 1205 | 
            +
                                
         | 
| 1206 | 
            +
                                // 再生速度を設定
         | 
| 1207 | 
            +
                                video.playbackRate = currentPlaybackRate;
         | 
| 1208 | 
            +
                                combinedAudioSource.playbackRate.value = currentPlaybackRate;
         | 
| 1209 | 
            +
                                
         | 
| 1210 | 
            +
                                // 動画と音声の同期を維持
         | 
| 1211 | 
            +
                                combinedAudioSource.onended = () => {
         | 
| 1212 | 
            +
                                    if (loopCheckbox.checked) {
         | 
| 1213 | 
            +
                                        video.currentTime = startTime;
         | 
| 1214 | 
            +
                                        playMedia();
         | 
| 1215 | 
            +
                                    } else {
         | 
| 1216 | 
            +
                                        pauseMedia();
         | 
| 1217 | 
             
                                    }
         | 
| 1218 | 
            +
                                };
         | 
| 1219 | 
             
                            }).catch(error => {
         | 
| 1220 | 
             
                                console.error('動画再生エラー:', error);
         | 
| 1221 | 
             
                            });
         | 
|  | |
|  | |
| 1222 | 
             
                        }
         | 
| 1223 | 
            +
                    } catch (error) {
         | 
| 1224 | 
            +
                        console.error('メディア再生エラー:', error);
         | 
| 1225 | 
             
                    }
         | 
| 1226 | 
            +
                }
         | 
| 1227 | 
            +
                
         | 
| 1228 | 
            +
                // 一時停止関数
         | 
| 1229 | 
            +
                function pauseMedia() {
         | 
| 1230 | 
            +
                    try {
         | 
| 1231 | 
            +
                        video.pause();
         | 
| 1232 | 
            +
                        isPlaying = false;
         | 
| 1233 | 
            +
                        playPauseBtn.textContent = '▶';
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1234 |  | 
| 1235 | 
            +
                        if (combinedAudioSource) {
         | 
| 1236 | 
            +
                            combinedAudioSource.stop();
         | 
| 1237 | 
            +
                            combinedAudioSource = null;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1238 | 
             
                        }
         | 
| 1239 | 
            +
                    } catch (error) {
         | 
| 1240 | 
            +
                        console.error('メディア一時停止エラー:', error);
         | 
| 1241 | 
             
                    }
         | 
| 1242 | 
            +
                }
         | 
| 1243 | 
            +
                
         | 
| 1244 | 
            +
                // 時間更新時の処理
         | 
| 1245 | 
            +
                video.addEventListener('timeupdate', function() {
         | 
| 1246 | 
            +
                    const duration = video.duration || videoDuration;
         | 
| 1247 | 
            +
                    const endTime = parseFloat(endTimeInput.value) || duration;
         | 
| 1248 |  | 
| 1249 | 
            +
                    if (video.currentTime >= endTime && endTime > 0) {
         | 
| 1250 | 
            +
                        if (loopCheckbox.checked) {
         | 
| 1251 | 
            +
                            const startTime = parseFloat(startTimeInput.value) || 0;
         | 
| 1252 | 
            +
                            video.currentTime = startTime;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1253 |  | 
| 1254 | 
            +
                            if (combinedAudioSource) {
         | 
| 1255 | 
            +
                                combinedAudioSource.stop();
         | 
| 1256 | 
            +
                                combinedAudioSource = audioContext.createBufferSource();
         | 
| 1257 | 
            +
                                combinedAudioSource.buffer = combinedAudioBuffer;
         | 
| 1258 | 
            +
                                combinedAudioSource.connect(audioContext.destination);
         | 
| 1259 | 
            +
                                combinedAudioSource.start(0, startTime);
         | 
| 1260 | 
            +
                                combinedAudioSource.playbackRate.value = currentPlaybackRate;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1261 | 
             
                            }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1262 | 
             
                        } else {
         | 
| 1263 | 
            +
                            pauseMedia();
         | 
| 1264 | 
            +
                            video.currentTime = endTime;
         | 
| 1265 | 
             
                        }
         | 
| 1266 | 
             
                    }
         | 
| 1267 |  | 
| 1268 | 
            +
                    updateTimeDisplay();
         | 
| 1269 | 
            +
                });
         | 
| 1270 | 
            +
                
         | 
| 1271 | 
            +
                // プログレスバークリックでシーク
         | 
| 1272 | 
            +
                progressContainer.addEventListener('click', function(e) {
         | 
| 1273 | 
            +
                    if (!video.duration) return;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1274 |  | 
| 1275 | 
            +
                    const rect = this.getBoundingClientRect();
         | 
| 1276 | 
            +
                    const pos = (e.clientX - rect.left) / rect.width;
         | 
| 1277 | 
            +
                    const seekTime = pos * video.duration;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1278 |  | 
| 1279 | 
            +
                    seekMedia(seekTime);
         | 
| 1280 | 
            +
                });
         | 
| 1281 | 
            +
                
         | 
| 1282 | 
            +
                // 指定した時間にシーク (改良版)
         | 
| 1283 | 
            +
                function seekMedia(time) {
         | 
| 1284 | 
            +
                    try {
         | 
| 1285 | 
            +
                        const duration = video.duration || videoDuration;
         | 
| 1286 | 
            +
                        const startTime = parseFloat(startTimeInput.value) || 0;
         | 
| 1287 | 
            +
                        const endTime = parseFloat(endTimeInput.value) || duration;
         | 
| 1288 | 
            +
                        
         | 
| 1289 | 
            +
                        const seekTime = Math.max(startTime, Math.min(time, endTime));
         | 
| 1290 | 
            +
                        video.currentTime = seekTime;
         | 
| 1291 | 
            +
                        
         | 
| 1292 | 
            +
                        if (combinedAudioSource) {
         | 
| 1293 | 
            +
                            combinedAudioSource.stop();
         | 
| 1294 | 
            +
                            combinedAudioSource = audioContext.createBufferSource();
         | 
| 1295 | 
            +
                            combinedAudioBuffer = combinedAudioBuffer;
         | 
| 1296 | 
            +
                            combinedAudioSource.connect(audioContext.destination);
         | 
| 1297 |  | 
| 1298 | 
            +
                            if (isPlaying) {
         | 
| 1299 | 
            +
                                combinedAudioSource.start(0, seekTime);
         | 
| 1300 | 
            +
                                combinedAudioSource.playbackRate.value = currentPlaybackRate;
         | 
| 1301 | 
            +
                            }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1302 | 
             
                        }
         | 
| 1303 | 
            +
                    } catch (error) {
         | 
| 1304 | 
            +
                        console.error('メディアシークエラー:', error);
         | 
| 1305 | 
             
                    }
         | 
| 1306 | 
            +
                }
         | 
| 1307 | 
            +
                
         | 
| 1308 | 
            +
                // プログレスバー上でマウス移動時に時間を表示
         | 
| 1309 | 
            +
                progressContainer.addEventListener('mousemove', function(e) {
         | 
| 1310 | 
            +
                    if (!video.duration) return;
         | 
| 1311 |  | 
| 1312 | 
            +
                    const rect = this.getBoundingClientRect();
         | 
| 1313 | 
            +
                    const pos = (e.clientX - rect.left) / rect.width;
         | 
| 1314 | 
            +
                    const hoverTime = pos * video.duration;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1315 |  | 
| 1316 | 
            +
                    const minutes = Math.floor(hoverTime / 60);
         | 
| 1317 | 
            +
                    const seconds = Math.floor(hoverTime % 60);
         | 
| 1318 | 
            +
                    const milliseconds = Math.floor((hoverTime % 1) * 100);
         | 
|  | |
| 1319 |  | 
| 1320 | 
            +
                    progressTime.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(milliseconds).padStart(2, '0')}`;
         | 
| 1321 | 
            +
                    progressTime.style.display = 'block';
         | 
| 1322 | 
            +
                    progressTime.style.left = `${pos * 100}%`;
         | 
| 1323 | 
            +
                });
         | 
| 1324 | 
            +
                
         | 
| 1325 | 
            +
                progressContainer.addEventListener('mouseleave', function() {
         | 
| 1326 | 
            +
                    progressTime.style.display = 'none';
         | 
| 1327 | 
            +
                });
         | 
| 1328 | 
            +
                
         | 
| 1329 | 
            +
                // 動画クリックで再生/一時停止
         | 
| 1330 | 
            +
                video.addEventListener('click', function() {
         | 
| 1331 | 
            +
                    togglePlayPause();
         | 
| 1332 | 
            +
                });
         | 
| 1333 | 
            +
                
         | 
| 1334 | 
            +
                // 音量コントロール
         | 
| 1335 | 
            +
                volumeSlider.addEventListener('input', function() {
         | 
| 1336 | 
            +
                    if (!isAudioCombined) return;
         | 
| 1337 | 
            +
                    video.volume = this.value;
         | 
| 1338 | 
            +
                    lastVolume = this.value;
         | 
| 1339 | 
            +
                    updateVolumeIcon();
         | 
| 1340 | 
            +
                });
         | 
| 1341 | 
            +
                
         | 
| 1342 | 
            +
                // 音量ボタン
         | 
| 1343 | 
            +
                volumeBtn.addEventListener('click', function() {
         | 
| 1344 | 
            +
                    if (!isAudioCombined) return;
         | 
| 1345 | 
            +
                    
         | 
| 1346 | 
            +
                    if (video.volume > 0) {
         | 
| 1347 | 
            +
                        lastVolume = video.volume;
         | 
| 1348 | 
            +
                        video.volume = 0;
         | 
| 1349 | 
            +
                        volumeSlider.value = 0;
         | 
| 1350 | 
            +
                    } else {
         | 
| 1351 | 
            +
                        video.volume = lastVolume;
         | 
| 1352 | 
            +
                        volumeSlider.value = lastVolume;
         | 
| 1353 | 
             
                    }
         | 
| 1354 | 
            +
                    updateVolumeIcon();
         | 
| 1355 | 
            +
                });
         | 
| 1356 | 
            +
                
         | 
| 1357 | 
            +
                // 音量アイコンを更新
         | 
| 1358 | 
            +
                function updateVolumeIcon() {
         | 
| 1359 | 
            +
                    if (video.volume === 0) {
         | 
| 1360 | 
            +
                        volumeBtn.textContent = '🔇';
         | 
| 1361 | 
            +
                    } else if (video.volume < 0.5) {
         | 
| 1362 | 
            +
                        volumeBtn.textContent = '🔈';
         | 
| 1363 | 
            +
                    } else {
         | 
| 1364 | 
            +
                        volumeBtn.textContent = '🔊';
         | 
| 1365 | 
            +
                    }
         | 
| 1366 | 
            +
                }
         | 
| 1367 | 
            +
                
         | 
| 1368 | 
            +
                // 再生速度スライダー (動画プレイヤー)
         | 
| 1369 | 
            +
                speedSlider.addEventListener('input', function() {
         | 
| 1370 | 
            +
                    if (!isAudioCombined) return;
         | 
| 1371 | 
            +
                    
         | 
| 1372 | 
            +
                    const speed = parseFloat(this.value);
         | 
| 1373 | 
            +
                    speedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1374 | 
            +
                    playbackSpeedSlider.value = speed;
         | 
| 1375 | 
            +
                    playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1376 | 
            +
                    updatePlaybackRate(speed);
         | 
| 1377 | 
            +
                });
         | 
| 1378 | 
            +
                
         | 
| 1379 | 
            +
                // 再生速度スライダー (設定メニュー)
         | 
| 1380 | 
            +
                playbackSpeedSlider.addEventListener('input', function() {
         | 
| 1381 | 
            +
                    if (!isAudioCombined) return;
         | 
| 1382 | 
            +
                    
         | 
| 1383 | 
            +
                    const speed = parseFloat(this.value);
         | 
| 1384 | 
            +
                    playbackSpeedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1385 | 
            +
                    speedSlider.value = speed;
         | 
| 1386 | 
            +
                    speedValue.textContent = speed.toFixed(2) + 'x';
         | 
| 1387 | 
            +
                    updatePlaybackRate(speed);
         | 
| 1388 | 
            +
                });
         | 
| 1389 | 
            +
                
         | 
| 1390 | 
            +
                function updatePlaybackRate(speed) {
         | 
| 1391 | 
            +
                    if (!isAudioCombined) return;
         | 
| 1392 |  | 
| 1393 | 
            +
                    currentPlaybackRate = speed;
         | 
| 1394 | 
            +
                    video.playbackRate = speed;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1395 |  | 
| 1396 | 
            +
                    if (combinedAudioSource) {
         | 
| 1397 | 
            +
                        combinedAudioSource.playbackRate.value = speed;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1398 | 
             
                    }
         | 
| 1399 | 
            +
                }
         | 
| 1400 | 
            +
                
         | 
| 1401 | 
            +
                // 全画面ボタン
         | 
| 1402 | 
            +
                fullscreenBtn.addEventListener('click', function() {
         | 
| 1403 | 
            +
                    if (!isFullscreen) {
         | 
| 1404 | 
            +
                        if (videoContainer.requestFullscreen) {
         | 
| 1405 | 
            +
                            videoContainer.requestFullscreen();
         | 
| 1406 | 
            +
                        } else if (videoContainer.webkitRequestFullscreen) {
         | 
| 1407 | 
            +
                            videoContainer.webkitRequestFullscreen();
         | 
| 1408 | 
            +
                        } else if (videoContainer.msRequestFullscreen) {
         | 
| 1409 | 
            +
                            videoContainer.msRequestFullscreen();
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1410 | 
             
                        }
         | 
| 1411 | 
            +
                    } else {
         | 
| 1412 | 
            +
                        if (document.exitFullscreen) {
         | 
| 1413 | 
            +
                            document.exitFullscreen();
         | 
| 1414 | 
            +
                        } else if (document.webkitExitFullscreen) {
         | 
| 1415 | 
            +
                            document.webkitExitFullscreen();
         | 
| 1416 | 
            +
                        } else if (document.msExitFullscreen) {
         | 
| 1417 | 
            +
                            document.msExitFullscreen();
         | 
|  | |
| 1418 | 
             
                        }
         | 
| 1419 | 
            +
                    }
         | 
| 1420 | 
            +
                });
         | 
| 1421 | 
            +
                
         | 
| 1422 | 
            +
                // 全画面変更イベント
         | 
| 1423 | 
            +
                document.addEventListener('fullscreenchange', handleFullscreenChange);
         | 
| 1424 | 
            +
                document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
         | 
| 1425 | 
            +
                document.addEventListener('msfullscreenchange', handleFullscreenChange);
         | 
| 1426 | 
            +
                
         | 
| 1427 | 
            +
                function handleFullscreenChange() {
         | 
| 1428 | 
            +
                    isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
         | 
| 1429 | 
            +
                    fullscreenBtn.textContent = isFullscreen ? '⛶' : '⛶';
         | 
| 1430 | 
            +
                    video.controls = false;
         | 
| 1431 | 
            +
                }
         | 
| 1432 | 
            +
                
         | 
| 1433 | 
            +
                // キーボードイベント (ESCで全画面終了)
         | 
| 1434 | 
            +
                document.addEventListener('keydown', function(e) {
         | 
| 1435 | 
            +
                    if (e.key === 'Escape' && isFullscreen) {
         | 
| 1436 | 
            +
                        if (document.exitFullscreen) {
         | 
| 1437 | 
            +
                            document.exitFullscreen();
         | 
| 1438 | 
            +
                        } else if (document.webkitExitFullscreen) {
         | 
| 1439 | 
            +
                            document.webkitExitFullscreen();
         | 
| 1440 | 
            +
                        } else if (document.msExitFullscreen) {
         | 
| 1441 | 
            +
                            document.msExitFullscreen();
         | 
| 1442 | 
             
                        }
         | 
| 1443 | 
            +
                    }
         | 
| 1444 | 
            +
                });
         | 
| 1445 | 
            +
                
         | 
| 1446 | 
            +
                // ボリュームスライダーのイベント
         | 
| 1447 | 
            +
                audioSliders.forEach((slider, index) => {
         | 
| 1448 | 
            +
                    slider.addEventListener('input', function() {
         | 
| 1449 | 
            +
                        const value = parseFloat(this.value);
         | 
| 1450 | 
            +
                        volumeValues[index].textContent = value.toFixed(2);
         | 
| 1451 | 
            +
                        
         | 
| 1452 | 
            +
                        // スライダーの背景を更新
         | 
| 1453 | 
            +
                        const percent = value * 100;
         | 
| 1454 | 
            +
                        this.style.backgroundSize = `${percent}% 100%`;
         | 
| 1455 | 
             
                    });
         | 
| 1456 | 
            +
                });
         | 
| 1457 | 
            +
                
         | 
| 1458 | 
            +
                globalVolumeSlider.addEventListener('input', function() {
         | 
| 1459 | 
            +
                    const value = parseFloat(this.value);
         | 
| 1460 | 
            +
                    // 表示値は0-10の範囲で表示
         | 
| 1461 | 
            +
                    globalVolumeValue.textContent = value.toFixed(1);
         | 
| 1462 |  | 
| 1463 | 
             
                    // スライダーの背景を更新
         | 
| 1464 | 
            +
                    const percent = (value - this.min) / (this.max - this.min) * 100;
         | 
| 1465 | 
            +
                    this.style.backgroundSize = `${percent}% 100%`;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1466 |  | 
| 1467 | 
            +
                    // 合成後に音量を適用(0-1の範囲に変換)
         | 
| 1468 | 
            +
                    if (isAudioCombined) {
         | 
| 1469 | 
            +
                        applyVolume();
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1470 | 
             
                    }
         | 
| 1471 | 
            +
                });
         | 
| 1472 | 
            +
                
         | 
| 1473 | 
            +
                // ループ設定変更時
         | 
| 1474 | 
            +
                loopCheckbox.addEventListener('change', function() {
         | 
| 1475 | 
            +
                    // 合成音声ではループは動画に依存する
         | 
| 1476 | 
            +
                });
         | 
| 1477 | 
            +
                
         | 
| 1478 | 
            +
                // 現在の秒数を開始時間に設定
         | 
| 1479 | 
            +
                setStartTimeBtn.addEventListener('click', function() {
         | 
| 1480 | 
            +
                    startTimeInput.value = video.currentTime.toFixed(2);
         | 
| 1481 | 
            +
                });
         | 
| 1482 | 
            +
                
         | 
| 1483 | 
            +
                // 現在の秒数を終了時間に設定
         | 
| 1484 | 
            +
                setEndTimeBtn.addEventListener('click', function() {
         | 
| 1485 | 
            +
                    endTimeInput.value = video.currentTime.toFixed(2);
         | 
| 1486 | 
            +
                });
         | 
| 1487 | 
            +
                
         | 
| 1488 | 
            +
                // 合成ボタンクリック
         | 
| 1489 | 
            +
                combineButton.addEventListener('click', combineAudio);
         | 
| 1490 | 
            +
                
         | 
| 1491 | 
            +
                // プレビューボタンクリック
         | 
| 1492 | 
            +
                previewButton.addEventListener('click', togglePreview);
         | 
| 1493 | 
            +
                
         | 
| 1494 | 
            +
                // 初期化
         | 
| 1495 | 
            +
                loadAudioFiles();
         | 
| 1496 | 
            +
                updateVolumeIcon();
         | 
| 1497 | 
            +
                volumeSlider.value = video.volume;
         | 
| 1498 | 
            +
                video.controls = false;
         | 
| 1499 | 
            +
                
         | 
| 1500 | 
            +
                // スライダーの背景を初期化
         | 
| 1501 | 
            +
                function initSliderBackgrounds() {
         | 
| 1502 | 
            +
                    const sliders = [
         | 
| 1503 | 
             
                        volumeSlider, 
         | 
| 1504 | 
             
                        speedSlider, 
         | 
| 1505 | 
             
                        globalVolumeSlider, 
         | 
|  | |
| 1507 | 
             
                        ...audioSliders
         | 
| 1508 | 
             
                    ];
         | 
| 1509 |  | 
| 1510 | 
            +
                    sliders.forEach(slider => {
         | 
| 1511 | 
             
                        if (slider) {
         | 
| 1512 | 
            +
                            slider.style.backgroundImage = 'linear-gradient(#64ffda, #64ffda)';
         | 
| 1513 | 
            +
                            slider.style.backgroundRepeat = 'no-repeat';
         | 
| 1514 | 
            +
                            
         | 
| 1515 | 
            +
                            if (slider === globalVolumeSlider) {
         | 
| 1516 | 
            +
                                const percent = (slider.value - slider.min) / (slider.max - slider.min) * 100;
         | 
| 1517 | 
            +
                                slider.style.backgroundSize = `${percent}% 100%`;
         | 
| 1518 | 
            +
                            } else {
         | 
| 1519 | 
            +
                                slider.style.backgroundSize = `${slider.value * 100}% 100%`;
         | 
| 1520 | 
            +
                            }
         | 
| 1521 | 
             
                        }
         | 
| 1522 | 
             
                    });
         | 
| 1523 | 
            +
                }
         | 
| 1524 | 
            +
                
         | 
| 1525 | 
            +
                initSliderBackgrounds();
         | 
| 1526 | 
            +
            });
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1527 | 
             
            </script>
         | 
| 1528 | 
             
            </body>
         | 
| 1529 | 
             
            </html>
         | 
