"use client"; import { useEffect, useRef, useState } from 'react'; import MidiPlayer from 'midi-player-js'; import Soundfont from 'soundfont-player'; // 定義 Soundfont Player 的 instrument 類型,以獲得更好的 TypeScript 支援 interface Instrument { play(noteName: string, when?: number, options?: any): void; stop(noteName?: string): void; } export default function Home() { // 1. 將 useRef 的初始值設為 null,避免在伺服器端存取 window const audioCtx = useRef(null); const instrument = useRef(null); const player = useRef(null); const [isLoaded, setIsLoaded] = useState(false); const [nowPlaying, setNowPlaying] = useState(""); useEffect(() => { // 2. 在 useEffect 中才建立 AudioContext,確保只在瀏覽器環境中執行 if (!audioCtx.current) { audioCtx.current = new (window.AudioContext || (window as any).webkitAudioContext)(); } // 載入 Soundfont 和 MIDI 播放器 Soundfont.instrument(audioCtx.current, 'acoustic_grand_piano').then(inst => { instrument.current = inst; player.current = new MidiPlayer.Player(event => { if (!instrument.current) return; if (event.name === 'Note on' && event.velocity > 0) { instrument.current.play(event.noteName); } if (event.name === 'Note off' || (event.name === 'Note on' && event.velocity === 0)) { instrument.current.stop(event.noteName); } }); player.current.on('end', () => setNowPlaying("")); setIsLoaded(true); }); // 3. 元件卸載時的清理函數,關閉 AudioContext return () => { if (audioCtx.current && audioCtx.current.state !== 'closed') { audioCtx.current.close(); } }; }, []); // 空依賴陣列確保此 effect 只執行一次 const loadMidiAndPlay = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file && player.current) { const reader = new FileReader(); reader.onload = (event) => { if (event.target?.result) { player.current?.loadArrayBuffer(event.target.result as ArrayBuffer); setNowPlaying(`正在播放: ${file.name}`); player.current?.play(); } }; reader.readAsArrayBuffer(file); } }; const handlePlay = () => { if (player.current?.isPlaying()) return; player.current?.play(); }; const handleStop = () => { player.current?.stop(); setNowPlaying(""); }; return (

🎵 MIDI Player Lite

{isLoaded ? ( <>

請選擇一個 .mid 或 .midi 檔案來播放。

{nowPlaying &&

{nowPlaying}

} ) : (

正在載入聲音引擎...

)}
); }