UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

157 lines (156 loc) 5.89 kB
import { AudioPlayer } from './AudioPlayer'; import { StateStore } from 'stream-chat'; export class AudioPlayerPool { constructor(config) { this.state = new StateStore({ activeAudioPlayer: null, }); this.pool = new Map(); this.audios = new Map(); this.sharedAudio = null; this.sharedOwnerId = null; this.getOrAdd = (params) => { const { playbackRates, plugins, ...descriptor } = params; let player = this.pool.get(params.id); if (player) { if (!player.disposed) { player.setDescriptor(descriptor); return player; } this.deregister(params.id); } player = new AudioPlayer({ playbackRates, plugins, ...descriptor, pool: this, }); this.pool.set(params.id, player); return player; }; /** * In case of allowConcurrentPlayback enabled, a new Audio is created and assigned to the given audioPlayer owner. * In case of disabled concurrency, the shared audio ownership is transferred to the new owner loading the owner's * source. * * @param ownerId * @param src */ this.acquireElement = ({ ownerId, src }) => { if (!this.allowConcurrentPlayback) { // Single shared element mode if (!this.sharedAudio) { this.sharedAudio = new Audio(); } // Handoff from previous owner if different if (this.sharedOwnerId && this.sharedOwnerId !== ownerId) { const previous = this.pool.get(this.sharedOwnerId); // Ask previous to pause and drop ref, but keep player in pool previous?.pause(); previous?.releaseElementForHandoff(); } this.sharedOwnerId = ownerId; if (this.sharedAudio.src !== src) { // setting src starts loading; avoid explicit load() to prevent currentTime reset flicker this.sharedAudio.src = src; } return this.sharedAudio; } // Concurrent-per-owner mode let audio = this.audios.get(ownerId); if (!audio) { audio = new Audio(); this.audios.set(ownerId, audio); } if (audio.src !== src) { // setting src starts loading; avoid explicit load() here as well audio.src = src; } return audio; }; /** * Removes the given audio players ownership of the shared audio element (in case of concurrent playback is disabled) * and pauses the reproduction of the audio. * In case of concurrent playback mode (allowConcurrentPlayback enabled), the audio is paused, * its source cleared and removed from the audios pool readied for garbage collection. * * @param ownerId */ this.releaseElement = (ownerId) => { if (!this.allowConcurrentPlayback) { if (this.sharedOwnerId !== ownerId) return; const el = this.sharedAudio; if (el) { try { el.pause(); } catch { // ignore } el.removeAttribute('src'); el.load(); } // Keep shared element instance for reuse this.sharedOwnerId = null; return; } const el = this.audios.get(ownerId); if (!el) return; try { el.pause(); } catch { // ignore } el.removeAttribute('src'); el.load(); this.audios.delete(ownerId); }; /** Sets active audio player when allowConcurrentPlayback is disabled */ this.setActiveAudioPlayer = (activeAudioPlayer) => { if (this.allowConcurrentPlayback) return; this.state.partialNext({ activeAudioPlayer }); }; /** Performs all the necessary cleanup actions and removes the player from the pool */ this.remove = (id) => { const player = this.pool.get(id); if (!player) return; player.requestRemoval(); }; /** Removes and cleans up all the players from the pool */ this.clear = () => { this.players.forEach((player) => { this.remove(player.id); }); }; this.registerSubscriptions = () => { // Only register subscriptions for players that have an attached element. // Avoid creating elements or cross-wiring listeners on the shared element in single-playback mode. this.players.forEach((p) => { if (p.elementRef) { p.registerSubscriptions(); } }); }; this.allowConcurrentPlayback = !!config?.allowConcurrentPlayback; } get players() { return Array.from(this.pool.values()); } get activeAudioPlayer() { return this.state.getLatestValue().activeAudioPlayer; } /** Removes the AudioPlayer instance from the pool of players */ deregister(id) { if (this.pool.has(id)) { this.pool.delete(id); } if (this.activeAudioPlayer?.id === id) { this.setActiveAudioPlayer(null); } } }