UNPKG

react-h5-audio-player

Version:

A customizable React audio player. Written in TypeScript. Mobile compatible. Keyboard friendly

591 lines (506 loc) 20.9 kB
import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized"; import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React, { Component, cloneElement, isValidElement, createRef } from 'react'; import { Icon } from '@iconify/react'; import playCircle from '@iconify/icons-mdi/play-circle'; import pauseCircle from '@iconify/icons-mdi/pause-circle'; import skipPrevious from '@iconify/icons-mdi/skip-previous'; import skipNext from '@iconify/icons-mdi/skip-next'; import fastForward from '@iconify/icons-mdi/fast-forward'; import rewind from '@iconify/icons-mdi/rewind'; import volumeHigh from '@iconify/icons-mdi/volume-high'; import volumeMute from '@iconify/icons-mdi/volume-mute'; import repeat from '@iconify/icons-mdi/repeat'; import repeatOff from '@iconify/icons-mdi/repeat-off'; import ProgressBar from './ProgressBar'; import CurrentTime from './CurrentTime'; import Duration from './Duration'; import VolumeBar from './VolumeBar'; import { RHAP_UI } from './constants'; import { throttle, getMainLayoutClassName, getDisplayTimeBySeconds } from './utils'; var H5AudioPlayer = function (_Component) { _inheritsLoose(H5AudioPlayer, _Component); function H5AudioPlayer() { var _this; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _Component.call.apply(_Component, [this].concat(args)) || this; _defineProperty(_assertThisInitialized(_this), "audio", createRef()); _defineProperty(_assertThisInitialized(_this), "progressBar", createRef()); _defineProperty(_assertThisInitialized(_this), "container", createRef()); _defineProperty(_assertThisInitialized(_this), "lastVolume", _this.props.volume); _defineProperty(_assertThisInitialized(_this), "listenTracker", void 0); _defineProperty(_assertThisInitialized(_this), "volumeAnimationTimer", void 0); _defineProperty(_assertThisInitialized(_this), "downloadProgressAnimationTimer", void 0); _defineProperty(_assertThisInitialized(_this), "togglePlay", function (e) { e.stopPropagation(); var audio = _this.audio.current; if (audio.paused && audio.src) { _this.playAudioPromise(); } else if (!audio.paused) { audio.pause(); } }); _defineProperty(_assertThisInitialized(_this), "playAudioPromise", function () { _this.audio.current.play().then(null).catch(function (err) { var onPlayError = _this.props.onPlayError; onPlayError && onPlayError(new Error(err)); }); }); _defineProperty(_assertThisInitialized(_this), "isPlaying", function () { var audio = _this.audio.current; if (!audio) return false; return !audio.paused && !audio.ended; }); _defineProperty(_assertThisInitialized(_this), "handlePlay", function (e) { _this.forceUpdate(); _this.props.onPlay && _this.props.onPlay(e); }); _defineProperty(_assertThisInitialized(_this), "handlePause", function (e) { if (!_this.audio) return; _this.forceUpdate(); _this.props.onPause && _this.props.onPause(e); }); _defineProperty(_assertThisInitialized(_this), "handleAbort", function (e) { _this.props.onAbort && _this.props.onAbort(e); }); _defineProperty(_assertThisInitialized(_this), "handleClickVolumeButton", function () { var audio = _this.audio.current; if (audio.volume > 0) { _this.lastVolume = audio.volume; audio.volume = 0; } else { audio.volume = _this.lastVolume; } }); _defineProperty(_assertThisInitialized(_this), "handleMuteChange", function () { _this.forceUpdate(); }); _defineProperty(_assertThisInitialized(_this), "handleClickLoopButton", function () { _this.audio.current.loop = !_this.audio.current.loop; _this.forceUpdate(); }); _defineProperty(_assertThisInitialized(_this), "handleClickRewind", function () { var _this$props = _this.props, progressJumpSteps = _this$props.progressJumpSteps, progressJumpStep = _this$props.progressJumpStep; var jumpStep = progressJumpSteps.backward || progressJumpStep; _this.setJumpTime(-jumpStep); }); _defineProperty(_assertThisInitialized(_this), "handleClickForward", function () { var _this$props2 = _this.props, progressJumpSteps = _this$props2.progressJumpSteps, progressJumpStep = _this$props2.progressJumpStep; var jumpStep = progressJumpSteps.forward || progressJumpStep; _this.setJumpTime(jumpStep); }); _defineProperty(_assertThisInitialized(_this), "setJumpTime", function (time) { var audio = _this.audio.current; var duration = audio.duration, prevTime = audio.currentTime; if (!isFinite(duration) || !isFinite(prevTime)) return; var currentTime = prevTime + time / 1000; if (currentTime < 0) { audio.currentTime = 0; currentTime = 0; } else if (currentTime > duration) { audio.currentTime = duration; currentTime = duration; } else { audio.currentTime = currentTime; } }); _defineProperty(_assertThisInitialized(_this), "setJumpVolume", function (volume) { var newVolume = _this.audio.current.volume + volume; if (newVolume < 0) newVolume = 0;else if (newVolume > 1) newVolume = 1; _this.audio.current.volume = newVolume; }); _defineProperty(_assertThisInitialized(_this), "handleKeyDown", function (e) { switch (e.keyCode) { case 32: if (e.target === _this.container.current || e.target === _this.progressBar.current) { e.preventDefault(); _this.togglePlay(e); } break; case 37: _this.handleClickRewind(); break; case 39: _this.handleClickForward(); break; case 38: e.preventDefault(); _this.setJumpVolume(_this.props.volumeJumpStep); break; case 40: e.preventDefault(); _this.setJumpVolume(-_this.props.volumeJumpStep); break; case 76: _this.handleClickLoopButton(); break; case 77: _this.handleClickVolumeButton(); break; } }); _defineProperty(_assertThisInitialized(_this), "renderUIModules", function (modules) { return modules.map(function (comp, i) { return _this.renderUIModule(comp, i); }); }); _defineProperty(_assertThisInitialized(_this), "renderUIModule", function (comp, key) { var _this$props3 = _this.props, defaultCurrentTime = _this$props3.defaultCurrentTime, progressUpdateInterval = _this$props3.progressUpdateInterval, showDownloadProgress = _this$props3.showDownloadProgress, showFilledProgress = _this$props3.showFilledProgress, showFilledVolume = _this$props3.showFilledVolume, defaultDuration = _this$props3.defaultDuration, customIcons = _this$props3.customIcons, showSkipControls = _this$props3.showSkipControls, onClickPrevious = _this$props3.onClickPrevious, onClickNext = _this$props3.onClickNext, showJumpControls = _this$props3.showJumpControls, customAdditionalControls = _this$props3.customAdditionalControls, customVolumeControls = _this$props3.customVolumeControls, muted = _this$props3.muted, timeFormat = _this$props3.timeFormat, volumeProp = _this$props3.volume, loopProp = _this$props3.loop, mse = _this$props3.mse; switch (comp) { case RHAP_UI.CURRENT_TIME: return React.createElement("div", { key: key, id: "rhap_current-time", className: "rhap_time rhap_current-time" }, React.createElement(CurrentTime, { audio: _this.audio.current, isLeftTime: false, defaultCurrentTime: defaultCurrentTime, timeFormat: timeFormat })); case RHAP_UI.CURRENT_LEFT_TIME: return React.createElement("div", { key: key, id: "rhap_current-left-time", className: "rhap_time rhap_current-left-time" }, React.createElement(CurrentTime, { audio: _this.audio.current, isLeftTime: true, defaultCurrentTime: defaultCurrentTime, timeFormat: timeFormat })); case RHAP_UI.PROGRESS_BAR: return React.createElement(ProgressBar, { key: key, ref: _this.progressBar, audio: _this.audio.current, progressUpdateInterval: progressUpdateInterval, showDownloadProgress: showDownloadProgress, showFilledProgress: showFilledProgress, onSeek: mse && mse.onSeek, srcDuration: mse && mse.srcDuration }); case RHAP_UI.DURATION: return React.createElement("div", { key: key, className: "rhap_time rhap_total-time" }, mse && mse.srcDuration ? getDisplayTimeBySeconds(mse.srcDuration, mse.srcDuration, _this.props.timeFormat) : React.createElement(Duration, { audio: _this.audio.current, defaultDuration: defaultDuration, timeFormat: timeFormat })); case RHAP_UI.ADDITIONAL_CONTROLS: return React.createElement("div", { key: key, className: "rhap_additional-controls" }, _this.renderUIModules(customAdditionalControls)); case RHAP_UI.MAIN_CONTROLS: { var isPlaying = _this.isPlaying(); var actionIcon; if (isPlaying) { actionIcon = customIcons.pause ? customIcons.pause : React.createElement(Icon, { icon: pauseCircle }); } else { actionIcon = customIcons.play ? customIcons.play : React.createElement(Icon, { icon: playCircle }); } return React.createElement("div", { key: key, className: "rhap_main-controls" }, showSkipControls && React.createElement("button", { "aria-label": "Previous", className: "rhap_button-clear rhap_main-controls-button rhap_skip-button", type: "button", onClick: onClickPrevious }, customIcons.previous ? customIcons.previous : React.createElement(Icon, { icon: skipPrevious })), showJumpControls && React.createElement("button", { "aria-label": "Rewind", className: "rhap_button-clear rhap_main-controls-button rhap_rewind-button", type: "button", onClick: _this.handleClickRewind }, customIcons.rewind ? customIcons.rewind : React.createElement(Icon, { icon: rewind })), React.createElement("button", { "aria-label": isPlaying ? 'Pause' : 'Play', className: "rhap_button-clear rhap_main-controls-button rhap_play-pause-button", type: "button", onClick: _this.togglePlay }, actionIcon), showJumpControls && React.createElement("button", { "aria-label": "Forward", className: "rhap_button-clear rhap_main-controls-button rhap_forward-button", type: "button", onClick: _this.handleClickForward }, customIcons.forward ? customIcons.forward : React.createElement(Icon, { icon: fastForward })), showSkipControls && React.createElement("button", { "aria-label": "Skip", className: "rhap_button-clear rhap_main-controls-button rhap_skip-button", type: "button", onClick: onClickNext }, customIcons.next ? customIcons.next : React.createElement(Icon, { icon: skipNext }))); } case RHAP_UI.VOLUME_CONTROLS: return React.createElement("div", { key: key, className: "rhap_volume-controls" }, _this.renderUIModules(customVolumeControls)); case RHAP_UI.LOOP: { var loop = _this.audio.current ? _this.audio.current.loop : loopProp; var loopIcon; if (loop) { loopIcon = customIcons.loop ? customIcons.loop : React.createElement(Icon, { icon: repeat }); } else { loopIcon = customIcons.loopOff ? customIcons.loopOff : React.createElement(Icon, { icon: repeatOff }); } return React.createElement("button", { key: key, "aria-label": loop ? 'Enable Loop' : 'Disable Loop', className: "rhap_button-clear rhap_repeat-button", type: "button", onClick: _this.handleClickLoopButton }, loopIcon); } case RHAP_UI.VOLUME: { var _ref = _this.audio.current || {}, _ref$volume = _ref.volume, volume = _ref$volume === void 0 ? muted ? 0 : volumeProp : _ref$volume; var volumeIcon; if (volume) { volumeIcon = customIcons.volume ? customIcons.volume : React.createElement(Icon, { icon: volumeHigh }); } else { volumeIcon = customIcons.volume ? customIcons.volumeMute : React.createElement(Icon, { icon: volumeMute }); } return React.createElement("div", { key: key, className: "rhap_volume-container" }, React.createElement("button", { "aria-label": volume ? 'Mute' : 'Unmute', onClick: _this.handleClickVolumeButton, type: "button", className: "rhap_button-clear rhap_volume-button" }, volumeIcon), React.createElement(VolumeBar, { audio: _this.audio.current, volume: volume, onMuteChange: _this.handleMuteChange, showFilledVolume: showFilledVolume })); } default: if (!isValidElement(comp)) { return null; } return comp.key ? comp : cloneElement(comp, { key: key }); } }); return _this; } var _proto = H5AudioPlayer.prototype; _proto.componentDidMount = function componentDidMount() { var _this2 = this; this.forceUpdate(); var audio = this.audio.current; if (this.props.muted) { audio.volume = 0; } else { audio.volume = this.lastVolume; } audio.addEventListener('error', function (e) { _this2.props.onError && _this2.props.onError(e); }); audio.addEventListener('canplay', function (e) { _this2.props.onCanPlay && _this2.props.onCanPlay(e); }); audio.addEventListener('canplaythrough', function (e) { _this2.props.onCanPlayThrough && _this2.props.onCanPlayThrough(e); }); audio.addEventListener('play', this.handlePlay); audio.addEventListener('abort', this.handleAbort); audio.addEventListener('ended', function (e) { _this2.props.onEnded && _this2.props.onEnded(e); }); audio.addEventListener('playing', function (e) { _this2.props.onPlaying && _this2.props.onPlaying(e); }); audio.addEventListener('seeking', function (e) { _this2.props.onSeeking && _this2.props.onSeeking(e); }); audio.addEventListener('seeked', function (e) { _this2.props.onSeeked && _this2.props.onSeeked(e); }); audio.addEventListener('waiting', function (e) { _this2.props.onWaiting && _this2.props.onWaiting(e); }); audio.addEventListener('emptied', function (e) { _this2.props.onEmptied && _this2.props.onEmptied(e); }); audio.addEventListener('stalled', function (e) { _this2.props.onStalled && _this2.props.onStalled(e); }); audio.addEventListener('suspend', function (e) { _this2.props.onSuspend && _this2.props.onSuspend(e); }); audio.addEventListener('loadstart', function (e) { _this2.props.onLoadStart && _this2.props.onLoadStart(e); }); audio.addEventListener('loadedmetadata', function (e) { _this2.props.onLoadedMetaData && _this2.props.onLoadedMetaData(e); }); audio.addEventListener('loadeddata', function (e) { _this2.props.onLoadedData && _this2.props.onLoadedData(e); }); audio.addEventListener('pause', this.handlePause); audio.addEventListener('timeupdate', throttle(function (e) { _this2.props.onListen && _this2.props.onListen(e); }, this.props.listenInterval)); audio.addEventListener('volumechange', function (e) { _this2.props.onVolumeChange && _this2.props.onVolumeChange(e); }); audio.addEventListener('encrypted', function (e) { var mse = _this2.props.mse; mse && mse.onEcrypted && mse.onEcrypted(e); }); }; _proto.componentDidUpdate = function componentDidUpdate(prevProps) { var _this$props4 = this.props, src = _this$props4.src, autoPlayAfterSrcChange = _this$props4.autoPlayAfterSrcChange; if (prevProps.src !== src) { if (autoPlayAfterSrcChange) { this.playAudioPromise(); } else { this.forceUpdate(); } } }; _proto.componentWillUnmount = function componentWillUnmount() { var audio = this.audio.current; if (audio) { audio.removeEventListener('play', this.handlePlay); audio.removeEventListener('pause', this.handlePause); audio.removeEventListener('abort', this.handleAbort); audio.removeAttribute('src'); audio.load(); } }; _proto.render = function render() { var _this$props5 = this.props, className = _this$props5.className, src = _this$props5.src, loopProp = _this$props5.loop, preload = _this$props5.preload, autoPlay = _this$props5.autoPlay, crossOrigin = _this$props5.crossOrigin, mediaGroup = _this$props5.mediaGroup, header = _this$props5.header, footer = _this$props5.footer, layout = _this$props5.layout, customProgressBarSection = _this$props5.customProgressBarSection, customControlsSection = _this$props5.customControlsSection, children = _this$props5.children, style = _this$props5.style; var loop = this.audio.current ? this.audio.current.loop : loopProp; return React.createElement("div", { role: "group", tabIndex: 0, "aria-label": "Audio Player", className: "rhap_container " + className, onKeyDown: this.handleKeyDown, ref: this.container, style: style }, React.createElement("audio", { src: src, controls: false, loop: loop, autoPlay: autoPlay, preload: preload, crossOrigin: crossOrigin, mediaGroup: mediaGroup, ref: this.audio }, children), header && React.createElement("div", { className: "rhap_header" }, header), React.createElement("div", { className: "rhap_main " + getMainLayoutClassName(layout) }, React.createElement("div", { className: "rhap_progress-section" }, this.renderUIModules(customProgressBarSection)), React.createElement("div", { className: "rhap_controls-section" }, this.renderUIModules(customControlsSection))), footer && React.createElement("div", { className: "rhap_footer" }, footer)); }; return H5AudioPlayer; }(Component); _defineProperty(H5AudioPlayer, "defaultProps", { autoPlay: false, autoPlayAfterSrcChange: true, listenInterval: 1000, progressJumpStep: 5000, progressJumpSteps: {}, volumeJumpStep: 0.1, loop: false, muted: false, preload: 'auto', progressUpdateInterval: 20, defaultCurrentTime: '--:--', defaultDuration: '--:--', timeFormat: 'auto', volume: 1, className: '', showJumpControls: true, showSkipControls: false, showDownloadProgress: true, showFilledProgress: true, showFilledVolume: false, customIcons: {}, customProgressBarSection: [RHAP_UI.CURRENT_TIME, RHAP_UI.PROGRESS_BAR, RHAP_UI.DURATION], customControlsSection: [RHAP_UI.ADDITIONAL_CONTROLS, RHAP_UI.MAIN_CONTROLS, RHAP_UI.VOLUME_CONTROLS], customAdditionalControls: [RHAP_UI.LOOP], customVolumeControls: [RHAP_UI.VOLUME], layout: 'stacked' }); export default H5AudioPlayer; export { RHAP_UI };