131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import scdl from 'soundcloud-downloader';
|
|
import { Readable } from 'stream';
|
|
|
|
interface SearchTrack {
|
|
id: number;
|
|
title: string;
|
|
user?: {
|
|
username: string;
|
|
avatar_url?: string;
|
|
};
|
|
artwork_url?: string;
|
|
duration: number;
|
|
permalink_url: string;
|
|
streamable: boolean;
|
|
downloadable: boolean;
|
|
}
|
|
|
|
interface TrackResult {
|
|
id: number;
|
|
title: string;
|
|
channel: string;
|
|
thumbnail: string;
|
|
duration: number;
|
|
url: string;
|
|
streamable: boolean;
|
|
downloadable: boolean;
|
|
}
|
|
|
|
interface TrackInfo {
|
|
title: string;
|
|
author: string;
|
|
length: number;
|
|
available: boolean;
|
|
}
|
|
|
|
export class SoundCloudService {
|
|
constructor() {
|
|
console.log('SoundCloud service initialized');
|
|
}
|
|
|
|
async searchTracks(query: string, maxResults: number = 10): Promise<TrackResult[]> {
|
|
try {
|
|
console.log(`Searching SoundCloud for: ${query}`);
|
|
|
|
// Search for tracks on SoundCloud
|
|
const tracks = await scdl.search({
|
|
query: query,
|
|
limit: maxResults,
|
|
resourceType: 'tracks'
|
|
}) as any;
|
|
|
|
if (!tracks || tracks.length === 0) {
|
|
console.log('No tracks found');
|
|
return [];
|
|
}
|
|
|
|
const trackResults: TrackResult[] = tracks.map(track => ({
|
|
id: track.id,
|
|
title: track.title,
|
|
channel: track.user?.username || 'Unknown Artist',
|
|
thumbnail: track.artwork_url || track.user?.avatar_url || 'https://via.placeholder.com/300x300?text=No+Image',
|
|
duration: Math.floor(track.duration / 1000) || 0, // Convert from ms to seconds
|
|
url: track.permalink_url,
|
|
streamable: track.streamable,
|
|
downloadable: track.downloadable
|
|
}));
|
|
|
|
console.log(`Found ${trackResults.length} tracks on SoundCloud`);
|
|
return trackResults;
|
|
|
|
} catch (error: any) {
|
|
console.error('SoundCloud search error:', error.message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getTrackInfo(trackId: string | number): Promise<TrackInfo> {
|
|
try {
|
|
const track = await scdl.getInfo(String(trackId)) as SearchTrack;
|
|
return {
|
|
title: track.title,
|
|
author: track.user?.username || 'Unknown',
|
|
length: Math.floor(track.duration / 1000),
|
|
available: track.streamable
|
|
};
|
|
} catch (error) {
|
|
console.error('Error getting track info:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getAudioStream(trackId: string | number): Promise<Readable> {
|
|
try {
|
|
console.log(`Getting audio stream for track: ${trackId}`);
|
|
|
|
// Get track info first
|
|
const trackInfo = await scdl.getInfo(String(trackId)) as SearchTrack;
|
|
|
|
if (!trackInfo.streamable) {
|
|
throw new Error('Track is not streamable');
|
|
}
|
|
|
|
console.log(`Track: ${trackInfo.title}`);
|
|
console.log(`Artist: ${trackInfo.user?.username || 'Unknown'}`);
|
|
console.log(`Duration: ${Math.floor(trackInfo.duration / 1000)}s`);
|
|
|
|
// Get audio stream
|
|
const stream = await scdl.download(String(trackId));
|
|
|
|
console.log('Audio stream obtained successfully from SoundCloud');
|
|
return stream;
|
|
|
|
} catch (error: any) {
|
|
console.error('SoundCloud download failed:', error.message);
|
|
|
|
// Try alternative approach
|
|
try {
|
|
console.log('Trying alternative SoundCloud method...');
|
|
const trackUrl = `https://soundcloud.com/track/${trackId}`;
|
|
const stream = await scdl.download(trackUrl);
|
|
|
|
console.log('Audio stream obtained with alternative method');
|
|
return stream;
|
|
|
|
} catch (fallbackError: any) {
|
|
console.error('Alternative method also failed:', fallbackError.message);
|
|
throw new Error(`SoundCloud download failed: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
} |