feat: bundle FFmpeg via ffmpeg-static for one-click deployment

Users no longer need to install FFmpeg separately — it is now bundled as
an npm dependency (ffmpeg-static). player.ts resolves the bundled binary
automatically and falls back to system PATH. start.bat and a new
setup.bat script handle dependency installation and project building.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 03:43:10 +08:00
1 parent 00231db0cb
commit 61d9bbe0b7
5 files changed
+1034 -18

No files matched your search

+897
View File
File diff suppressed because it is too large. Load diff
+7
View File
@@ -15,9 +15,16 @@
"dependencies": { "dependencies": {
"@discordjs/opus": "^0.10.0", "@discordjs/opus": "^0.10.0",
"@honeybbq/teamspeak-client": "^0.1.0", "@honeybbq/teamspeak-client": "^0.1.0",
"@koa/router": "^15.4.0",
"@sansenjian/qq-music-api": "^2.2.9",
"axios": "^1.14.0", "axios": "^1.14.0",
"better-sqlite3": "^12.8.0", "better-sqlite3": "^12.8.0",
"chalk": "^5.6.2",
"express": "^5.2.1", "express": "^5.2.1",
"ffmpeg-static": "^5.3.0",
"koa": "^3.2.0",
"koa-bodyparser": "^4.4.1",
"koa-static": "^5.0.0",
"NeteaseCloudMusicApi": "^4.30.0", "NeteaseCloudMusicApi": "^4.30.0",
"pino": "^10.3.1", "pino": "^10.3.1",
"ts3-nodejs-library": "^3.5.1", "ts3-nodejs-library": "^3.5.1",
+91
View File
@@ -0,0 +1,91 @@
@echo off
title TSMusicBot Setup
echo ============================================
echo TSMusicBot - First-Time Setup (Windows)
echo ============================================
echo.
:: Resolve project root (one level up from scripts/)
cd /d "%~dp0.."
:: ---- Step 1: Check / install Node.js ----
where node >nul 2>&1
if %errorlevel% neq 0 (
echo Node.js not found. Attempting automatic installation...
echo.
:: Try winget first (available on Windows 10 1709+ and Windows 11)
where winget >nul 2>&1
if %errorlevel% equ 0 (
echo Installing Node.js via winget...
winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements
if %errorlevel% neq 0 (
echo winget installation failed. Please install Node.js manually from https://nodejs.org
pause
exit /b 1
)
:: Refresh PATH so node is available in this session
call refreshenv >nul 2>&1
:: If refreshenv is not available, ask user to restart
where node >nul 2>&1
if %errorlevel% neq 0 (
echo.
echo Node.js was installed but is not yet available in this terminal.
echo Please close this window and run setup.bat again.
pause
exit /b 0
)
) else (
echo winget is not available on this system.
echo Please install Node.js 20 LTS manually from https://nodejs.org
echo After installing, close this window and run setup.bat again.
pause
exit /b 1
)
) else (
echo [OK] Node.js found.
node --version
)
echo.
:: ---- Step 2: Install npm dependencies ----
echo Installing dependencies (this may take a few minutes)...
call npm install
if %errorlevel% neq 0 (
echo.
echo npm install failed. Check the error messages above.
pause
exit /b 1
)
echo [OK] Dependencies installed.
echo.
:: ---- Step 3: Build the project ----
echo Building TypeScript project...
call npx tsc
if %errorlevel% neq 0 (
echo.
echo Build failed. Check the error messages above.
pause
exit /b 1
)
echo [OK] Build succeeded.
echo.
:: ---- Step 4: Create default config if missing ----
if not exist "config.json" (
echo Creating default config.json...
echo Please edit config.json with your TeamSpeak server details before starting the bot.
) else (
echo [OK] config.json already exists.
)
echo.
:: ---- Done ----
echo ============================================
echo Setup complete!
echo ============================================
echo.
echo To start the bot, run: scripts\start.bat
echo.
pause
+25 -17
View File
@@ -6,34 +6,42 @@ echo.
:: Check if node is available :: Check if node is available
where node >nul 2>&1 where node >nul 2>&1
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
echo Node.js is not installed. Please install Node.js 20 LTS from https://nodejs.org echo Node.js is not installed.
echo Run scripts\setup.bat for automatic installation, or install Node.js 20+ from https://nodejs.org
pause pause
exit /b 1 exit /b 1
) )
:: Check if ffmpeg is available :: Resolve project root (one level up from scripts/)
where ffmpeg >nul 2>&1 cd /d "%~dp0.."
if %errorlevel% neq 0 (
echo FFmpeg not found in PATH. Checking local directory...
if exist "%~dp0ffmpeg\ffmpeg.exe" (
set PATH=%~dp0ffmpeg;%PATH%
echo Using bundled FFmpeg.
) else (
echo FFmpeg is required. Please install FFmpeg or place it in the ffmpeg\ directory.
pause
exit /b 1
)
)
:: Install dependencies if needed :: Install dependencies if needed
if not exist "%~dp0node_modules" ( if not exist "node_modules" (
echo Installing dependencies... echo Installing dependencies...
cd /d "%~dp0"
call npm install --production call npm install --production
if %errorlevel% neq 0 (
echo Failed to install dependencies.
pause
exit /b 1
)
) )
:: Build if dist/ doesn't exist
if not exist "dist" (
echo Building project...
call npx tsc
if %errorlevel% neq 0 (
echo Build failed.
pause
exit /b 1
)
)
:: FFmpeg is bundled via ffmpeg-static — no PATH check needed.
echo FFmpeg is bundled via node_modules (ffmpeg-static).
echo.
:: Start the application :: Start the application
cd /d "%~dp0"
node dist/index.js node dist/index.js
pause pause
+14 -1
View File
@@ -1,8 +1,19 @@
import { spawn, type ChildProcess } from "node:child_process"; import { spawn, type ChildProcess } from "node:child_process";
import { EventEmitter } from "node:events"; import { EventEmitter } from "node:events";
import { createRequire } from "node:module";
import { createOpusEncoder, PCM_FRAME_BYTES, type Encoder } from "./encoder.js"; import { createOpusEncoder, PCM_FRAME_BYTES, type Encoder } from "./encoder.js";
import type { Logger } from "../logger.js"; import type { Logger } from "../logger.js";
// ffmpeg-static is a CJS module that exports the path to the bundled ffmpeg binary.
const require = createRequire(import.meta.url);
const ffmpegPath: string | null = require("ffmpeg-static");
/** Resolve ffmpeg binary: prefer bundled ffmpeg-static, fall back to system PATH. */
function getFfmpegCommand(): string {
if (ffmpegPath) return ffmpegPath;
return "ffmpeg"; // fallback to system-installed ffmpeg
}
export interface PlayerEvents { export interface PlayerEvents {
frame: (opusFrame: Buffer) => void; frame: (opusFrame: Buffer) => void;
trackEnd: () => void; trackEnd: () => void;
@@ -57,7 +68,9 @@ export class AudioPlayer extends EventEmitter {
"-", "-",
); );
this.ffmpeg = spawn("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] }); const ffmpegBin = getFfmpegCommand();
this.logger.debug({ ffmpeg: ffmpegBin }, "Using ffmpeg binary");
this.ffmpeg = spawn(ffmpegBin, args, { stdio: ["ignore", "pipe", "pipe"] });
this.ffmpeg.stderr!.on("data", () => {}); this.ffmpeg.stderr!.on("data", () => {});