mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-24 20:34:49 +08:00
feat: playbackrate menu
This commit is contained in:
@@ -152,3 +152,22 @@ div[data-media-provider] video {
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Vidstack Menu 自定义样式:背景黑色,文字白色 */
|
||||
.vds-menu-items {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.vds-menu-items .vds-radio {
|
||||
padding: 0rem 1rem; /* 减少单项内边距 */
|
||||
margin: 0; /* 清除默认外边距 */
|
||||
}
|
||||
|
||||
.vds-menu-items .vds-radio + .vds-radio {
|
||||
margin-top: 2px; /* 相邻项之间仅 2px 间隔 */
|
||||
}
|
||||
|
||||
.vds-menu-items .vds-radio-label {
|
||||
font-size: 0.875rem; /* 调小字号 */
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ import {
|
||||
isHLSProvider,
|
||||
MediaPlayer,
|
||||
MediaProvider,
|
||||
Menu,
|
||||
RadioGroup,
|
||||
SeekButton,
|
||||
} from '@vidstack/react';
|
||||
import {
|
||||
AirPlayIcon,
|
||||
CheckIcon,
|
||||
SeekBackward10Icon,
|
||||
SeekForward10Icon,
|
||||
} from '@vidstack/react/icons';
|
||||
@@ -1433,7 +1436,10 @@ function PlayPageClient() {
|
||||
>
|
||||
<AdBlockIcon enabled={blockAdEnabled} />
|
||||
</button>
|
||||
<PlaybackRateButton playerRef={playerRef} />
|
||||
<PlaybackRateButton
|
||||
playerRef={playerRef}
|
||||
playerContainerRef={playerContainerRef}
|
||||
/>
|
||||
{/* 自定义 AirPlay 按钮 */}
|
||||
<AirPlayButton className='vds-button'>
|
||||
<AirPlayIcon className='vds-icon' />
|
||||
@@ -1787,8 +1793,10 @@ function PlayPageClient() {
|
||||
|
||||
const PlaybackRateButton = ({
|
||||
playerRef,
|
||||
playerContainerRef,
|
||||
}: {
|
||||
playerRef: React.RefObject<any>;
|
||||
playerContainerRef: React.RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
const [rate, setRate] = useState(1);
|
||||
const rates = [0.75, 1.0, 1.25, 1.5, 2.0, 3.0];
|
||||
@@ -1803,18 +1811,38 @@ const PlaybackRateButton = ({
|
||||
return unsubscribe;
|
||||
}, [playerRef]);
|
||||
|
||||
const cycleRate = () => {
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
const currentIndex = rates.indexOf(rate);
|
||||
const nextIndex = (currentIndex + 1) % rates.length;
|
||||
player.playbackRate = rates[nextIndex];
|
||||
};
|
||||
|
||||
return (
|
||||
<button className='vds-button' onClick={cycleRate}>
|
||||
{rate === 1 ? '倍速' : `${rate.toFixed(2)}x`}
|
||||
</button>
|
||||
<Menu.Root className='vds-menu'>
|
||||
<Menu.Button className='vds-menu-button vds-button' aria-label='Settings'>
|
||||
<span>倍速</span>
|
||||
</Menu.Button>
|
||||
<Menu.Items className='vds-menu-items' placement='top' offset={0}>
|
||||
<RadioGroup.Root
|
||||
className='vds-radio-group'
|
||||
aria-label='Custom Options'
|
||||
value={rate.toString()}
|
||||
onChange={(value) => {
|
||||
const player = playerRef.current;
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
player.playbackRate = Number(value);
|
||||
playerContainerRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
{rates.reverse().map((rate) => (
|
||||
<RadioGroup.Item
|
||||
className='vds-radio'
|
||||
value={rate.toString()}
|
||||
key={rate}
|
||||
>
|
||||
<CheckIcon className='vds-icon' />
|
||||
<span className='vds-radio-label'>{rate}</span>
|
||||
</RadioGroup.Item>
|
||||
))}
|
||||
</RadioGroup.Root>
|
||||
</Menu.Items>
|
||||
</Menu.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user