improve player data display

This commit is contained in:
SrGooglo 2025-02-05 02:39:35 +00:00
parent ac93563a5e
commit 13ab074840
5 changed files with 240 additions and 168 deletions

View File

@ -30,7 +30,13 @@ const EventsHandlers = {
return app.cores.player.controls.mute("toggle")
},
"like": async (ctx) => {
await app.cores.player.toggleCurrentTrackLike(!ctx.track_manifest?.liked)
if (!ctx.track_manifest) {
return false
}
const track = app.cores.player.track()
return await track.manifest.serviceOperations.toggleItemFavourite("track", ctx.track_manifest._id)
},
}
@ -84,12 +90,6 @@ const Controls = (props) => {
onClick={() => handleAction("next")}
disabled={playerState.control_locked}
/>
{
app.isMobile && <LikeButton
onClick={() => handleAction("like")}
liked={playerState.track_manifest?.liked}
/>
}
{
!app.isMobile && <antd.Popover
content={React.createElement(
@ -113,6 +113,13 @@ const Controls = (props) => {
</button>
</antd.Popover>
}
{
app.isMobile && <LikeButton
liked={playerState.track_manifest?.serviceOperations.fetchLikeStatus}
onClick={() => handleAction("like")}
/>
}
</div>
}

View File

@ -6,12 +6,18 @@ import LikeButton from "@components/LikeButton"
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
import MusicModel from "@models/music"
const ExtraActions = (props) => {
const [playerState] = usePlayerStateContext()
const handleClickLike = async () => {
await MusicModel.toggleItemFavourite("track", playerState.track_manifest._id)
if (!playerState.track_manifest) {
console.error("Cannot like a track if nothing is playing")
return false
}
const track = app.cores.player.track()
await track.manifest.serviceOperations.toggleItemFavourite("track", playerState.track_manifest._id)
}
return <div className="extra_actions">
@ -24,8 +30,8 @@ const ExtraActions = (props) => {
}
{
!app.isMobile && <LikeButton
liked={playerState.track_manifest?.fetchLikeStatus}
!app.isMobile && playerState.track_manifest?._id && <LikeButton
liked={playerState.track_manifest?.serviceOperations.fetchLikeStatus}
onClick={handleClickLike}
/>
}

View File

@ -118,6 +118,9 @@ export default class SeekBar extends React.Component {
this.calculateTime()
this.updateAll()
},
"player.durationchange": () => {
this.calculateDuration()
},
}
tick = () => {

View File

@ -25,6 +25,32 @@ function isOverflown(parent, element) {
return elementRect.width > parentRect.width
}
const Indicators = (props) => {
const { track } = props
if (!track) {
return null
}
const indicators = []
if (track.metadata) {
if (track.metadata.lossless) {
indicators.push(<Icons.Lossless />)
}
}
if (indicators.length === 0) {
return null
}
return (
<div className="toolbar_player_indicators_wrapper">
<div className="toolbar_player_indicators">{indicators}</div>
</div>
)
}
const ServiceIndicator = (props) => {
if (!props.service) {
return null
@ -32,9 +58,11 @@ const ServiceIndicator = (props) => {
switch (props.service) {
case "tidal": {
return <div className="service_indicator">
return (
<div className="service_indicator">
<Icons.SiTidal />
</div>
)
}
default: {
return null
@ -47,11 +75,10 @@ const Player = (props) => {
const contentRef = React.useRef()
const titleRef = React.useRef()
const subtitleRef = React.useRef()
const [topActionsVisible, setTopActionsVisible] = React.useState(false)
const [titleOverflown, setTitleOverflown] = React.useState(false)
const [subtitleOverflown, setSubtitleOverflown] = React.useState(false)
const [coverAnalysis, setCoverAnalysis] = React.useState(null)
const handleOnMouseInteraction = (e) => {
if (e.type === "mouseenter") {
@ -61,65 +88,69 @@ const Player = (props) => {
}
}
const {
title,
album,
artistStr,
liked,
service,
lyrics_enabled,
cover_analysis,
cover,
} = playerState.track_manifest ?? {}
const { title, artistStr, service, cover_analysis, cover } =
playerState.track_manifest ?? {}
const playing = playerState.playback_status === "playing"
const stopped = playerState.playback_status === "stopped"
const titleText = (!playing && stopped) ? "Stopped" : (title ?? "Untitled")
const titleText = !playing && stopped ? "Stopped" : (title ?? "Untitled")
const subtitleText = ""
React.useEffect(() => {
const titleIsOverflown = isOverflown(contentRef.current, titleRef.current)
const titleIsOverflown = isOverflown(
contentRef.current,
titleRef.current,
)
setTitleOverflown(titleIsOverflown)
}, [title])
return <div
className={classnames(
"toolbar_player_wrapper",
{
"hover": topActionsVisible,
"minimized": playerState.minimized,
"cover_light": cover_analysis?.isLight,
React.useEffect(() => {
const trackInstance = app.cores.player.track()
if (playerState.track_manifest && trackInstance) {
if (
typeof trackInstance.manifest.analyzeCoverColor === "function"
) {
trackInstance.manifest
.analyzeCoverColor()
.then((analysis) => {
setCoverAnalysis(analysis)
})
.catch((err) => {
console.error("Failed to get cover analysis", err)
})
}
)}
}
}, [playerState.track_manifest])
return (
<div
className={classnames("toolbar_player_wrapper", {
hover: topActionsVisible,
minimized: playerState.minimized,
cover_light: coverAnalysis?.isLight,
})}
style={{
"--cover_averageValues": RGBStringToValues(cover_analysis?.rgb),
"--cover_isLight": cover_analysis?.isLight,
"--cover_averageValues": RGBStringToValues(
coverAnalysis?.rgb ?? "0,0,0",
),
"--cover_isLight": coverAnalysis?.isLight ?? false,
}}
onMouseEnter={handleOnMouseInteraction}
onMouseLeave={handleOnMouseInteraction}
>
<div
className={classnames(
"toolbar_player_top_actions",
<div className={classnames("toolbar_player_top_actions")}>
{!playerState.control_locked && (
<antd.Button icon={<Icons.MdCast />} shape="circle" />
)}
>
{
!playerState.control_locked && <antd.Button
icon={<Icons.MdCast />}
shape="circle"
/>
}
{
lyrics_enabled && <antd.Button
icon={<Icons.MdLyrics />}
<antd.Button
icon={<Icons.MdFullscreen />}
shape="circle"
onClick={() => app.location.push("/lyrics")}
/>
}
{/* <antd.Button
icon={<Icons.MdOfflineBolt />}
@ -133,56 +164,42 @@ const Player = (props) => {
onClick={() => app.cores.player.close()}
/>
</div>
<div
className={classnames(
"toolbar_player"
)}
>
<div className={classnames("toolbar_player")}>
<div
className="toolbar_cover_background"
style={{
backgroundImage: `url(${cover})`
backgroundImage: `url(${cover})`,
}}
/>
<div
className="toolbar_player_content"
ref={contentRef}
>
<div className="toolbar_player_content" ref={contentRef}>
<div className="toolbar_player_info">
<h1
ref={titleRef}
className={classnames(
"toolbar_player_info_title",
{
["overflown"]: titleOverflown
}
)}
className={classnames("toolbar_player_info_title", {
["overflown"]: titleOverflown,
})}
>
<ServiceIndicator
service={service}
/>
<ServiceIndicator service={service} />
{titleText}
</h1>
{
titleOverflown && <Marquee
gradientColor={RGBStringToValues(cover_analysis?.rgb)}
{titleOverflown && (
<Marquee
gradientColor={RGBStringToValues(
coverAnalysis?.rgb ?? "0,0,0",
)}
gradientWidth={20}
play={playerState.playback_status !== "stopped"}
>
<h1
className="toolbar_player_info_title"
>
<ServiceIndicator
service={service}
/>
<h1 className="toolbar_player_info_title">
<ServiceIndicator service={service} />
{titleText}
</h1>
</Marquee>
}
)}
<p className="toolbar_player_info_subtitle">
{artistStr ?? ""}
@ -201,9 +218,12 @@ const Player = (props) => {
<ExtraActions />
</div>
<Indicators track={playerState.track_manifest} />
</div>
</div>
</div>
)
}
export default Player

View File

@ -267,3 +267,39 @@
padding: 0;
}
}
.toolbar_player_indicators_wrapper {
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
.toolbar_player_indicators {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: fit-content;
padding: 7px 10px;
border-radius: 12px;
background-color: rgba(var(--layoutBackgroundColor), 0.7);
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
font-size: 1rem;
svg {
margin: 0 !important;
color: white
}
}
}