global player
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import { BlurView } from "expo-blur";
|
||||
import React, { useMemo } from "react";
|
||||
import {
|
||||
Image,
|
||||
Platform,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
import { icons, img } from "../../assets";
|
||||
import usePlayer from "../../hooks/usePlayer";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
|
||||
const formatDuration = (ms) => {
|
||||
const totalSeconds = Math.max(0, Math.floor((ms || 0) / 1000));
|
||||
const minutes = Math.floor(totalSeconds / 60)
|
||||
.toString()
|
||||
.padStart(1, "0");
|
||||
const seconds = (totalSeconds % 60).toString().padStart(2, "0");
|
||||
return `${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
const GlobalAudioPlayer = () => {
|
||||
const { currentTrack, isPlaying, positionMs, durationMs, pause, resume } =
|
||||
usePlayer();
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const hasTrack = !!currentTrack?.source;
|
||||
if (!hasTrack) return null;
|
||||
|
||||
const title = currentTrack?.title || "Lecture en cours";
|
||||
const artist = currentTrack?.artist || "";
|
||||
const progress =
|
||||
durationMs > 0 ? Math.min(1, Math.max(0, positionMs / durationMs)) : 0;
|
||||
|
||||
const artworkSource = useMemo(() => {
|
||||
if (typeof currentTrack?.artwork === "number") return currentTrack.artwork;
|
||||
if (currentTrack?.artwork && typeof currentTrack.artwork === "string") {
|
||||
return { uri: currentTrack.artwork };
|
||||
}
|
||||
if (currentTrack?.coverUrl && typeof currentTrack.coverUrl === "string") {
|
||||
return { uri: currentTrack.coverUrl };
|
||||
}
|
||||
return img.placeholder;
|
||||
}, [currentTrack?.artwork, currentTrack?.coverUrl]);
|
||||
|
||||
const handleToggle = async () => {
|
||||
if (isPlaying) {
|
||||
await pause();
|
||||
} else {
|
||||
await resume();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View pointerEvents="box-none" style={styles.host}>
|
||||
<BlurView
|
||||
intensity={Platform.OS === "ios" ? 40 : 15}
|
||||
tint="dark"
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
paddingBottom: Math.max(insets.bottom, 12),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Image source={artworkSource} style={styles.cover} />
|
||||
<View style={styles.details}>
|
||||
<Text numberOfLines={1} style={styles.title}>
|
||||
{title}
|
||||
</Text>
|
||||
{!!artist && (
|
||||
<Text numberOfLines={1} style={styles.artist}>
|
||||
{artist}
|
||||
</Text>
|
||||
)}
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={styles.progressTrack}>
|
||||
<View
|
||||
style={[styles.progressFill, { width: `${progress * 100}%` }]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.time}>{formatDuration(positionMs)}</Text>
|
||||
<Text style={styles.time}>{formatDuration(durationMs)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.actions}>
|
||||
<Pressable
|
||||
onPress={handleToggle}
|
||||
hitSlop={12}
|
||||
style={styles.toggleButton}
|
||||
>
|
||||
<Image
|
||||
source={isPlaying ? icons.pause : icons.play}
|
||||
style={styles.toggleIcon}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</BlurView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
host: {
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
pointerEvents: "box-none",
|
||||
},
|
||||
container: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 12,
|
||||
borderRadius: 20,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
backgroundColor: "rgba(18, 11, 29, 0.85)",
|
||||
borderWidth: 1,
|
||||
borderColor: "#372152",
|
||||
},
|
||||
cover: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 12,
|
||||
backgroundColor: "#1f1829",
|
||||
},
|
||||
details: {
|
||||
flex: 1,
|
||||
gap: 6,
|
||||
},
|
||||
title: {
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 14,
|
||||
},
|
||||
artist: {
|
||||
color: "#C2B7D6",
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 12,
|
||||
},
|
||||
progressContainer: {
|
||||
gap: 6,
|
||||
},
|
||||
progressTrack: {
|
||||
height: 4,
|
||||
borderRadius: 8,
|
||||
backgroundColor: "rgba(255,255,255,0.1)",
|
||||
overflow: "hidden",
|
||||
},
|
||||
progressFill: {
|
||||
height: 4,
|
||||
backgroundColor: "#F94697",
|
||||
},
|
||||
timeContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
time: {
|
||||
color: "#C2B7D6",
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 10,
|
||||
},
|
||||
actions: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
toggleButton: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||
},
|
||||
toggleIcon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
tintColor: Palette.white,
|
||||
resizeMode: "contain",
|
||||
},
|
||||
});
|
||||
|
||||
export default GlobalAudioPlayer;
|
||||
Reference in New Issue
Block a user