stream-chat-react
Version:
React components to create chat conversations or livestream style chat
157 lines (156 loc) • 5.89 kB
JavaScript
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);
}
}
}