Add music player

This commit is contained in:
SirBlob committed 2026-10-31 21:17:42 -04:00
1 parent c820672803
commit 40604b4a04
17 files changed
+1153

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
import { Quality, Ytdl, parseVideoId } from '@distube/ytdl-core';
import { spawn } from 'node:child_process';
import { Readable } from 'node:stream';
import { playlist } from '#lib/content';
import type { Track } from '#lib/music/types';
const downloadBufferBytes = 32 * 1024 * 1024;
const mp3Bitrate = '160k';
const youtube = new Ytdl();
const playlistVideoIds = playlist.videos.flatMap((video) => {
const videoId = parseVideoId(video);
return videoId ? [videoId] : [];
});
const allowedVideoIds = new Set(playlistVideoIds);
const trackCache = new Map<string, Track>();
export function isPlaylistVideo(videoId: string): boolean {
return allowedVideoIds.has(videoId);
}
async function getTrack(videoId: string): Promise<Track> {
const cachedTrack = trackCache.get(videoId);
if (cachedTrack) return cachedTrack;
const { videoDetails } = await youtube.getInfo(videoId);
const track: Track = {
id: videoId,
title: videoDetails.title,
artist: videoDetails.author.name,
durationSeconds: videoDetails.lengthSeconds,
cover: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
};
trackCache.set(videoId, track);
return track;
}
export async function getPlaylistTracks(): Promise<Track[]> {
const results = await Promise.allSettled(playlistVideoIds.map(getTrack));
return results.flatMap((result, index) => {
if (result.status === 'fulfilled') return [result.value];
console.error(`Music lookup failed for ${playlistVideoIds[index]}:`, result.reason);
return [];
});
}
export function streamTrackAsMp3(videoId: string, startSeconds: number): ReadableStream<Uint8Array> {
const downloadController = new AbortController();
const youtubeAudio = youtube.download(videoId, {
quality: Quality.HighestAudio,
filter: { hasAudio: true, hasVideo: false },
highWaterMark: downloadBufferBytes,
signal: downloadController.signal
});
const ffmpegArguments = ['-loglevel', 'error', '-ss', String(startSeconds), '-i', 'pipe:0', '-vn', '-f', 'mp3', '-b:a', mp3Bitrate, 'pipe:1'];
const ffmpeg = spawn('ffmpeg', ffmpegArguments, { stdio: ['pipe', 'pipe', 'inherit'] });
const stop = () => {
downloadController.abort();
youtubeAudio.destroy();
if (ffmpeg.exitCode === null) ffmpeg.kill('SIGKILL');
};
youtubeAudio.on('error', (error) => {
console.error(`Music download failed for ${videoId}:`, error);
stop();
});
ffmpeg.on('error', (error) => {
console.error('ffmpeg failed to start:', error);
stop();
});
ffmpeg.stdin.on('error', stop);
ffmpeg.stdout.on('close', stop);
youtubeAudio.pipe(ffmpeg.stdin);
return Readable.toWeb(ffmpeg.stdout) as ReadableStream<Uint8Array>;
}