mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
* feat(music player): add play once mode to stop after track ends (#2992) * fix(music player): only track play history in shuffle mode
This commit is contained in:
parent
8b91fca929
commit
e646919e6d
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "Liste wiederholen",
|
||||
"singleRepeat": "Einzeln wiederholen",
|
||||
"shuffle": "Zufallswiedergabe",
|
||||
"playOnce": "Einmal abspielen",
|
||||
"playbackSpeed": "Wiedergabegeschwindigkeit",
|
||||
"searchResult": "Suchergebnis",
|
||||
"preparingBathDownload": "Batch-Download wird vorbereitet...",
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@
|
|||
"listRepeat": "List Repeat",
|
||||
"singleRepeat": "Single Repeat",
|
||||
"shuffle": "Shuffle",
|
||||
"playOnce": "Play Once",
|
||||
"playbackSpeed": "Playback Speed",
|
||||
"searchResult": "Search Results",
|
||||
"preparingBathDownload": "Preparing batch download...",
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "Repetir lista",
|
||||
"singleRepeat": "Repetir uno",
|
||||
"shuffle": "Aleatorio",
|
||||
"playOnce": "Reproducir una vez",
|
||||
"playbackSpeed": "Velocidad de reproducción",
|
||||
"searchResult": "Resultados de búsqueda",
|
||||
"preparingBathDownload": "Preparando descarga por lotes...",
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "Répétition de liste",
|
||||
"singleRepeat": "Répétition unique",
|
||||
"shuffle": "Aléatoire",
|
||||
"playOnce": "Lecture unique",
|
||||
"playbackSpeed": "Vitesse de lecture",
|
||||
"searchResult": "Résultats de recherche",
|
||||
"preparingBathDownload": "Préparation du téléchargement par lot...",
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "Ripetizione lista",
|
||||
"singleRepeat": "Ripetizione singola",
|
||||
"shuffle": "Casuale",
|
||||
"playOnce": "Riproduci una volta",
|
||||
"playbackSpeed": "Velocità riproduzione",
|
||||
"searchResult": "Risultati ricerca",
|
||||
"preparingBathDownload": "Preparazione download in lotti...",
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@
|
|||
"listRepeat": "リストリピート",
|
||||
"singleRepeat": "シングルリピート",
|
||||
"shuffle": "シャッフル",
|
||||
"playOnce": "一回再生",
|
||||
"playbackSpeed": "再生速度",
|
||||
"searchResult": "検索結果",
|
||||
"preparingBathDownload": "ダウンロード準備中...",
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "목록 반복",
|
||||
"singleRepeat": "한 곡 반복",
|
||||
"shuffle": "무작위 재생",
|
||||
"playOnce": "한 번 재생",
|
||||
"playbackSpeed": "재생 속도",
|
||||
"searchResult": "검색 결과",
|
||||
"preparingBathDownload": "일괄 다운로드 준비 중...",
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "Repetir lista",
|
||||
"singleRepeat": "Repetir única",
|
||||
"shuffle": "Aleatório",
|
||||
"playOnce": "Reproduzir uma vez",
|
||||
"playbackSpeed": "Velocidade de reprodução",
|
||||
"searchResult": "Resultados da busca",
|
||||
"preparingBathDownload": "Preparando download em lote...",
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@
|
|||
"listRepeat": "Повтор списка",
|
||||
"singleRepeat": "Повтор трека",
|
||||
"shuffle": "Случайное воспроизведение",
|
||||
"playOnce": "Воспроизвести один раз",
|
||||
"playbackSpeed": "Скорость воспроизведения",
|
||||
"searchResult": "Результаты поиска",
|
||||
"preparingBathDownload": "Подготовка пакетной загрузки...",
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@
|
|||
"listRepeat": "列表循环",
|
||||
"singleRepeat": "单曲循环",
|
||||
"shuffle": "随机播放",
|
||||
"playOnce": "单次播放",
|
||||
"playbackSpeed": "播放速度",
|
||||
"searchResult": "搜索结果",
|
||||
"preparingBathDownload": "正在准备打包下载...",
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@
|
|||
"listRepeat": "清單循環",
|
||||
"singleRepeat": "單曲循環",
|
||||
"shuffle": "隨機播放",
|
||||
"playOnce": "單次播放",
|
||||
"playbackSpeed": "播放速度",
|
||||
"searchResult": "搜尋結果",
|
||||
"preparingBathDownload": "正在準備打包下載...",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { SvgIcon, SvgIconProps } from "@mui/material";
|
||||
|
||||
export default function ArrowRepeatOff(props: SvgIconProps) {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<path d="m14.712 2.289l-.087-.078a1 1 0 0 0-1.327.078l-.078.087a.999.999 0 0 0 .078 1.326l1.299 1.297H8.999l-.24.004A6.997 6.997 0 0 0 2 11.993a6.94 6.94 0 0 0 1.189 3.899a.999.999 0 0 0 1.626-1.163l-.135-.218A4.997 4.997 0 0 1 9 6.998h5.595l-1.297 1.297l-.078.087a.999.999 0 0 0 1.492 1.326l3.006-3.003l.077-.087a.999.999 0 0 0-.078-1.326zm6.075 5.771A.999.999 0 0 0 19 8.677c0 .209.064.402.172.561a4.997 4.997 0 0 1-4.17 7.75H9.414l1.294-1.29l.083-.096a1 1 0 0 0-.006-1.23l-.077-.088l-.095-.084a1.001 1.001 0 0 0-1.232.006l-.088.078l-3.005 3.003l-.083.095a1 1 0 0 0 .006 1.231l.077.087l3.005 3.003l.095.084a1 1 0 0 0 1.397-1.41l-.077-.087l-1.304-1.303H15l.24-.003a6.997 6.997 0 0 0 5.546-10.927z" />
|
||||
<line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ export const LoopMode = {
|
|||
list_repeat: 0,
|
||||
single_repeat: 1,
|
||||
shuffle: 2,
|
||||
play_once: 3,
|
||||
};
|
||||
|
||||
const MusicPlayer = () => {
|
||||
|
|
@ -78,10 +79,9 @@ const MusicPlayer = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
playHistory.current.push(index ?? 0);
|
||||
|
||||
switch (loopMode) {
|
||||
case LoopMode.list_repeat:
|
||||
case LoopMode.play_once:
|
||||
if (isNext) {
|
||||
playIndex(((index ?? 0) + 1) % playerState?.files.length);
|
||||
} else {
|
||||
|
|
@ -93,6 +93,7 @@ const MusicPlayer = () => {
|
|||
break;
|
||||
case LoopMode.shuffle:
|
||||
if (isNext) {
|
||||
playHistory.current.push(index ?? 0);
|
||||
const nextIndex = Math.floor(Math.random() * playerState?.files.length);
|
||||
playIndex(nextIndex);
|
||||
} else {
|
||||
|
|
@ -106,8 +107,10 @@ const MusicPlayer = () => {
|
|||
);
|
||||
|
||||
const onPlayEnded = useCallback(() => {
|
||||
loopProceed(true);
|
||||
}, []);
|
||||
if (loopMode !== LoopMode.play_once) {
|
||||
loopProceed(true);
|
||||
}
|
||||
}, [loopMode, loopProceed]);
|
||||
|
||||
const timeUpdate = useCallback(() => {
|
||||
setCurrent(Math.floor(audio.current?.currentTime || 0));
|
||||
|
|
@ -152,7 +155,7 @@ const MusicPlayer = () => {
|
|||
}, []);
|
||||
|
||||
const toggleLoopMode = useCallback(() => {
|
||||
setLoopMode((loopMode) => (loopMode + 1) % 3);
|
||||
setLoopMode((loopMode) => (loopMode + 1) % 4);
|
||||
}, []);
|
||||
|
||||
const setLoopModeHandler = useCallback((mode: number) => {
|
||||
|
|
@ -168,12 +171,7 @@ const MusicPlayer = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<audio
|
||||
ref={audio}
|
||||
onPause={() => setPlaying(false)}
|
||||
onPlay={() => setPlaying(true)}
|
||||
onEnded={() => loopProceed(true)}
|
||||
/>
|
||||
<audio ref={audio} onPause={() => setPlaying(false)} onPlay={() => setPlaying(true)} onEnded={onPlayEnded} />
|
||||
<Tooltip title={playingTooltip} enterDelay={0}>
|
||||
<IconButton ref={icon} onClick={onPlayerPopoverOpen} size="large">
|
||||
{playing ? <MusicNote2Play /> : <MusicNote2 />}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { MediaMetaElements } from "../../FileManager/Sidebar/MediaMetaCard.tsx";
|
|||
import AppsList from "../../Icons/AppsList.tsx";
|
||||
import ArrowRepeatAll from "../../Icons/ArrowRepeatAll.tsx";
|
||||
import ArrowRepeatOne from "../../Icons/ArrowRepeatOne.tsx";
|
||||
import ArrowRepeatOff from "../../Icons/ArrowRepeatOff.tsx";
|
||||
import ArrowShuffle from "../../Icons/ArrowShuffle.tsx";
|
||||
import MusicNote1 from "../../Icons/MusicNote1.tsx";
|
||||
import { LoopMode } from "./MusicPlayer.tsx";
|
||||
|
|
@ -396,6 +397,7 @@ export const PlayerPopup = ({
|
|||
{loopMode == LoopMode.list_repeat && <ArrowRepeatAll fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||
{loopMode == LoopMode.single_repeat && <ArrowRepeatOne fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||
{loopMode == LoopMode.shuffle && <ArrowShuffle fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||
{loopMode == LoopMode.play_once && <ArrowRepeatOff fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useMemo } from "react";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import ArrowRepeatAll from "../../Icons/ArrowRepeatAll.tsx";
|
||||
import ArrowRepeatOne from "../../Icons/ArrowRepeatOne.tsx";
|
||||
import ArrowRepeatOff from "../../Icons/ArrowRepeatOff.tsx";
|
||||
import ArrowShuffle from "../../Icons/ArrowShuffle.tsx";
|
||||
import { LoopMode } from "./MusicPlayer.tsx";
|
||||
|
||||
|
|
@ -39,6 +40,8 @@ export const RepeatModePopover = ({
|
|||
return "single_repeat";
|
||||
case LoopMode.shuffle:
|
||||
return "shuffle";
|
||||
case LoopMode.play_once:
|
||||
return "play_once";
|
||||
default:
|
||||
return "list_repeat";
|
||||
}
|
||||
|
|
@ -62,6 +65,9 @@ export const RepeatModePopover = ({
|
|||
case "shuffle":
|
||||
newLoopMode = LoopMode.shuffle;
|
||||
break;
|
||||
case "play_once":
|
||||
newLoopMode = LoopMode.play_once;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
|
@ -115,6 +121,10 @@ export const RepeatModePopover = ({
|
|||
<ArrowShuffle fontSize="small" sx={{ mr: 1 }} />
|
||||
{t("fileManager.shuffle")}
|
||||
</NoWrapToggleButton>
|
||||
<NoWrapToggleButton value="play_once">
|
||||
<ArrowRepeatOff fontSize="small" sx={{ mr: 1 }} />
|
||||
{t("fileManager.playOnce")}
|
||||
</NoWrapToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
|
|
|||
Loading…
Reference in New Issue