216 lines
5.0 KiB
TypeScript
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);
|