@story-telling-reporter/react-scroll-to-audio
Version:
## Create Embed Code See [@story-telling-reporter/react-embed-code-generator](https://github.com/nickhsine/story-telling-reporter/blob/main/packages/embed-code-generator/README.md) for more information.
311 lines (300 loc) • 12.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ScrollToAudio = ScrollToAudio;
exports.buildBottomEntryPointStaticMarkup = buildBottomEntryPointStaticMarkup;
var _react = _interopRequireWildcard(require("react"));
var _reactDom = require("react-dom");
var _styledComponents = _interopRequireDefault(require("styled-components"));
var _icons = require("./icons");
var _debounce = _interopRequireDefault(require("lodash/debounce"));
var _reactUiToolkit = require("@story-telling-reporter/react-ui-toolkit");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const {
Hint
} = _reactUiToolkit.twreporter;
const _ = {
debounce: _debounce.default
};
function ScrollToAudio({
id = 'scroll-to-audio-id',
audioUrls,
className,
preload = 'auto',
hintOnly = false,
hintId
}) {
const audioRef = (0, _react.useRef)(null);
const [muted, setMuted] = _reactUiToolkit.hooks.useMuted(true, audioRef);
const topEntryPointRef = (0, _react.useRef)(null);
const bottomEntryPointRef = (0, _react.useRef)(null);
const [paused, setPaused] = (0, _react.useState)(true);
const [mounted, setMounted] = (0, _react.useState)(false);
const [hideMuteButton, setHideMuteButton] = (0, _react.useState)(true); // hide mute button initially
(0, _react.useEffect)(() => {
setMounted(true);
}, []);
(0, _react.useEffect)(() => {
if (hintOnly) {
return;
}
const handleScroll = _.debounce(() => {
const topEntryElement = topEntryPointRef.current;
const bottomEntryElement = bottomEntryPointRef.current;
if (!topEntryElement || topEntryElement.getBoundingClientRect().width == 0 || topEntryElement.getBoundingClientRect().height == 0) {
console.log(`[react-scroll-to-audio][${id}] \`topEntryElement\` is not available. Remove scroll event listener.`);
window.removeEventListener('scroll', handleScroll);
return;
}
const viewportHeight = window.innerHeight;
const rootMargin = Math.ceil(viewportHeight * 0.25);
const topEntryY = topEntryElement.getBoundingClientRect().y;
// top entry point is below viewport
// which means element is outside viewport bottom
if (topEntryY > viewportHeight - rootMargin) {
setHideMuteButton(true);
setPaused(true);
return;
}
let bottomEntryY = 0;
// bottom entry point is not existed
// give it a default value: top entry + 100vh
if (!bottomEntryElement) {
bottomEntryY = topEntryY + viewportHeight;
} else {
bottomEntryY = bottomEntryElement.getBoundingClientRect().y;
}
// bottom entry point is above viewport top,
// which means element is outside viewport
if (bottomEntryY < 0 + rootMargin) {
setHideMuteButton(true);
setPaused(true);
}
// top entry point is in the viewport or above viewport bottom
// AND
// bottom entry point is in the viewport or below viewport top
// which means element is inside viewport
if (topEntryY < viewportHeight - rootMargin && bottomEntryY > 0 + rootMargin) {
setHideMuteButton(false);
if (!muted) {
// do not play audio since it's muted
setPaused(false);
}
}
}, 50);
console.log(`[react-scroll-to-audio][${id}] add scroll event listener. \`muted\` state is ${muted}`);
window.addEventListener('scroll', handleScroll);
return () => {
console.log(`[react-scroll-to-audio][${id}] useEffect cleanup function. Remove scroll event listener.`);
window.removeEventListener('scroll', handleScroll);
};
}, [muted, hintOnly]);
// set audio muted attribute according to browser muted state
(0, _react.useEffect)(() => {
const audioElement = audioRef.current;
if (!audioElement) {
return;
}
audioElement.muted = muted;
}, [muted]);
(0, _react.useEffect)(() => {
const audioElement = audioRef.current;
if (!audioElement) {
return;
}
if (paused) {
audioElement.pause();
console.log(`[react-scroll-to-audio][${id}] audio paused.`);
} else {
const startPlayPromise = audioElement.play();
if (startPlayPromise !== undefined) {
startPlayPromise
// play successfully
.then(() => {
console.log(`[react-scroll-to-audio][${id}] audio plays successfully.`);
audioElement.setAttribute('data-played', 'true');
})
// fail to play
.catch(error => {
// browser prevent from playing audio before user interactions
console.log(`[react-scroll-to-audio][${id}] unable to play audio`);
console.log(`[react-scroll-to-audio][${id}] error: `, error);
// pause and mute audio since browser does not allow to play it
setPaused(true);
setMuted(true);
});
}
}
}, [paused]);
const onMuteButtonClick = () => {
const nextMuted = !muted;
setMuted(nextMuted);
// pause audio if muted, otherwise play the audio
setPaused(nextMuted);
};
const bottomEntryId = id + '-bottom-entry-point';
let buttonJsx = null;
let bottomEntryPlaceholder = null;
if (mounted) {
bottomEntryPlaceholder = document.getElementById(bottomEntryId);
const mobileButtonJsx = /*#__PURE__*/(0, _jsxRuntime.jsx)(MobileOnly, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MuteButtonWithMobileToolBar, {
className: "scroll-to-audio-muted-button",
$hide: hideMuteButton,
onClick: onMuteButtonClick,
children: muted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.MuteIcon, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SoundIcon, {})
})
});
const desktopButtonJsx = /*#__PURE__*/(0, _jsxRuntime.jsx)(DesktopOnly, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(FixedMuteButton, {
className: "scroll-to-audio-muted-button",
$hide: hideMuteButton,
onClick: onMuteButtonClick,
children: muted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.MuteIcon, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SoundIcon, {})
})
});
buttonJsx = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [mobileButtonJsx, desktopButtonJsx]
});
}
if (hintOnly) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Hint, {
id: hintId
});
}
const audioJsx = /*#__PURE__*/(0, _jsxRuntime.jsx)("audio", {
ref: audioRef,
preload: preload,
"data-played": false,
"data-paused": paused,
"data-twreporter-story-telling": true,
"data-muted": muted,
style: {
display: 'none'
},
playsInline: true,
loop: true,
children: audioUrls.map((url, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)("source", {
src: url
}, `audio_source_${index}`))
});
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(TopEntryContainer, {
"data-twreporter-story-telling": true,
"data-react-scroll-to-audio": true,
"data-id": `${id}-top-entry-point`,
"data-top-entry": true,
className: className,
ref: topEntryPointRef,
children: audioJsx
}), bottomEntryPlaceholder && /*#__PURE__*/(0, _reactDom.createPortal)( /*#__PURE__*/(0, _jsxRuntime.jsx)(BottomEntryContainer, {
"data-twreporter-story-telling": true,
"data-react-scroll-to-audio": true,
"data-id": bottomEntryId,
"data-bottom-entry": true,
ref: bottomEntryPointRef
}), bottomEntryPlaceholder), buttonJsx]
});
}
function buildBottomEntryPointStaticMarkup({
id = 'scroll-to-audio-id'
}) {
const bottomEntryId = id + '-bottom-entry-point';
return `<div id="${bottomEntryId}"></div>`;
}
const TopEntryContainer = _styledComponents.default.div.withConfig({
displayName: "src__TopEntryContainer",
componentId: "sc-1rtlo6l-0"
})(["min-height:10px;"]);
const BottomEntryContainer = _styledComponents.default.div.withConfig({
displayName: "src__BottomEntryContainer",
componentId: "sc-1rtlo6l-1"
})(["min-height:10px;"]);
const MuteButton = _styledComponents.default.div.withConfig({
displayName: "src__MuteButton",
componentId: "sc-1rtlo6l-2"
})(["height:40px;width:40px;border-radius:100%;background-color:#00000040;display:flex;cursor:pointer;> svg{width:20px;height:20px;margin:auto;fill:white;}&:hover{background-color:#00000080;}"]);
const MuteButtonWithMobileToolBar = (0, _styledComponents.default)(MuteButton).withConfig({
displayName: "src__MuteButtonWithMobileToolBar",
componentId: "sc-1rtlo6l-3"
})(["position:fixed;bottom:calc(40px + 48px);left:calc(50vw + 50% - 40px - 16px);", " transition:transform 300ms ease-in-out;"], props => {
return props !== null && props !== void 0 && props.$hide ? `transform: translateY(150px);` // slide out the viewport
: `transform: translateY(0);`;
});
const FixedMuteButton = (0, _styledComponents.default)(MuteButton).withConfig({
displayName: "src__FixedMuteButton",
componentId: "sc-1rtlo6l-4"
})(["position:fixed;bottom:16px;right:16px;z-index:800;", " transition:transform 300ms ease-in-out;"], props => {
return props !== null && props !== void 0 && props.$hide ? 'transform: translateY(calc((40px + 16px) * 2));' // slide out the viewport
: 'transform: translateY(0);';
});
const MobileOnly = _styledComponents.default.div.withConfig({
displayName: "src__MobileOnly",
componentId: "sc-1rtlo6l-5"
})(["display:none;@media (max-width:1023px){display:block;}"]);
const DesktopOnly = _styledComponents.default.div.withConfig({
displayName: "src__DesktopOnly",
componentId: "sc-1rtlo6l-6"
})(["display:none;@media (min-width:1024px){display:block;}"]);
//function fadeOut(
// audioElement: HTMLVideoElement,
// targetVolume: number,
// duration: number
//) {
// // make sure volume is between 0 to 1
// targetVolume = Math.max(0, Math.min(1, targetVolume))
// // decrease volume step by step
// const volumeDecreaseStep =
// (audioElement.volume - targetVolume) / (duration / 100)
// const interval = setInterval(() => {
// if (audioElement.volume > targetVolume + volumeDecreaseStep) {
// audioElement.volume -= volumeDecreaseStep
// } else {
// // decrease to the target volume
// audioElement.volume = targetVolume
// audioElement.pause()
// console.log('[react-scroll-to-audio] audio paused.')
// clearInterval(interval)
// }
// }, 100)
//}
//function fadeIn(
// audioElement: HTMLVideoElement,
// targetVolume: number,
// duration: number
//) {
// // make sure volume is between 0 to 1
// targetVolume = Math.max(0, Math.min(1, targetVolume))
// // increase volume step by step
// const volumeIncreaseStep = targetVolume / (duration / 100)
// audioElement.volume = 0 // start audio with 0 volume
// // play the audio
// const startPlayPromise = audioElement.play()
// if (startPlayPromise !== undefined) {
// startPlayPromise
// // play successfully
// .then(() => {
// console.log('[react-scroll-to-audio] audio plays successfully.')
// audioElement.setAttribute('data-played', 'true')
// const interval = setInterval(() => {
// if (audioElement.volume < targetVolume - volumeIncreaseStep) {
// audioElement.volume += volumeIncreaseStep
// } else {
// // increase to the target volume
// audioElement.volume = targetVolume
// clearInterval(interval)
// }
// }, 100)
// })
// // fail to play
// .catch((error) => {
// // browser prevent from playing audio before user interactions
// console.log('[react-scroll-to-audio] unable to play audio')
// console.log('[react-scroll-to-audio] error: ', error)
// })
// }
//}