diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 54fa87a..c299457 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { Slot, Stack, router, usePathname } from "expo-router"; +import { Stack, router, usePathname } from "expo-router"; import { QueryClientProvider } from "react-query"; import queryClient from "@/lib/queryClient"; import { View } from "react-native"; @@ -12,6 +12,9 @@ import { useStore } from "zustand"; import authStore from "@/stores/authStore"; import { toastStore } from "@/stores/toastStore"; import Dialog from "@ui/Dialog"; +import AudioPlayerProvider from "@/components/containers/AudioPlayerProvider"; +import AudioPlayer from "@/components/pages/music/AudioPlayer"; +import MiniPlayer from "@/components/pages/music/MiniPlayer"; const RootLayout = () => { const insets = useSafeAreaInsets(); @@ -40,13 +43,15 @@ const RootLayout = () => { + + { @@ -54,6 +59,7 @@ const RootLayout = () => { }} /> + ); }; diff --git a/src/app/apps/files.tsx b/src/app/apps/files.tsx index db19095..3907a28 100644 --- a/src/app/apps/files.tsx +++ b/src/app/apps/files.tsx @@ -19,6 +19,8 @@ import Head from "@/components/utility/Head"; import Container from "@ui/Container"; import FileUpload from "@/components/pages/files/FileUpload"; import ActionButton from "@/components/pages/files/ActionButton"; +import { getFileType } from "@/lib/utils"; +import { audioPlayer } from "@/stores/audioPlayerStore"; const FilesPage = () => { const { isLoggedIn } = useAuth(); @@ -105,12 +107,17 @@ const FilesPage = () => { { + onSelect={(file, idx) => { if (file.isDirectory) { - setParams({ path: file.path }); - } else { - setViewFile(file); + return setParams({ path: file.path }); } + + const fileType = getFileType(file.path); + if (fileType === "audio") { + return audioPlayer.play(data, idx); + } + + setViewFile(file); }} /> diff --git a/src/components/containers/AudioPlayer.tsx b/src/components/containers/AudioPlayer.tsx deleted file mode 100644 index a05f971..0000000 --- a/src/components/containers/AudioPlayer.tsx +++ /dev/null @@ -1,374 +0,0 @@ -import { AVPlaybackStatusSuccess, Audio } from "expo-av"; -import { forwardRef, useEffect, useMemo, useRef, useState } from "react"; -import jsmediatags from "jsmediatags/build2/jsmediatags"; -import { MediaTags } from "@/types/mediaTags"; -import Box from "../ui/Box"; -import Text from "../ui/Text"; -import { - base64encode, - cn, - encodeUrl, - getFileType, - getFilename, -} from "@/lib/utils"; -import { FlatList, Image, Pressable } from "react-native"; -import { useFilesContext } from "../pages/files/FilesContext"; -import { HStack } from "../ui/Stack"; -import Button from "../ui/Button"; -import { Ionicons } from "../ui/Icons"; -import { Slider } from "@miblanchard/react-native-slider"; -import bgImage from "@/assets/images/audioplayer-bg.jpeg"; -import { FileItem } from "@/types/files"; -import Input from "@ui/Input"; -import { useAsyncStorage } from "@/hooks/useAsyncStorage"; - -type Props = { - path: string; - uri: string; -}; - -const AudioPlayer = ({ path, uri }: Props) => { - const { files, setViewFile } = useFilesContext(); - const soundRef = useRef(null); - const [curFileIdx, setFileIdx] = useState(-1); - const [status, setStatus] = useState(null); - const [mediaTags, setMediaTags] = useState(null); - const [playback, setPlayback] = useAsyncStorage("playback", { - repeat: false, - shuffle: false, - }); - const filename = getFilename(decodeURIComponent(uri)); - - const playlist = useMemo(() => { - let items = files.filter((f) => getFileType(f.name) === "audio"); - - if (playback.shuffle) { - items = items.sort(() => Math.random() - 0.5); - } - - return items; - }, [files, playback.shuffle]); - - const playIdx = (idx: number) => { - if (!playlist.length || idx < 0) { - return; - } - - const file = playlist[idx % playlist.length]; - if (file) { - setViewFile(file); - } - }; - - const playNext = (increment = 1, startIdx?: number) => { - const curIdx = startIdx ?? curFileIdx; - if (!playlist.length || curIdx < 0) { - return; - } - playIdx(curIdx + increment); - }; - - useEffect(() => { - if (!playlist?.length) { - return; - } - - const fileIdx = playlist.findIndex((file) => path === file.path); - setFileIdx(fileIdx); - - const onNext = () => playNext(1, fileIdx); - - async function play() { - try { - const { sound } = await Audio.Sound.createAsync({ uri }); - soundRef.current = sound; - - sound.setIsLoopingAsync(playback.repeat); - sound.setOnPlaybackStatusUpdate((st: AVPlaybackStatusSuccess) => { - setStatus(st as any); - - if (st.didJustFinish) { - onNext(); - } - }); - - await sound.playAsync(); - - if (soundRef.current !== sound) { - await sound.unloadAsync(); - } - } catch (err) { - if (err instanceof DOMException) { - if (err.name === "NotSupportedError") { - setTimeout(onNext, 3000); - } - } - } - } - - function loadMediaTags() { - const tagsReader = new jsmediatags.Reader(uri + "&dl=true"); - setMediaTags(null); - - tagsReader.read({ - onSuccess: (result: any) => { - const mediaTagsResult = { ...result }; - - if (result?.tags?.picture) { - const { data, format } = result.tags.picture; - let base64String = ""; - for (let i = 0; i < data.length; i++) { - base64String += String.fromCharCode(data[i]); - } - mediaTagsResult.picture = `data:${format};base64,${base64encode( - base64String - )}`; - delete data?.tags?.picture; - } - - setMediaTags(mediaTagsResult); - }, - }); - } - - loadMediaTags(); - play(); - - return () => { - soundRef.current?.unloadAsync(); - soundRef.current = null; - }; - }, [uri, path, playlist]); - - useEffect(() => { - if (status?.isLoaded) { - soundRef.current?.setIsLoopingAsync(playback.repeat); - } - }, [playback.repeat, status?.isLoaded]); - - return ( - - - - - - - - {mediaTags?.picture ? ( - - ) : null} - - Now Playing - - {mediaTags?.tags?.title || filename} - - {mediaTags?.tags?.artist ? ( - - {mediaTags.tags.artist} - - ) : null} - - - { - if (!soundRef.current) { - return; - } - - if (!status?.isPlaying) { - await soundRef.current.playAsync(); - } - - const [progress] = value; - const pos = (progress / 100.0) * (status?.durationMillis || 0); - soundRef.current.setPositionAsync(pos); - }} - /> - - - {formatTime(status?.positionMillis || 0)} - - - {formatTime(status?.durationMillis || 0)} - - - - - -