jeffsi-meet/react/features/shared-music/components/web/DirectAudioManager.tsx

216 lines
5.0 KiB
TypeScript

import React from 'react';
import { connect } from 'react-redux';
import { PLAYBACK_STATUSES } from '../../constants';
import logger from '../../logger';
import AbstractMusicManager, {
IProps,
_mapDispatchToProps,
_mapStateToProps
} from './AbstractMusicManager';
/**
* Manager for direct audio URL playback using HTML5 audio element.
*/
class DirectAudioManager extends AbstractMusicManager {
audioRef: React.RefObject<HTMLAudioElement>;
_autoplayBlocked: boolean;
/**
* Initializes a new DirectAudioManager instance.
*
* @param {Object} props - This component's props.
* @returns {void}
*/
constructor(props: IProps) {
super(props);
this.audioRef = React.createRef();
this._autoplayBlocked = false;
}
/**
* Retrieves the current audio element ref.
*
* @returns {HTMLAudioElement | null}
*/
get player() {
return this.audioRef.current;
}
/**
* Indicates the playback state of the music.
*
* @returns {string}
*/
override getPlaybackStatus() {
let status;
if (!this.player) {
return;
}
if (this.player.paused) {
status = PLAYBACK_STATUSES.PAUSED;
} else {
status = PLAYBACK_STATUSES.PLAYING;
}
return status;
}
/**
* Indicates whether the music is muted.
*
* @returns {boolean}
*/
override isMuted() {
return this.player?.muted;
}
/**
* Retrieves current volume.
*
* @returns {number}
*/
override getVolume() {
return Number(this.player?.volume);
}
/**
* Retrieves current time.
*
* @returns {number}
*/
override getTime() {
return Number(this.player?.currentTime);
}
/**
* Seeks music to provided time.
*
* @param {number} time - The time to seek to.
* @returns {void}
*/
override seek(time: number) {
if (this.player) {
this.player.currentTime = time;
}
}
/**
* Plays music. Handles autoplay restrictions on mobile browsers.
*
* @returns {Promise<void> | undefined}
*/
override play() {
const playPromise = this.player?.play();
if (playPromise !== undefined) {
playPromise.catch((error: Error) => {
if (error.name === 'NotAllowedError') {
logger.warn('Autoplay blocked by browser. Music will play on user interaction.');
this._autoplayBlocked = true;
// Add a one-time click listener to resume playback
const resumePlayback = () => {
if (this._autoplayBlocked && this.player) {
this.player.play().catch(() => {
// Ignore if still blocked
});
this._autoplayBlocked = false;
}
document.removeEventListener('click', resumePlayback);
document.removeEventListener('touchstart', resumePlayback);
};
document.addEventListener('click', resumePlayback, { once: true });
document.addEventListener('touchstart', resumePlayback, { once: true });
} else {
logger.error('Error playing music:', error);
}
});
}
return playPromise;
}
/**
* Pauses music.
*
* @returns {void}
*/
override pause() {
return this.player?.pause();
}
/**
* Mutes music.
*
* @returns {void}
*/
override mute() {
if (this.player) {
this.player.muted = true;
}
}
/**
* Unmutes music.
*
* @returns {void}
*/
override unMute() {
if (this.player) {
this.player.muted = false;
}
}
/**
* Retrieves audio element options.
*
* @returns {Object}
*/
getPlayerOptions() {
const { _isOwner, musicId } = this.props;
let options: any = {
autoPlay: true,
src: musicId,
controls: _isOwner,
onError: () => this.onError(),
onPlay: () => this.onPlay(),
onVolumeChange: () => this.onVolumeChange()
};
if (_isOwner) {
options = {
...options,
onPause: () => this.onPause(),
onTimeUpdate: this.throttledFireUpdateSharedMusicEvent
};
}
return options;
}
/**
* Implements React Component's render.
*
* @inheritdoc
*/
override render() {
return (
<audio
className = 'hide'
id = 'sharedMusicPlayer'
ref = { this.audioRef }
{ ...this.getPlayerOptions() } />
);
}
}
export default connect(_mapStateToProps, _mapDispatchToProps)(DirectAudioManager);