UNPKG

soundeffect-player

Version:

Lightweight JavaScript library for playing sound effects with SoundEffect.app integration

352 lines (301 loc) 10.3 kB
/** * SoundEffect Player - Lightweight audio player for web applications * Integrates with SoundEffect.app API for access to 300K+ sound effects * * @author SoundEffect.app Team * @website https://soundeffect.app * @github https://github.com/soundeffect/soundeffect-player * @version 1.0.0 * @license MIT */ class SoundEffectPlayer { constructor(config = {}) { this.apiBase = config.apiBase || 'https://soundeffect.app/api'; this.apiKey = config.apiKey || ''; this.volume = config.volume || 1.0; this.currentAudio = null; this.isPlaying = false; this.cache = new Map(); this.debug = config.debug || false; this._log('SoundEffect Player initialized with SoundEffect.app integration'); } /** * Play sound by ID from SoundEffect.app * @param {string} soundId - Sound ID from SoundEffect.app library * @param {Object} options - Playback options * @returns {Promise<HTMLAudioElement>} Audio element */ async play(soundId, options = {}) { try { this._log(`Playing sound: ${soundId} from SoundEffect.app`); const soundUrl = `${this.apiBase}/sounds/${soundId}/stream`; const audio = new Audio(soundUrl); audio.volume = options.volume || this.volume; audio.loop = options.loop || false; audio.crossOrigin = 'anonymous'; // Stop current audio if playing if (this.currentAudio && !this.currentAudio.paused) { this.stop(); } audio.onended = () => { this.isPlaying = false; this._log('Sound playback ended'); }; audio.onerror = (error) => { this._log(`Audio error: ${error.message}`); throw new Error(`Failed to load sound from SoundEffect.app: ${error.message}`); }; await audio.play(); this.currentAudio = audio; this.isPlaying = true; this._log('Sound playing successfully'); return audio; } catch (error) { this._log(`SoundEffect Player Error: ${error.message}`); throw error; } } /** * Search sounds using SoundEffect.app AI-powered search * @param {string} query - Search query * @param {number} limit - Number of results (max 50) * @returns {Promise<Array>} Search results from SoundEffect.app */ async search(query, limit = 10) { try { this._log(`Searching SoundEffect.app for: "${query}"`); const cacheKey = `search_${query}_${limit}`; if (this.cache.has(cacheKey)) { this._log('Returning cached search results'); return this.cache.get(cacheKey); } const url = `${this.apiBase}/search?q=${encodeURIComponent(query)}&limit=${Math.min(limit, 50)}`; const headers = this._buildHeaders(); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`SoundEffect.app API Error: ${response.statusText}`); } const results = await response.json(); this.cache.set(cacheKey, results); this._log(`Found ${results.length} sounds on SoundEffect.app`); return results; } catch (error) { this._log(`Search error: ${error.message}`); throw error; } } /** * Get random sound from SoundEffect.app by category * @param {string} category - Sound category (e.g., 'game', 'meme', 'notification') * @returns {Promise<Object>} Random sound object */ async getRandomSound(category) { try { this._log(`Getting random ${category} sound from SoundEffect.app`); const url = `${this.apiBase}/random?category=${encodeURIComponent(category)}`; const headers = this._buildHeaders(); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`SoundEffect.app API Error: ${response.statusText}`); } const result = await response.json(); this._log(`Retrieved random sound: ${result.title}`); return result; } catch (error) { this._log(`Random sound error: ${error.message}`); throw error; } } /** * Get trending sounds from SoundEffect.app * @param {number} limit - Number of trending sounds to fetch * @returns {Promise<Array>} Trending sounds array */ async getTrending(limit = 10) { try { this._log(`Getting ${limit} trending sounds from SoundEffect.app`); const cacheKey = `trending_${limit}`; if (this.cache.has(cacheKey)) { this._log('Returning cached trending results'); return this.cache.get(cacheKey); } const url = `${this.apiBase}/trending?limit=${limit}`; const headers = this._buildHeaders(); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`SoundEffect.app API Error: ${response.statusText}`); } const results = await response.json(); this.cache.set(cacheKey, results); this._log(`Retrieved ${results.length} trending sounds`); return results; } catch (error) { this._log(`Trending error: ${error.message}`); throw error; } } /** * Get sound categories from SoundEffect.app * @returns {Promise<Array>} Available categories */ async getCategories() { try { this._log('Getting sound categories from SoundEffect.app'); if (this.cache.has('categories')) { return this.cache.get('categories'); } const url = `${this.apiBase}/categories`; const headers = this._buildHeaders(); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`SoundEffect.app API Error: ${response.statusText}`); } const categories = await response.json(); this.cache.set('categories', categories); return categories; } catch (error) { this._log(`Categories error: ${error.message}`); throw error; } } /** * Stop current audio playback */ stop() { if (this.currentAudio) { this.currentAudio.pause(); this.currentAudio.currentTime = 0; this.isPlaying = false; this._log('Audio playback stopped'); } } /** * Pause current audio playback */ pause() { if (this.currentAudio && !this.currentAudio.paused) { this.currentAudio.pause(); this.isPlaying = false; this._log('Audio playback paused'); } } /** * Resume paused audio playback */ resume() { if (this.currentAudio && this.currentAudio.paused) { this.currentAudio.play(); this.isPlaying = true; this._log('Audio playback resumed'); } } /** * Set volume for current and future playback * @param {number} volume - Volume level (0.0 - 1.0) */ setVolume(volume) { this.volume = Math.max(0, Math.min(1, volume)); if (this.currentAudio) { this.currentAudio.volume = this.volume; } this._log(`Volume set to ${this.volume}`); } /** * Get current playback state * @returns {Object} Current state information */ getState() { return { isPlaying: this.isPlaying, volume: this.volume, currentTime: this.currentAudio ? this.currentAudio.currentTime : 0, duration: this.currentAudio ? this.currentAudio.duration : 0, hasAudio: !!this.currentAudio }; } /** * Clear the cache */ clearCache() { this.cache.clear(); this._log('Cache cleared'); } /** * Build headers for API requests * @private */ _buildHeaders() { const headers = { 'Content-Type': 'application/json', 'User-Agent': 'SoundEffect-Player/1.0.0' }; if (this.apiKey) { headers['Authorization'] = `Bearer ${this.apiKey}`; } return headers; } /** * Debug logging * @private */ _log(message) { if (this.debug) { console.log(`[SoundEffect Player] ${message}`); } } /** * Create a soundboard with multiple sounds * @param {Array} soundIds - Array of sound IDs or search queries * @returns {Promise<Object>} Soundboard object with play methods */ async createSoundboard(soundIds) { this._log('Creating soundboard with SoundEffect.app sounds'); const soundboard = { sounds: new Map(), player: this }; for (const soundId of soundIds) { try { // If it's a search query, get the first result if (soundId.includes(' ') || soundId.length > 20) { const results = await this.search(soundId, 1); if (results.length > 0) { soundboard.sounds.set(soundId, results[0].id); } } else { soundboard.sounds.set(soundId, soundId); } } catch (error) { this._log(`Failed to add sound to soundboard: ${soundId}`); } } // Add convenience methods soundboard.play = (key) => { const soundId = soundboard.sounds.get(key); if (soundId) { return this.play(soundId); } throw new Error(`Sound not found in soundboard: ${key}`); }; soundboard.playRandom = () => { const keys = Array.from(soundboard.sounds.keys()); const randomKey = keys[Math.floor(Math.random() * keys.length)]; return soundboard.play(randomKey); }; soundboard.list = () => Array.from(soundboard.sounds.keys()); this._log(`Soundboard created with ${soundboard.sounds.size} sounds`); return soundboard; } } // Export for different module systems if (typeof module !== 'undefined' && module.exports) { module.exports = SoundEffectPlayer; } else if (typeof define === 'function' && define.amd) { define([], () => SoundEffectPlayer); } else { window.SoundEffectPlayer = SoundEffectPlayer; } // Also export as default for ES6 modules if (typeof exports !== 'undefined') { exports.default = SoundEffectPlayer; }