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",
|
"listRepeat": "Liste wiederholen",
|
||||||
"singleRepeat": "Einzeln wiederholen",
|
"singleRepeat": "Einzeln wiederholen",
|
||||||
"shuffle": "Zufallswiedergabe",
|
"shuffle": "Zufallswiedergabe",
|
||||||
|
"playOnce": "Einmal abspielen",
|
||||||
"playbackSpeed": "Wiedergabegeschwindigkeit",
|
"playbackSpeed": "Wiedergabegeschwindigkeit",
|
||||||
"searchResult": "Suchergebnis",
|
"searchResult": "Suchergebnis",
|
||||||
"preparingBathDownload": "Batch-Download wird vorbereitet...",
|
"preparingBathDownload": "Batch-Download wird vorbereitet...",
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,7 @@
|
||||||
"listRepeat": "List Repeat",
|
"listRepeat": "List Repeat",
|
||||||
"singleRepeat": "Single Repeat",
|
"singleRepeat": "Single Repeat",
|
||||||
"shuffle": "Shuffle",
|
"shuffle": "Shuffle",
|
||||||
|
"playOnce": "Play Once",
|
||||||
"playbackSpeed": "Playback Speed",
|
"playbackSpeed": "Playback Speed",
|
||||||
"searchResult": "Search Results",
|
"searchResult": "Search Results",
|
||||||
"preparingBathDownload": "Preparing batch download...",
|
"preparingBathDownload": "Preparing batch download...",
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@
|
||||||
"listRepeat": "Repetir lista",
|
"listRepeat": "Repetir lista",
|
||||||
"singleRepeat": "Repetir uno",
|
"singleRepeat": "Repetir uno",
|
||||||
"shuffle": "Aleatorio",
|
"shuffle": "Aleatorio",
|
||||||
|
"playOnce": "Reproducir una vez",
|
||||||
"playbackSpeed": "Velocidad de reproducción",
|
"playbackSpeed": "Velocidad de reproducción",
|
||||||
"searchResult": "Resultados de búsqueda",
|
"searchResult": "Resultados de búsqueda",
|
||||||
"preparingBathDownload": "Preparando descarga por lotes...",
|
"preparingBathDownload": "Preparando descarga por lotes...",
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@
|
||||||
"listRepeat": "Répétition de liste",
|
"listRepeat": "Répétition de liste",
|
||||||
"singleRepeat": "Répétition unique",
|
"singleRepeat": "Répétition unique",
|
||||||
"shuffle": "Aléatoire",
|
"shuffle": "Aléatoire",
|
||||||
|
"playOnce": "Lecture unique",
|
||||||
"playbackSpeed": "Vitesse de lecture",
|
"playbackSpeed": "Vitesse de lecture",
|
||||||
"searchResult": "Résultats de recherche",
|
"searchResult": "Résultats de recherche",
|
||||||
"preparingBathDownload": "Préparation du téléchargement par lot...",
|
"preparingBathDownload": "Préparation du téléchargement par lot...",
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@
|
||||||
"listRepeat": "Ripetizione lista",
|
"listRepeat": "Ripetizione lista",
|
||||||
"singleRepeat": "Ripetizione singola",
|
"singleRepeat": "Ripetizione singola",
|
||||||
"shuffle": "Casuale",
|
"shuffle": "Casuale",
|
||||||
|
"playOnce": "Riproduci una volta",
|
||||||
"playbackSpeed": "Velocità riproduzione",
|
"playbackSpeed": "Velocità riproduzione",
|
||||||
"searchResult": "Risultati ricerca",
|
"searchResult": "Risultati ricerca",
|
||||||
"preparingBathDownload": "Preparazione download in lotti...",
|
"preparingBathDownload": "Preparazione download in lotti...",
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,7 @@
|
||||||
"listRepeat": "リストリピート",
|
"listRepeat": "リストリピート",
|
||||||
"singleRepeat": "シングルリピート",
|
"singleRepeat": "シングルリピート",
|
||||||
"shuffle": "シャッフル",
|
"shuffle": "シャッフル",
|
||||||
|
"playOnce": "一回再生",
|
||||||
"playbackSpeed": "再生速度",
|
"playbackSpeed": "再生速度",
|
||||||
"searchResult": "検索結果",
|
"searchResult": "検索結果",
|
||||||
"preparingBathDownload": "ダウンロード準備中...",
|
"preparingBathDownload": "ダウンロード準備中...",
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@
|
||||||
"listRepeat": "목록 반복",
|
"listRepeat": "목록 반복",
|
||||||
"singleRepeat": "한 곡 반복",
|
"singleRepeat": "한 곡 반복",
|
||||||
"shuffle": "무작위 재생",
|
"shuffle": "무작위 재생",
|
||||||
|
"playOnce": "한 번 재생",
|
||||||
"playbackSpeed": "재생 속도",
|
"playbackSpeed": "재생 속도",
|
||||||
"searchResult": "검색 결과",
|
"searchResult": "검색 결과",
|
||||||
"preparingBathDownload": "일괄 다운로드 준비 중...",
|
"preparingBathDownload": "일괄 다운로드 준비 중...",
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@
|
||||||
"listRepeat": "Repetir lista",
|
"listRepeat": "Repetir lista",
|
||||||
"singleRepeat": "Repetir única",
|
"singleRepeat": "Repetir única",
|
||||||
"shuffle": "Aleatório",
|
"shuffle": "Aleatório",
|
||||||
|
"playOnce": "Reproduzir uma vez",
|
||||||
"playbackSpeed": "Velocidade de reprodução",
|
"playbackSpeed": "Velocidade de reprodução",
|
||||||
"searchResult": "Resultados da busca",
|
"searchResult": "Resultados da busca",
|
||||||
"preparingBathDownload": "Preparando download em lote...",
|
"preparingBathDownload": "Preparando download em lote...",
|
||||||
|
|
|
||||||
|
|
@ -400,6 +400,7 @@
|
||||||
"listRepeat": "Повтор списка",
|
"listRepeat": "Повтор списка",
|
||||||
"singleRepeat": "Повтор трека",
|
"singleRepeat": "Повтор трека",
|
||||||
"shuffle": "Случайное воспроизведение",
|
"shuffle": "Случайное воспроизведение",
|
||||||
|
"playOnce": "Воспроизвести один раз",
|
||||||
"playbackSpeed": "Скорость воспроизведения",
|
"playbackSpeed": "Скорость воспроизведения",
|
||||||
"searchResult": "Результаты поиска",
|
"searchResult": "Результаты поиска",
|
||||||
"preparingBathDownload": "Подготовка пакетной загрузки...",
|
"preparingBathDownload": "Подготовка пакетной загрузки...",
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,7 @@
|
||||||
"listRepeat": "列表循环",
|
"listRepeat": "列表循环",
|
||||||
"singleRepeat": "单曲循环",
|
"singleRepeat": "单曲循环",
|
||||||
"shuffle": "随机播放",
|
"shuffle": "随机播放",
|
||||||
|
"playOnce": "单次播放",
|
||||||
"playbackSpeed": "播放速度",
|
"playbackSpeed": "播放速度",
|
||||||
"searchResult": "搜索结果",
|
"searchResult": "搜索结果",
|
||||||
"preparingBathDownload": "正在准备打包下载...",
|
"preparingBathDownload": "正在准备打包下载...",
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,7 @@
|
||||||
"listRepeat": "清單循環",
|
"listRepeat": "清單循環",
|
||||||
"singleRepeat": "單曲循環",
|
"singleRepeat": "單曲循環",
|
||||||
"shuffle": "隨機播放",
|
"shuffle": "隨機播放",
|
||||||
|
"playOnce": "單次播放",
|
||||||
"playbackSpeed": "播放速度",
|
"playbackSpeed": "播放速度",
|
||||||
"searchResult": "搜尋結果",
|
"searchResult": "搜尋結果",
|
||||||
"preparingBathDownload": "正在準備打包下載...",
|
"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,
|
list_repeat: 0,
|
||||||
single_repeat: 1,
|
single_repeat: 1,
|
||||||
shuffle: 2,
|
shuffle: 2,
|
||||||
|
play_once: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MusicPlayer = () => {
|
const MusicPlayer = () => {
|
||||||
|
|
@ -78,10 +79,9 @@ const MusicPlayer = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
playHistory.current.push(index ?? 0);
|
|
||||||
|
|
||||||
switch (loopMode) {
|
switch (loopMode) {
|
||||||
case LoopMode.list_repeat:
|
case LoopMode.list_repeat:
|
||||||
|
case LoopMode.play_once:
|
||||||
if (isNext) {
|
if (isNext) {
|
||||||
playIndex(((index ?? 0) + 1) % playerState?.files.length);
|
playIndex(((index ?? 0) + 1) % playerState?.files.length);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -93,6 +93,7 @@ const MusicPlayer = () => {
|
||||||
break;
|
break;
|
||||||
case LoopMode.shuffle:
|
case LoopMode.shuffle:
|
||||||
if (isNext) {
|
if (isNext) {
|
||||||
|
playHistory.current.push(index ?? 0);
|
||||||
const nextIndex = Math.floor(Math.random() * playerState?.files.length);
|
const nextIndex = Math.floor(Math.random() * playerState?.files.length);
|
||||||
playIndex(nextIndex);
|
playIndex(nextIndex);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -106,8 +107,10 @@ const MusicPlayer = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPlayEnded = useCallback(() => {
|
const onPlayEnded = useCallback(() => {
|
||||||
loopProceed(true);
|
if (loopMode !== LoopMode.play_once) {
|
||||||
}, []);
|
loopProceed(true);
|
||||||
|
}
|
||||||
|
}, [loopMode, loopProceed]);
|
||||||
|
|
||||||
const timeUpdate = useCallback(() => {
|
const timeUpdate = useCallback(() => {
|
||||||
setCurrent(Math.floor(audio.current?.currentTime || 0));
|
setCurrent(Math.floor(audio.current?.currentTime || 0));
|
||||||
|
|
@ -152,7 +155,7 @@ const MusicPlayer = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleLoopMode = useCallback(() => {
|
const toggleLoopMode = useCallback(() => {
|
||||||
setLoopMode((loopMode) => (loopMode + 1) % 3);
|
setLoopMode((loopMode) => (loopMode + 1) % 4);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setLoopModeHandler = useCallback((mode: number) => {
|
const setLoopModeHandler = useCallback((mode: number) => {
|
||||||
|
|
@ -168,12 +171,7 @@ const MusicPlayer = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<audio
|
<audio ref={audio} onPause={() => setPlaying(false)} onPlay={() => setPlaying(true)} onEnded={onPlayEnded} />
|
||||||
ref={audio}
|
|
||||||
onPause={() => setPlaying(false)}
|
|
||||||
onPlay={() => setPlaying(true)}
|
|
||||||
onEnded={() => loopProceed(true)}
|
|
||||||
/>
|
|
||||||
<Tooltip title={playingTooltip} enterDelay={0}>
|
<Tooltip title={playingTooltip} enterDelay={0}>
|
||||||
<IconButton ref={icon} onClick={onPlayerPopoverOpen} size="large">
|
<IconButton ref={icon} onClick={onPlayerPopoverOpen} size="large">
|
||||||
{playing ? <MusicNote2Play /> : <MusicNote2 />}
|
{playing ? <MusicNote2Play /> : <MusicNote2 />}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { MediaMetaElements } from "../../FileManager/Sidebar/MediaMetaCard.tsx";
|
||||||
import AppsList from "../../Icons/AppsList.tsx";
|
import AppsList from "../../Icons/AppsList.tsx";
|
||||||
import ArrowRepeatAll from "../../Icons/ArrowRepeatAll.tsx";
|
import ArrowRepeatAll from "../../Icons/ArrowRepeatAll.tsx";
|
||||||
import ArrowRepeatOne from "../../Icons/ArrowRepeatOne.tsx";
|
import ArrowRepeatOne from "../../Icons/ArrowRepeatOne.tsx";
|
||||||
|
import ArrowRepeatOff from "../../Icons/ArrowRepeatOff.tsx";
|
||||||
import ArrowShuffle from "../../Icons/ArrowShuffle.tsx";
|
import ArrowShuffle from "../../Icons/ArrowShuffle.tsx";
|
||||||
import MusicNote1 from "../../Icons/MusicNote1.tsx";
|
import MusicNote1 from "../../Icons/MusicNote1.tsx";
|
||||||
import { LoopMode } from "./MusicPlayer.tsx";
|
import { LoopMode } from "./MusicPlayer.tsx";
|
||||||
|
|
@ -396,6 +397,7 @@ export const PlayerPopup = ({
|
||||||
{loopMode == LoopMode.list_repeat && <ArrowRepeatAll fontSize={"medium"} htmlColor={mainIconColor} />}
|
{loopMode == LoopMode.list_repeat && <ArrowRepeatAll fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||||
{loopMode == LoopMode.single_repeat && <ArrowRepeatOne fontSize={"medium"} htmlColor={mainIconColor} />}
|
{loopMode == LoopMode.single_repeat && <ArrowRepeatOne fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||||
{loopMode == LoopMode.shuffle && <ArrowShuffle fontSize={"medium"} htmlColor={mainIconColor} />}
|
{loopMode == LoopMode.shuffle && <ArrowShuffle fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||||
|
{loopMode == LoopMode.play_once && <ArrowRepeatOff fontSize={"medium"} htmlColor={mainIconColor} />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ArrowRepeatAll from "../../Icons/ArrowRepeatAll.tsx";
|
import ArrowRepeatAll from "../../Icons/ArrowRepeatAll.tsx";
|
||||||
import ArrowRepeatOne from "../../Icons/ArrowRepeatOne.tsx";
|
import ArrowRepeatOne from "../../Icons/ArrowRepeatOne.tsx";
|
||||||
|
import ArrowRepeatOff from "../../Icons/ArrowRepeatOff.tsx";
|
||||||
import ArrowShuffle from "../../Icons/ArrowShuffle.tsx";
|
import ArrowShuffle from "../../Icons/ArrowShuffle.tsx";
|
||||||
import { LoopMode } from "./MusicPlayer.tsx";
|
import { LoopMode } from "./MusicPlayer.tsx";
|
||||||
|
|
||||||
|
|
@ -39,6 +40,8 @@ export const RepeatModePopover = ({
|
||||||
return "single_repeat";
|
return "single_repeat";
|
||||||
case LoopMode.shuffle:
|
case LoopMode.shuffle:
|
||||||
return "shuffle";
|
return "shuffle";
|
||||||
|
case LoopMode.play_once:
|
||||||
|
return "play_once";
|
||||||
default:
|
default:
|
||||||
return "list_repeat";
|
return "list_repeat";
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +65,9 @@ export const RepeatModePopover = ({
|
||||||
case "shuffle":
|
case "shuffle":
|
||||||
newLoopMode = LoopMode.shuffle;
|
newLoopMode = LoopMode.shuffle;
|
||||||
break;
|
break;
|
||||||
|
case "play_once":
|
||||||
|
newLoopMode = LoopMode.play_once;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +121,10 @@ export const RepeatModePopover = ({
|
||||||
<ArrowShuffle fontSize="small" sx={{ mr: 1 }} />
|
<ArrowShuffle fontSize="small" sx={{ mr: 1 }} />
|
||||||
{t("fileManager.shuffle")}
|
{t("fileManager.shuffle")}
|
||||||
</NoWrapToggleButton>
|
</NoWrapToggleButton>
|
||||||
|
<NoWrapToggleButton value="play_once">
|
||||||
|
<ArrowRepeatOff fontSize="small" sx={{ mr: 1 }} />
|
||||||
|
{t("fileManager.playOnce")}
|
||||||
|
</NoWrapToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
|
|
||||||
<Divider sx={{ mb: 2 }} />
|
<Divider sx={{ mb: 2 }} />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue