Merge pull request #851 from Stremio/feat/player-mobile-slide-volume

Player: Improve volume UX on mobile and tablets
This commit is contained in:
Timothy Z. 2025-03-24 10:20:21 +02:00 committed by GitHub
commit 1bd56e0f3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 9 deletions

View file

@ -31,14 +31,18 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl
const retainThumb = React.useCallback(() => {
window.addEventListener('blur', onBlur);
window.addEventListener('mouseup', onMouseUp);
window.addEventListener('touchend', onTouchEnd);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('touchmove', onTouchMove);
document.documentElement.className = classnames(document.documentElement.className, styles['active-slider-within']);
}, []);
const releaseThumb = React.useCallback(() => {
cancelThumbAnimation();
window.removeEventListener('blur', onBlur);
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('touchend', onTouchEnd);
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('touchmove', onTouchMove);
const classList = document.documentElement.className.split(' ');
const classIndex = classList.indexOf(styles['active-slider-within']);
if (classIndex !== -1) {
@ -85,6 +89,36 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl
retainThumb();
}, []);
const onTouchStart = React.useCallback((event) => {
const touch = event.touches[0];
const value = calculateValueForMouseX(touch.clientX);
if (typeof onSlideRef.current === 'function') {
onSlideRef.current(value);
}
retainThumb();
event.preventDefault();
}, []);
const onTouchMove = React.useCallback((event) => {
requestThumbAnimation(() => {
const touch = event.touches[0];
const value = calculateValueForMouseX(touch.clientX);
if (typeof onSlideRef.current === 'function') {
onSlideRef.current(value);
}
});
event.preventDefault();
}, []);
const onTouchEnd = React.useCallback((event) => {
const touch = event.changedTouches[0];
const value = calculateValueForMouseX(touch.clientX);
if (typeof onCompleteRef.current === 'function') {
onCompleteRef.current(value);
}
releaseThumb();
}, []);
React.useLayoutEffect(() => {
if (!routeFocused || disabled) {
releaseThumb();
@ -98,7 +132,7 @@ const Slider = ({ className, value, buffered, minimumValue, maximumValue, disabl
const thumbPosition = Math.max(0, Math.min(1, (valueRef.current - minimumValueRef.current) / (maximumValueRef.current - minimumValueRef.current)));
const bufferedPosition = Math.max(0, Math.min(1, (bufferedRef.current - minimumValueRef.current) / (maximumValueRef.current - minimumValueRef.current)));
return (
<div ref={sliderContainerRef} className={classnames(className, styles['slider-container'], { 'disabled': disabled })} onMouseDown={onMouseDown}>
<div ref={sliderContainerRef} className={classnames(className, styles['slider-container'], { 'disabled': disabled })} onMouseDown={onMouseDown} onTouchStart={onTouchStart}>
<div className={styles['layer']}>
<div className={classnames(styles['track'], { [styles['audio-boost']]: audioBoost })} />
</div>

View file

@ -9,7 +9,7 @@ const { useServices } = require('stremio/services');
const SeekBar = require('./SeekBar');
const VolumeSlider = require('./VolumeSlider');
const styles = require('./styles');
const { useBinaryState } = require('stremio/common');
const { useBinaryState, usePlatform } = require('stremio/common');
const { t } = require('i18next');
const ControlBar = ({
@ -40,9 +40,11 @@ const ControlBar = ({
onToggleSideDrawer,
onToggleOptionsMenu,
onToggleStatisticsMenu,
onTouchEnd,
...props
}) => {
const { chromecast } = useServices();
const platform = usePlatform();
const [chromecastServiceActive, setChromecastServiceActive] = React.useState(() => chromecast.active);
const [buttonsMenuOpen, , , toggleButtonsMenu] = useBinaryState(false);
const onSubtitlesButtonMouseDown = React.useCallback((event) => {
@ -103,7 +105,7 @@ const ControlBar = ({
};
}, []);
return (
<div {...props} className={classnames(className, styles['control-bar-container'])}>
<div {...props} onTouchStart={props.onMouseOver} onTouchMove={props.onMouseMove} onTouchEnd={onTouchEnd} className={classnames(className, styles['control-bar-container'])}>
<SeekBar
className={styles['seek-bar']}
time={time}
@ -135,12 +137,16 @@ const ControlBar = ({
}
/>
</Button>
<VolumeSlider
className={styles['volume-slider']}
volume={volume}
muted={muted}
onVolumeChangeRequested={onVolumeChangeRequested}
/>
{
!platform.isMobile ?
<VolumeSlider
className={styles['volume-slider']}
volume={volume}
muted={muted}
onVolumeChangeRequested={onVolumeChangeRequested}
/>
: null
}
<div className={styles['spacing']} />
<Button className={styles['control-bar-buttons-menu-button']} onClick={toggleButtonsMenu}>
<Icon className={styles['icon']} name={'more-vertical'} />
@ -206,6 +212,9 @@ ControlBar.propTypes = {
onToggleSideDrawer: PropTypes.func,
onToggleOptionsMenu: PropTypes.func,
onToggleStatisticsMenu: PropTypes.func,
onMouseOver: PropTypes.func,
onMouseMove: PropTypes.func,
onTouchEnd: PropTypes.func,
};
module.exports = ControlBar;

View file

@ -717,6 +717,7 @@ const Player = ({ urlParams, queryParams }) => {
onToggleSideDrawer={toggleSideDrawer}
onMouseMove={onBarMouseMove}
onMouseOver={onBarMouseMove}
onTouchEnd={onContainerMouseLeave}
/>
{
nextVideoPopupOpen ?