mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
improve player data display
This commit is contained in:
parent
ac93563a5e
commit
13ab074840
@ -30,7 +30,13 @@ const EventsHandlers = {
|
|||||||
return app.cores.player.controls.mute("toggle")
|
return app.cores.player.controls.mute("toggle")
|
||||||
},
|
},
|
||||||
"like": async (ctx) => {
|
"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")}
|
onClick={() => handleAction("next")}
|
||||||
disabled={playerState.control_locked}
|
disabled={playerState.control_locked}
|
||||||
/>
|
/>
|
||||||
{
|
|
||||||
app.isMobile && <LikeButton
|
|
||||||
onClick={() => handleAction("like")}
|
|
||||||
liked={playerState.track_manifest?.liked}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
!app.isMobile && <antd.Popover
|
!app.isMobile && <antd.Popover
|
||||||
content={React.createElement(
|
content={React.createElement(
|
||||||
@ -113,6 +113,13 @@ const Controls = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
</antd.Popover>
|
</antd.Popover>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
app.isMobile && <LikeButton
|
||||||
|
liked={playerState.track_manifest?.serviceOperations.fetchLikeStatus}
|
||||||
|
onClick={() => handleAction("like")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,12 +6,18 @@ import LikeButton from "@components/LikeButton"
|
|||||||
|
|
||||||
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
import { usePlayerStateContext } from "@contexts/WithPlayerContext"
|
||||||
|
|
||||||
import MusicModel from "@models/music"
|
|
||||||
|
|
||||||
const ExtraActions = (props) => {
|
const ExtraActions = (props) => {
|
||||||
const [playerState] = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const handleClickLike = async () => {
|
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">
|
return <div className="extra_actions">
|
||||||
@ -24,8 +30,8 @@ const ExtraActions = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!app.isMobile && <LikeButton
|
!app.isMobile && playerState.track_manifest?._id && <LikeButton
|
||||||
liked={playerState.track_manifest?.fetchLikeStatus}
|
liked={playerState.track_manifest?.serviceOperations.fetchLikeStatus}
|
||||||
onClick={handleClickLike}
|
onClick={handleClickLike}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,9 @@ export default class SeekBar extends React.Component {
|
|||||||
this.calculateTime()
|
this.calculateTime()
|
||||||
this.updateAll()
|
this.updateAll()
|
||||||
},
|
},
|
||||||
|
"player.durationchange": () => {
|
||||||
|
this.calculateDuration()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tick = () => {
|
tick = () => {
|
||||||
|
@ -15,195 +15,215 @@ import ExtraActions from "../ExtraActions"
|
|||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
function isOverflown(parent, element) {
|
function isOverflown(parent, element) {
|
||||||
if (!parent || !element) {
|
if (!parent || !element) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentRect = parent.getBoundingClientRect()
|
const parentRect = parent.getBoundingClientRect()
|
||||||
const elementRect = element.getBoundingClientRect()
|
const elementRect = element.getBoundingClientRect()
|
||||||
|
|
||||||
return elementRect.width > parentRect.width
|
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) => {
|
const ServiceIndicator = (props) => {
|
||||||
if (!props.service) {
|
if (!props.service) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (props.service) {
|
switch (props.service) {
|
||||||
case "tidal": {
|
case "tidal": {
|
||||||
return <div className="service_indicator">
|
return (
|
||||||
<Icons.SiTidal />
|
<div className="service_indicator">
|
||||||
</div>
|
<Icons.SiTidal />
|
||||||
}
|
</div>
|
||||||
default: {
|
)
|
||||||
return null
|
}
|
||||||
}
|
default: {
|
||||||
}
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Player = (props) => {
|
const Player = (props) => {
|
||||||
const [playerState] = usePlayerStateContext()
|
const [playerState] = usePlayerStateContext()
|
||||||
|
|
||||||
const contentRef = React.useRef()
|
const contentRef = React.useRef()
|
||||||
const titleRef = React.useRef()
|
const titleRef = React.useRef()
|
||||||
const subtitleRef = React.useRef()
|
|
||||||
|
|
||||||
const [topActionsVisible, setTopActionsVisible] = React.useState(false)
|
const [topActionsVisible, setTopActionsVisible] = React.useState(false)
|
||||||
const [titleOverflown, setTitleOverflown] = React.useState(false)
|
const [titleOverflown, setTitleOverflown] = React.useState(false)
|
||||||
const [subtitleOverflown, setSubtitleOverflown] = React.useState(false)
|
const [coverAnalysis, setCoverAnalysis] = React.useState(null)
|
||||||
|
|
||||||
const handleOnMouseInteraction = (e) => {
|
const handleOnMouseInteraction = (e) => {
|
||||||
if (e.type === "mouseenter") {
|
if (e.type === "mouseenter") {
|
||||||
setTopActionsVisible(true)
|
setTopActionsVisible(true)
|
||||||
} else {
|
} else {
|
||||||
setTopActionsVisible(false)
|
setTopActionsVisible(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { title, artistStr, service, cover_analysis, cover } =
|
||||||
title,
|
playerState.track_manifest ?? {}
|
||||||
album,
|
|
||||||
artistStr,
|
|
||||||
liked,
|
|
||||||
service,
|
|
||||||
lyrics_enabled,
|
|
||||||
cover_analysis,
|
|
||||||
cover,
|
|
||||||
} = playerState.track_manifest ?? {}
|
|
||||||
|
|
||||||
const playing = playerState.playback_status === "playing"
|
const playing = playerState.playback_status === "playing"
|
||||||
const stopped = playerState.playback_status === "stopped"
|
const stopped = playerState.playback_status === "stopped"
|
||||||
|
|
||||||
const titleText = (!playing && stopped) ? "Stopped" : (title ?? "Untitled")
|
const titleText = !playing && stopped ? "Stopped" : (title ?? "Untitled")
|
||||||
const subtitleText = ""
|
const subtitleText = ""
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const titleIsOverflown = isOverflown(contentRef.current, titleRef.current)
|
const titleIsOverflown = isOverflown(
|
||||||
|
contentRef.current,
|
||||||
|
titleRef.current,
|
||||||
|
)
|
||||||
|
|
||||||
setTitleOverflown(titleIsOverflown)
|
setTitleOverflown(titleIsOverflown)
|
||||||
}, [title])
|
}, [title])
|
||||||
|
|
||||||
return <div
|
React.useEffect(() => {
|
||||||
className={classnames(
|
const trackInstance = app.cores.player.track()
|
||||||
"toolbar_player_wrapper",
|
|
||||||
{
|
|
||||||
"hover": topActionsVisible,
|
|
||||||
"minimized": playerState.minimized,
|
|
||||||
"cover_light": cover_analysis?.isLight,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
"--cover_averageValues": RGBStringToValues(cover_analysis?.rgb),
|
|
||||||
"--cover_isLight": cover_analysis?.isLight,
|
|
||||||
}}
|
|
||||||
onMouseEnter={handleOnMouseInteraction}
|
|
||||||
onMouseLeave={handleOnMouseInteraction}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
"toolbar_player_top_actions",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
!playerState.control_locked && <antd.Button
|
|
||||||
icon={<Icons.MdCast />}
|
|
||||||
shape="circle"
|
|
||||||
|
|
||||||
/>
|
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 (
|
||||||
lyrics_enabled && <antd.Button
|
<div
|
||||||
icon={<Icons.MdLyrics />}
|
className={classnames("toolbar_player_wrapper", {
|
||||||
shape="circle"
|
hover: topActionsVisible,
|
||||||
onClick={() => app.location.push("/lyrics")}
|
minimized: playerState.minimized,
|
||||||
/>
|
cover_light: coverAnalysis?.isLight,
|
||||||
}
|
})}
|
||||||
|
style={{
|
||||||
|
"--cover_averageValues": RGBStringToValues(
|
||||||
|
coverAnalysis?.rgb ?? "0,0,0",
|
||||||
|
),
|
||||||
|
"--cover_isLight": coverAnalysis?.isLight ?? false,
|
||||||
|
}}
|
||||||
|
onMouseEnter={handleOnMouseInteraction}
|
||||||
|
onMouseLeave={handleOnMouseInteraction}
|
||||||
|
>
|
||||||
|
<div className={classnames("toolbar_player_top_actions")}>
|
||||||
|
{!playerState.control_locked && (
|
||||||
|
<antd.Button icon={<Icons.MdCast />} shape="circle" />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* <antd.Button
|
<antd.Button
|
||||||
|
icon={<Icons.MdFullscreen />}
|
||||||
|
shape="circle"
|
||||||
|
onClick={() => app.location.push("/lyrics")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <antd.Button
|
||||||
icon={<Icons.MdOfflineBolt />}
|
icon={<Icons.MdOfflineBolt />}
|
||||||
>
|
>
|
||||||
HyperDrive
|
HyperDrive
|
||||||
</antd.Button> */}
|
</antd.Button> */}
|
||||||
|
|
||||||
<antd.Button
|
<antd.Button
|
||||||
icon={<Icons.FiX />}
|
icon={<Icons.FiX />}
|
||||||
shape="circle"
|
shape="circle"
|
||||||
onClick={() => app.cores.player.close()}
|
onClick={() => app.cores.player.close()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className={classnames("toolbar_player")}>
|
||||||
className={classnames(
|
<div
|
||||||
"toolbar_player"
|
className="toolbar_cover_background"
|
||||||
)}
|
style={{
|
||||||
>
|
backgroundImage: `url(${cover})`,
|
||||||
<div
|
}}
|
||||||
className="toolbar_cover_background"
|
/>
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${cover})`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div className="toolbar_player_content" ref={contentRef}>
|
||||||
className="toolbar_player_content"
|
<div className="toolbar_player_info">
|
||||||
ref={contentRef}
|
<h1
|
||||||
>
|
ref={titleRef}
|
||||||
<div className="toolbar_player_info">
|
className={classnames("toolbar_player_info_title", {
|
||||||
<h1
|
["overflown"]: titleOverflown,
|
||||||
ref={titleRef}
|
})}
|
||||||
className={classnames(
|
>
|
||||||
"toolbar_player_info_title",
|
<ServiceIndicator service={service} />
|
||||||
{
|
|
||||||
["overflown"]: titleOverflown
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ServiceIndicator
|
|
||||||
service={service}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{titleText}
|
{titleText}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{
|
{titleOverflown && (
|
||||||
titleOverflown && <Marquee
|
<Marquee
|
||||||
gradientColor={RGBStringToValues(cover_analysis?.rgb)}
|
gradientColor={RGBStringToValues(
|
||||||
gradientWidth={20}
|
coverAnalysis?.rgb ?? "0,0,0",
|
||||||
play={playerState.playback_status !== "stopped"}
|
)}
|
||||||
>
|
gradientWidth={20}
|
||||||
<h1
|
play={playerState.playback_status !== "stopped"}
|
||||||
className="toolbar_player_info_title"
|
>
|
||||||
>
|
<h1 className="toolbar_player_info_title">
|
||||||
<ServiceIndicator
|
<ServiceIndicator service={service} />
|
||||||
service={service}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{titleText}
|
{titleText}
|
||||||
</h1>
|
</h1>
|
||||||
</Marquee>
|
</Marquee>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<p className="toolbar_player_info_subtitle">
|
<p className="toolbar_player_info_subtitle">
|
||||||
{artistStr ?? ""}
|
{artistStr ?? ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="toolbar_player_actions">
|
<div className="toolbar_player_actions">
|
||||||
<Controls />
|
<Controls />
|
||||||
|
|
||||||
<SeekBar
|
<SeekBar
|
||||||
stopped={playerState.playback_status === "stopped"}
|
stopped={playerState.playback_status === "stopped"}
|
||||||
playing={playerState.playback_status === "playing"}
|
playing={playerState.playback_status === "playing"}
|
||||||
streamMode={playerState.livestream_mode}
|
streamMode={playerState.livestream_mode}
|
||||||
disabled={playerState.control_locked}
|
disabled={playerState.control_locked}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExtraActions />
|
<ExtraActions />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<Indicators track={playerState.track_manifest} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Player
|
export default Player
|
||||||
|
@ -266,4 +266,40 @@
|
|||||||
.ant-btn {
|
.ant-btn {
|
||||||
padding: 0;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user