soundeffect-player
Version:
Lightweight JavaScript library for playing sound effects with SoundEffect.app integration
352 lines (301 loc) • 10.3 kB
JavaScript
/**
* 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;
}