+layout.svelte
Код: Выделить всё
import { page } from "$app/state";
import {
...
PlaybackOverlay
} from "my-ui-framework";
...
import {
currentPlaying,
setCurrentPlaying,
setCurrentPlayingCurrentTime,
cycleRepeatType,
} from "$lib/stores/playback.ts";
...
let { children } = $props();
...
let currentPlayingAudio: HTMLAudioElement;
let needSeek = $state(false);
$effect(() => {
if (currentPlayingAudio && $currentPlaying.trackId !== "-1") {
if (needSeek) {
currentPlayingAudio.currentTime = $currentPlaying.currentTime;
needSeek = false;
}
if ($currentPlaying.isPaused) {
currentPlayingAudio.pause();
} else {
currentPlayingAudio
.play()
.catch((e) => console.warn("Failed to play track", e));
}
}
});
let lastCurrentPlayingTime = $state($currentPlaying.currentTime);
{@render children()}
{
setCurrentPlaying({
...$currentPlaying,
length: Math.floor(currentPlayingAudio?.duration || 0),
});
}}
ontimeupdate={() => {
if (!Math.floor(currentPlayingAudio?.duration)) return;
if ($currentPlaying.currentTime != lastCurrentPlayingTime) {
setCurrentPlayingCurrentTime(
Math.floor(currentPlayingAudio?.currentTime || 0),
);
}
lastCurrentPlayingTime = Math.floor(
currentPlayingAudio?.currentTime || 0,
);
}}
onended={() => {
console.log("Track ended");
}}
>
{#if $currentPlaying.trackId !== "-1"}
{/if}
{#if $playbackOverlay?.visible}
{
$playbackOverlay.visible = false;
}}
onShuffleIconClick={() => {
setCurrentPlaying({
...$currentPlaying,
shuffle: !$currentPlaying.shuffle,
});
}}
onPauseButtonClick={() => {
if ($currentPlaying.trackId !== "-1") {
setCurrentPlaying({
...$currentPlaying,
isPaused: !$currentPlaying.isPaused,
});
}
}}
onRepeatIconClick={() => {
cycleRepeatType();
}}
onSeek={(time) => {
needSeek = true;
setCurrentPlayingCurrentTime(time);
}}
toNext={() => {
console.log("toNext");
}}
>
{/if}
...
образно
Код: Выделить всё
import { writable } from "svelte/store";
interface CurrentPlaying {
isPaused: boolean,
trackId: string,
albumId: string,
name: string,
artist: string,
albumCover: string,
currentTime: number,
length: number,
shuffle: boolean,
repeatType: "none" | "list" | "single";
}
interface Track {
id: string,
name: string,
artist: string,
albumCover: string,
length: number
}
const defaultCurrentPlayingData: CurrentPlaying = {
isPaused: true,
trackId: "-1",
albumId: "-1",
name: "No Track is Now Playing",
artist: "No Artist is Now Listening to",
albumCover: "",
currentTime: 0,
length: 0,
shuffle: false,
repeatType: "none"
}
export const currentPlaying = writable(defaultCurrentPlayingData)
export const queue = writable([])
export const setCurrentPlaying = (currentPlayingData: CurrentPlaying) => {
currentPlaying.set(currentPlayingData);
}
export const setCurrentPlayingCurrentTime = (newCurrentTime: number) => {
currentPlaying.update((state) => {
return { ...state, currentTime: newCurrentTime };
});
}
const repeatTypes = ["none", "list", "single"];
let currentRepeatTypeIndex = 0;
currentPlaying.subscribe(value => {
currentRepeatTypeIndex = repeatTypes.indexOf(value.repeatType || "none");
});
export const cycleRepeatType = () => {
currentPlaying.update((state) => {
const nextRepeatType = repeatTypes[(currentRepeatTypeIndex + 1) % repeatTypes.length] as "none" | "list" | "single";
return { ...state, repeatType: nextRepeatType };
});
}
// let intervalId: ReturnType | null = null;
// const startProgress = () => {
// if (intervalId) clearInterval(intervalId);
// if (currentPlaying.subscribe(state => state.length)) {
// intervalId = setInterval(() => {
// currentPlaying.update((state) => {
// if (!state.isPaused && state.currentTime < state.length) {
// return { ...state, currentTime: state.currentTime + 1 };
// }
// return state;
// });
// }, 1000);
// }
// };
// const stopProgress = () => {
// if (intervalId) {
// clearInterval(intervalId);
// }
// };
// currentPlaying.subscribe((state) => {
// if (state.isPaused) {
// stopProgress();
// } else {
// startProgress();
// }
// })
my-ui-framework
my-ackackoverlay.svelteобразно
import "../styles/global.css";
import "../styles/playback-overlay.css";
...
interface Props {
display: boolean;
cover?: string;
title?: string;
artist?: string;
currentTime: number;
length?: number;
isPaused?: boolean;
shuffle?: boolean;
repeatType?: "none" | "list" | "single";
onCollapseIconClick?: () => void;
onPlaylistIconClick?: () => void;
onLikeIconClick?: () => void;
onOptionsIconClick?: () => void;
onShuffleIconClick?: () => void;
onPrevIconClick?: () => void;
onPauseButtonClick?: () => void;
onNextIconClick?: () => void;
onRepeatIconClick?: () => void;
onSeek?: (time: number) => void;
toNext?: () => void;
}
let {
display = false,
cover,
title,
artist,
currentTime,
length,
isPaused,
shuffle,
repeatType,
onCollapseIconClick,
onPlaylistIconClick,
onLikeIconClick,
onOptionsIconClick,
onShuffleIconClick,
onPrevIconClick,
onPauseButtonClick,
onNextIconClick,
onRepeatIconClick,
onSeek,
toNext,
}: Props = $props();
let formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60)
.toString()
.padStart(2, "0");
const secs = Math.floor(seconds % 60)
.toString()
.padStart(2, "0");
return `${mins}:${secs}`;
};
...
let isDragging = $state(false);
let localCurrentTime = $state(currentTime ?? 0);
let handleSliderInput = (event: Event) => {
const target = event.target as HTMLInputElement;
localCurrentTime = parseInt(target.value);
isDragging = true;
};
let handleSliderChange = (event: Event) => {
const target = event.target as HTMLInputElement;
const newTime = parseInt(target.value);
onSeek?.(newTime);
isDragging = false;
};
$effect(() => {
if (length && length > 0) {
if (currentTime >= length) {
toNext?.();
}
}
});
{#if display}
...
{formatTime(
isDragging ? localCurrentTime : (currentTime ?? 0),
)}
{formatTime(length ?? 0)}
...
{/if}
< /code>
Почему звук перезапускается после поиска, и как я могу его исправить, чтобы он возобновился из желаемой позиции без перезапуска? Любые предложения или советы будут высоко оценены.
Подробнее здесь: https://stackoverflow.com/questions/795 ... t-playback
Мобильная версия