move player components to individual dir

This commit is contained in:
SrGooglo 2023-05-29 16:04:54 +00:00
parent a1617c2e66
commit c962f19ff8
12 changed files with 759 additions and 688 deletions

View File

@ -1,549 +0,0 @@
import React from "react"
import * as antd from "antd"
import Slider from "@mui/material/Slider"
import classnames from "classnames"
import UseAnimations from "react-useanimations"
import LoadingAnimation from "react-useanimations/lib/loading"
import LikeButton from "components/LikeButton"
import { Icons, createIconRender } from "components/Icons"
import "./index.less"
// TODO: Queue view
const AudioVolume = (props) => {
return <div className="volumeSlider">
<antd.Slider
min={0}
max={1}
step={0.01}
value={props.volume}
onAfterChange={props.onChange}
defaultValue={props.defaultValue}
tooltip={{
formatter: (value) => {
return `${Math.round(value * 100)}%`
}
}}
vertical
/>
</div>
}
export class SeekBar extends React.Component {
state = {
timeText: "00:00",
durationText: "00:00",
sliderTime: 0,
sliderLock: false,
}
handleSeek = (value) => {
if (value > 0) {
// calculate the duration of the audio
const duration = app.cores.player.duration()
// calculate the seek of the audio
const seek = (value / 100) * duration
app.cores.player.seek(seek)
} else {
app.cores.player.seek(0)
}
}
calculateDuration = () => {
// get current audio duration
const audioDuration = app.cores.player.duration()
if (isNaN(audioDuration)) {
return
}
console.log(`Audio duration: ${audioDuration}`)
// set duration
this.setState({
durationText: this.seekToTimeLabel(audioDuration)
})
}
calculateTime = () => {
// get current audio seek
const seek = app.cores.player.seek()
// set time
this.setState({
timeText: this.seekToTimeLabel(seek)
})
}
seekToTimeLabel = (value) => {
// convert seek to minutes and seconds
const minutes = Math.floor(value / 60)
// add leading zero if minutes is less than 10
const minutesString = minutes < 10 ? `0${minutes}` : minutes
// get seconds
const seconds = Math.floor(value - minutes * 60)
// add leading zero if seconds is less than 10
const secondsString = seconds < 10 ? `0${seconds}` : seconds
return `${minutesString}:${secondsString}`
}
updateProgressBar = () => {
if (this.state.sliderLock) {
return
}
const seek = app.cores.player.seek()
const duration = app.cores.player.duration()
const percent = (seek / duration) * 100
this.setState({
sliderTime: percent
})
}
updateAll = () => {
this.calculateTime()
this.updateProgressBar()
}
events = {
"player.status.update": (status) => {
console.log(`Player status updated: ${status}`)
switch (status) {
case "stopped":
this.setState({
timeText: "00:00",
durationText: "00:00",
sliderTime: 0,
})
break
case "playing":
this.updateAll()
this.calculateDuration()
break
default:
break
}
},
"player.current.update": (currentAudioManifest) => {
console.log(`Player current audio updated:`, currentAudioManifest)
this.updateAll()
this.setState({
timeText: "00:00",
sliderTime: 0,
})
this.calculateDuration()
},
"player.duration.update": (duration) => {
console.log(`Player duration updated: ${duration}`)
this.calculateDuration()
},
"player.seek.update": (seek) => {
console.log(`Player seek updated: ${seek}`)
this.calculateTime()
this.updateAll()
},
}
tick = () => {
if (this.props.playing || this.props.streamMode) {
this.interval = setInterval(() => {
this.updateAll()
}, 1000)
} else {
if (this.interval) {
clearInterval(this.interval)
}
}
}
componentDidMount = () => {
this.calculateDuration()
this.tick()
for (const [event, callback] of Object.entries(this.events)) {
app.eventBus.on(event, callback)
}
}
componentWillUnmount = () => {
for (const [event, callback] of Object.entries(this.events)) {
app.eventBus.off(event, callback)
}
}
componentDidUpdate = (prevProps, prevState) => {
if (this.props.playing !== prevProps.playing) {
this.tick()
}
}
render() {
return <div
className={classnames(
"status",
)}
>
<div
className={classnames(
"progress",
{
["hidden"]: this.props.streamMode,
}
)}
>
<Slider
size="small"
value={this.state.sliderTime}
disabled={this.props.stopped || this.props.streamMode || this.props.disabled}
min={0}
max={100}
step={0.1}
onChange={(_, value) => {
this.setState({
sliderTime: value,
sliderLock: true
})
}}
onChangeCommitted={() => {
this.setState({
sliderLock: false
})
this.handleSeek(this.state.sliderTime)
if (!this.props.playing) {
app.cores.player.playback.play()
}
}}
valueLabelDisplay="auto"
valueLabelFormat={(value) => {
return this.seekToTimeLabel((value / 100) * app.cores.player.duration())
}}
/>
</div>
<div className="timers">
<div>
<span>{this.state.timeText}</span>
</div>
<div>
{
this.props.streamMode ? <antd.Tag>Live</antd.Tag> : <span>{this.state.durationText}</span>
}
</div>
</div>
</div>
}
}
const AudioPlayerChangeModeButton = (props) => {
const [mode, setMode] = React.useState(app.cores.player.playback.mode())
const modeToIcon = {
"normal": "MdArrowForward",
"repeat": "MdRepeat",
"shuffle": "MdShuffle",
}
const onClick = () => {
const modes = Object.keys(modeToIcon)
const newMode = modes[(modes.indexOf(mode) + 1) % modes.length]
app.cores.player.playback.mode(newMode)
setMode(newMode)
}
return <antd.Button
icon={createIconRender(modeToIcon[mode])}
onClick={onClick}
disabled={props.disabled ?? false}
type="ghost"
/>
}
export default class AudioPlayer extends React.Component {
state = {
loading: app.cores.player.getState("loading") ?? false,
currentPlaying: app.cores.player.getState("currentAudioManifest"),
playbackStatus: app.cores.player.getState("playbackStatus") ?? "stopped",
audioMuted: app.cores.player.getState("audioMuted") ?? false,
audioVolume: app.cores.player.getState("audioVolume") ?? 0.3,
bpm: app.cores.player.getState("trackBPM") ?? 0,
showControls: false,
minimized: false,
streamMode: false,
syncModeLocked: app.cores.player.getState("syncModeLocked"),
syncMode: app.cores.player.getState("syncMode"),
}
events = {
"player.syncModeLocked.update": (to) => {
this.setState({ syncModeLocked: to })
},
"player.syncMode.update": (to) => {
this.setState({ syncMode: to })
},
"player.livestream.update": (data) => {
this.setState({ streamMode: data })
},
"player.bpm.update": (data) => {
this.setState({ bpm: data })
},
"player.loading.update": (data) => {
this.setState({ loading: data })
},
"player.status.update": (data) => {
this.setState({ playbackStatus: data })
},
"player.current.update": (data) => {
this.setState({ currentPlaying: data })
},
"player.mute.update": (data) => {
this.setState({ audioMuted: data })
},
"player.volume.update": (data) => {
this.setState({ audioVolume: data })
},
"player.minimized.update": (minimized) => {
this.setState({ minimized })
}
}
componentDidMount = async () => {
Object.entries(this.events).forEach(([event, callback]) => {
app.eventBus.on(event, callback)
})
}
componentWillUnmount() {
Object.entries(this.events).forEach(([event, callback]) => {
app.eventBus.off(event, callback)
})
}
onMouse = (event) => {
const { type } = event
if (type === "mouseenter") {
this.setState({ showControls: true })
} else if (type === "mouseleave") {
this.setState({ showControls: false })
}
}
minimize = () => {
app.cores.player.minimize()
}
close = () => {
app.cores.player.close()
}
openVisualizer = () => {
app.setLocation("/lyrics")
}
inviteSync = () => {
app.cores.sync.music.createSyncRoom()
}
updateVolume = (value) => {
app.cores.player.volume(value)
}
toogleMute = () => {
app.cores.player.toogleMute()
}
onClickPlayButton = () => {
if (this.state.streamMode) {
return app.cores.player.playback.stop()
}
app.cores.player.playback.toogle()
}
onClickPreviousButton = () => {
app.cores.player.playback.previous()
}
onClickNextButton = () => {
app.cores.player.playback.next()
}
onClickLikeButton = () => {
// TODO: Like
console.log("Like")
this.setState({ liked: !this.state.liked })
}
render() {
const {
loading,
currentPlaying,
playbackStatus,
audioMuted,
audioVolume,
} = this.state
return <div
className={classnames(
"embbededMediaPlayerWrapper",
{
["hovering"]: this.state.showControls,
["minimized"]: this.state.minimized,
}
)}
onMouseEnter={this.onMouse}
onMouseLeave={this.onMouse}
>
<div className="top_controls">
<antd.Button
icon={<Icons.MdFirstPage />}
onClick={this.minimize}
shape="circle"
/>
{
!this.state.syncModeLocked && !this.state.syncMode && <antd.Button
icon={<Icons.MdShare />}
onClick={this.inviteSync}
shape="circle"
/>
}
<antd.Button
icon={<Icons.MdOpenInFull />}
onClick={this.openVisualizer}
shape="circle"
/>
<antd.Button
className="bottom_btn"
icon={<Icons.X />}
onClick={this.close}
shape="square"
/>
</div>
<div className="player">
<div
className="cover"
style={{
backgroundImage: `url(${(currentPlaying?.thumbnail) ?? "/assets/no_song.png"})`,
}}
/>
<div className="header">
<div className="info">
<div className="title">
<h2>
{
currentPlaying?.title
? currentPlaying?.title
: (loading ? "Loading..." : (currentPlaying?.title ?? "Untitled"))
}
</h2>
</div>
<div className="subTitle">
{
currentPlaying?.artist && <div className="artist">
<h3>
{currentPlaying?.artist ?? "Unknown"}
</h3>
</div>
}
<LikeButton
onClick={this.onClickLikeButton}
liked={this.state.liked}
/>
</div>
</div>
</div>
<div className="controls">
<AudioPlayerChangeModeButton
disabled={this.state.syncModeLocked}
/>
<antd.Button
type="ghost"
shape="round"
icon={<Icons.ChevronLeft />}
onClick={this.onClickPreviousButton}
disabled={this.state.syncModeLocked}
/>
<antd.Button
type="primary"
shape="circle"
icon={this.state.streamMode ? <Icons.MdStop /> : playbackStatus === "playing" ? <Icons.MdPause /> : <Icons.MdPlayArrow />}
onClick={this.onClickPlayButton}
className="playButton"
disabled={this.state.syncModeLocked}
>
{
loading && <div className="loadCircle">
<UseAnimations
animation={LoadingAnimation}
size="100%"
/>
</div>
}
</antd.Button>
<antd.Button
type="ghost"
shape="round"
icon={<Icons.ChevronRight />}
onClick={this.onClickNextButton}
disabled={this.state.syncModeLocked}
/>
<antd.Popover
content={React.createElement(
AudioVolume,
{ onChange: this.updateVolume, defaultValue: audioVolume }
)}
trigger="hover"
>
<div
className="muteButton"
onClick={this.toogleMute}
>
{
audioMuted
? <Icons.VolumeX />
: <Icons.Volume2 />
}
</div>
</antd.Popover>
</div>
<SeekBar
stopped={playbackStatus === "stopped"}
playing={playbackStatus === "playing"}
streamMode={this.state.streamMode}
disabled={this.state.syncModeLocked}
/>
</div>
</div>
}
}

View File

@ -0,0 +1,23 @@
import React from "react"
import * as antd from "antd"
import "./index.less"
export default (props) => {
return <div className="player-volume_slider">
<antd.Slider
min={0}
max={1}
step={0.01}
value={props.volume}
onAfterChange={props.onChange}
defaultValue={props.defaultValue}
tooltip={{
formatter: (value) => {
return `${Math.round(value * 100)}%`
}
}}
vertical
/>
</div>
}

View File

@ -0,0 +1,14 @@
.ant-popover-inner-content {
height: fit-content;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.player-volume_slider {
height: 150px;
padding-bottom: 10px;
}
}

View File

@ -0,0 +1,31 @@
import React from "react"
import * as antd from "antd"
import { Icons, createIconRender } from "components/Icons"
export default (props) => {
const [mode, setMode] = React.useState(app.cores.player.playback.mode())
const modeToIcon = {
"normal": "MdArrowForward",
"repeat": "MdRepeat",
"shuffle": "MdShuffle",
}
const onClick = () => {
const modes = Object.keys(modeToIcon)
const newMode = modes[(modes.indexOf(mode) + 1) % modes.length]
app.cores.player.playback.mode(newMode)
setMode(newMode)
}
return <antd.Button
icon={createIconRender(modeToIcon[mode])}
onClick={onClick}
disabled={props.disabled ?? false}
type="ghost"
/>
}

View File

@ -0,0 +1,101 @@
import React from "react"
import * as antd from "antd"
import UseAnimations from "react-useanimations"
import LoadingAnimation from "react-useanimations/lib/loading"
import { Icons } from "components/Icons"
import AudioVolume from "components/Player/AudioVolume"
import AudioPlayerChangeModeButton from "components/Player/ChangeModeButton"
import "./index.less"
export default ({
className,
controls,
syncModeLocked = false,
syncMode = false,
streamMode,
playbackStatus,
onVolumeUpdate,
onMuteUpdate,
audioVolume = 0.3,
audioMuted = false,
loading = false,
} = {}) => {
const onClickActionsButton = (event) => {
if (typeof controls !== "object") {
console.warn("[AudioPlayer] onClickActionsButton: props.controls is not an object")
return false
}
if (typeof controls[event] !== "function") {
console.warn(`[AudioPlayer] onClickActionsButton: ${event} is not a function`)
return false
}
return controls[event]()
}
return <div
className={
className ?? "player-controls"
}
>
<AudioPlayerChangeModeButton
disabled={syncModeLocked}
/>
<antd.Button
type="ghost"
shape="round"
icon={<Icons.ChevronLeft />}
onClick={() => onClickActionsButton("previous")}
disabled={syncModeLocked}
/>
<antd.Button
type="primary"
shape="circle"
icon={streamMode ? <Icons.MdStop /> : playbackStatus === "playing" ? <Icons.MdPause /> : <Icons.MdPlayArrow />}
onClick={() => onClickActionsButton("toogle")}
className="playButton"
disabled={syncModeLocked}
>
{
loading && <div className="loadCircle">
<UseAnimations
animation={LoadingAnimation}
size="100%"
/>
</div>
}
</antd.Button>
<antd.Button
type="ghost"
shape="round"
icon={<Icons.ChevronRight />}
onClick={() => onClickActionsButton("next")}
disabled={syncModeLocked}
/>
<antd.Popover
content={React.createElement(
AudioVolume,
{ onChange: onVolumeUpdate, defaultValue: audioVolume }
)}
trigger="hover"
>
<div
className="muteButton"
onClick={onMuteUpdate}
>
{
audioMuted
? <Icons.VolumeX />
: <Icons.Volume2 />
}
</div>
</antd.Popover>
</div>
}

View File

@ -0,0 +1,61 @@
.player-controls {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
width: 100%;
svg {
color: var(--text-color);
margin: 0 !important;
}
.playButton {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.loadCircle {
position: absolute;
z-index: 330;
top: 0;
right: 0;
left: 0;
width: 100%;
height: 100%;
margin: auto;
align-self: center;
justify-self: center;
transform: scale(1.5);
svg {
width: 100%;
height: 100%;
path {
stroke: var(--text-color);
stroke-width: 1;
}
}
}
}
.muteButton {
padding: 10px;
svg {
font-size: 1rem;
}
}
}

View File

@ -0,0 +1,240 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import LikeButton from "components/LikeButton"
import { Icons } from "components/Icons"
import SeekBar from "components/Player/SeekBar"
import Controls from "components/Player/Controls"
import "./index.less"
// TODO: Queue view
export default class AudioPlayer extends React.Component {
state = {
loading: app.cores.player.getState("loading") ?? false,
currentPlaying: app.cores.player.getState("currentAudioManifest"),
playbackStatus: app.cores.player.getState("playbackStatus") ?? "stopped",
audioMuted: app.cores.player.getState("audioMuted") ?? false,
audioVolume: app.cores.player.getState("audioVolume") ?? 0.3,
bpm: app.cores.player.getState("trackBPM") ?? 0,
showControls: false,
minimized: false,
streamMode: false,
syncModeLocked: app.cores.player.getState("syncModeLocked"),
syncMode: app.cores.player.getState("syncMode"),
}
events = {
"player.syncModeLocked.update": (to) => {
this.setState({ syncModeLocked: to })
},
"player.syncMode.update": (to) => {
this.setState({ syncMode: to })
},
"player.livestream.update": (data) => {
this.setState({ streamMode: data })
},
"player.bpm.update": (data) => {
this.setState({ bpm: data })
},
"player.loading.update": (data) => {
this.setState({ loading: data })
},
"player.status.update": (data) => {
this.setState({ playbackStatus: data })
},
"player.current.update": (data) => {
this.setState({ currentPlaying: data })
},
"player.mute.update": (data) => {
this.setState({ audioMuted: data })
},
"player.volume.update": (data) => {
this.setState({ audioVolume: data })
},
"player.minimized.update": (minimized) => {
this.setState({ minimized })
}
}
componentDidMount = async () => {
Object.entries(this.events).forEach(([event, callback]) => {
app.eventBus.on(event, callback)
})
}
componentWillUnmount() {
Object.entries(this.events).forEach(([event, callback]) => {
app.eventBus.off(event, callback)
})
}
onMouse = (event) => {
const { type } = event
if (type === "mouseenter") {
this.setState({ showControls: true })
} else if (type === "mouseleave") {
this.setState({ showControls: false })
}
}
minimize = () => {
app.cores.player.minimize()
}
close = () => {
app.cores.player.close()
}
openVisualizer = () => {
app.setLocation("/lyrics")
}
inviteSync = () => {
app.cores.sync.music.createSyncRoom()
}
updateVolume = (value) => {
app.cores.player.volume(value)
}
toogleMute = () => {
app.cores.player.toogleMute()
}
onClickPlayButton = () => {
if (this.state.streamMode) {
return app.cores.player.playback.stop()
}
app.cores.player.playback.toogle()
}
onClickPreviousButton = () => {
app.cores.player.playback.previous()
}
onClickNextButton = () => {
app.cores.player.playback.next()
}
onClickLikeButton = () => {
// TODO: Like
console.log("Like")
this.setState({ liked: !this.state.liked })
}
render() {
const {
loading,
currentPlaying,
playbackStatus,
audioMuted,
audioVolume,
} = this.state
return <div
className={classnames(
"embbededMediaPlayerWrapper",
{
["hovering"]: this.state.showControls,
["minimized"]: this.state.minimized,
}
)}
onMouseEnter={this.onMouse}
onMouseLeave={this.onMouse}
>
<div className="top_controls">
<antd.Button
icon={<Icons.MdFirstPage />}
onClick={this.minimize}
shape="circle"
/>
{
!this.state.syncModeLocked && !this.state.syncMode && <antd.Button
icon={<Icons.MdShare />}
onClick={this.inviteSync}
shape="circle"
/>
}
<antd.Button
icon={<Icons.MdOpenInFull />}
onClick={this.openVisualizer}
shape="circle"
/>
<antd.Button
className="bottom_btn"
icon={<Icons.X />}
onClick={this.close}
shape="square"
/>
</div>
<div className="player">
<div
className="cover"
style={{
backgroundImage: `url(${(currentPlaying?.thumbnail) ?? "/assets/no_song.png"})`,
}}
/>
<div className="header">
<div className="info">
<div className="title">
<h2>
{
currentPlaying?.title
? currentPlaying?.title
: (loading ? "Loading..." : (currentPlaying?.title ?? "Untitled"))
}
</h2>
</div>
<div className="subTitle">
{
currentPlaying?.artist && <div className="artist">
<h3>
{currentPlaying?.artist ?? "Unknown"}
</h3>
</div>
}
<LikeButton
onClick={this.onClickLikeButton}
liked={this.state.liked}
/>
</div>
</div>
</div>
<Controls
syncModeLocked={this.state.syncModeLocked}
syncMode={this.state.syncMode}
playbackStatus={playbackStatus}
audioMuted={audioMuted}
audioVolume={audioVolume}
onVolumeUpdate={this.updateVolume}
onMuteUpdate={this.toogleMute}
controls={{
previous: this.onClickPreviousButton,
toogle: this.onClickPlayButton,
next: this.onClickNextButton,
}}
/>
<SeekBar
stopped={playbackStatus === "stopped"}
playing={playbackStatus === "playing"}
streamMode={this.state.streamMode}
disabled={this.state.syncModeLocked}
/>
</div>
</div>
}
}

View File

@ -177,145 +177,6 @@
}
}
}
.controls {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
width: 100%;
svg {
color: var(--text-color);
margin: 0 !important;
}
.playButton {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.loadCircle {
position: absolute;
z-index: 330;
top: 0;
right: 0;
left: 0;
width: 100%;
height: 100%;
margin: auto;
align-self: center;
justify-self: center;
transform: scale(1.5);
svg {
width: 100%;
height: 100%;
path {
stroke: var(--text-color);
stroke-width: 1;
}
}
}
}
.muteButton {
padding: 10px;
svg {
font-size: 1rem;
}
}
}
}
.status {
z-index: 330;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-self: center;
width: 90%;
height: 100%;
margin: 20px 0 10px 0;
border-radius: 8px;
transition: all 150ms ease-in-out;
&.hidden {
height: 0px;
opacity: 0;
pointer-events: none;
}
.progress {
width: 100%;
height: 100%;
transition: all 150ms ease-in-out;
&.hidden {
opacity: 0;
height: 0;
pointer-events: none;
}
}
.timers {
display: inline-flex;
flex-direction: row;
width: 100%;
height: fit-content;
justify-content: space-between;
align-items: center;
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color);
}
}
}
.ant-popover-inner-content {
height: fit-content;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.volumeSlider {
height: 250px;
padding-bottom: 10px;
}
}

View File

@ -0,0 +1,228 @@
import React from "react"
import * as antd from "antd"
import Slider from "@mui/material/Slider"
import classnames from "classnames"
import "./index.less"
export default class SeekBar extends React.Component {
state = {
timeText: "00:00",
durationText: "00:00",
sliderTime: 0,
sliderLock: false,
}
handleSeek = (value) => {
if (value > 0) {
// calculate the duration of the audio
const duration = app.cores.player.duration()
// calculate the seek of the audio
const seek = (value / 100) * duration
app.cores.player.seek(seek)
} else {
app.cores.player.seek(0)
}
}
calculateDuration = () => {
// get current audio duration
const audioDuration = app.cores.player.duration()
if (isNaN(audioDuration)) {
return
}
console.log(`Audio duration: ${audioDuration}`)
// set duration
this.setState({
durationText: this.seekToTimeLabel(audioDuration)
})
}
calculateTime = () => {
// get current audio seek
const seek = app.cores.player.seek()
// set time
this.setState({
timeText: this.seekToTimeLabel(seek)
})
}
seekToTimeLabel = (value) => {
// convert seek to minutes and seconds
const minutes = Math.floor(value / 60)
// add leading zero if minutes is less than 10
const minutesString = minutes < 10 ? `0${minutes}` : minutes
// get seconds
const seconds = Math.floor(value - minutes * 60)
// add leading zero if seconds is less than 10
const secondsString = seconds < 10 ? `0${seconds}` : seconds
return `${minutesString}:${secondsString}`
}
updateProgressBar = () => {
if (this.state.sliderLock) {
return
}
const seek = app.cores.player.seek()
const duration = app.cores.player.duration()
const percent = (seek / duration) * 100
this.setState({
sliderTime: percent
})
}
updateAll = () => {
this.calculateTime()
this.updateProgressBar()
}
events = {
"player.status.update": (status) => {
console.log(`Player status updated: ${status}`)
switch (status) {
case "stopped":
this.setState({
timeText: "00:00",
durationText: "00:00",
sliderTime: 0,
})
break
case "playing":
this.updateAll()
this.calculateDuration()
break
default:
break
}
},
"player.current.update": (currentAudioManifest) => {
console.log(`Player current audio updated:`, currentAudioManifest)
this.updateAll()
this.setState({
timeText: "00:00",
sliderTime: 0,
})
this.calculateDuration()
},
"player.duration.update": (duration) => {
console.log(`Player duration updated: ${duration}`)
this.calculateDuration()
},
"player.seek.update": (seek) => {
console.log(`Player seek updated: ${seek}`)
this.calculateTime()
this.updateAll()
},
}
tick = () => {
if (this.props.playing || this.props.streamMode) {
this.interval = setInterval(() => {
this.updateAll()
}, 1000)
} else {
if (this.interval) {
clearInterval(this.interval)
}
}
}
componentDidMount = () => {
this.calculateDuration()
this.tick()
for (const [event, callback] of Object.entries(this.events)) {
app.eventBus.on(event, callback)
}
}
componentWillUnmount = () => {
for (const [event, callback] of Object.entries(this.events)) {
app.eventBus.off(event, callback)
}
}
componentDidUpdate = (prevProps, prevState) => {
if (this.props.playing !== prevProps.playing) {
this.tick()
}
}
render() {
return <div
className={classnames(
"player-seek_bar",
)}
>
<div
className={classnames(
"progress",
{
["hidden"]: this.props.streamMode,
}
)}
>
<Slider
size="small"
value={this.state.sliderTime}
disabled={this.props.stopped || this.props.streamMode || this.props.disabled}
min={0}
max={100}
step={0.1}
onChange={(_, value) => {
this.setState({
sliderTime: value,
sliderLock: true
})
}}
onChangeCommitted={() => {
this.setState({
sliderLock: false
})
this.handleSeek(this.state.sliderTime)
if (!this.props.playing) {
app.cores.player.playback.play()
}
}}
valueLabelDisplay="auto"
valueLabelFormat={(value) => {
return this.seekToTimeLabel((value / 100) * app.cores.player.duration())
}}
/>
</div>
<div className="timers">
<div>
<span>{this.state.timeText}</span>
</div>
<div>
{
this.props.streamMode ? <antd.Tag>Live</antd.Tag> : <span>{this.state.durationText}</span>
}
</div>
</div>
</div>
}
}

View File

@ -0,0 +1,61 @@
.player-seek_bar {
z-index: 330;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-self: center;
width: 90%;
height: 100%;
margin: 20px 0 10px 0;
border-radius: 8px;
transition: all 150ms ease-in-out;
&.hidden {
height: 0px;
opacity: 0;
pointer-events: none;
}
.progress {
width: 100%;
height: 100%;
transition: all 150ms ease-in-out;
&.hidden {
opacity: 0;
height: 0;
pointer-events: none;
}
}
.timers {
display: inline-flex;
flex-direction: row;
width: 100%;
height: fit-content;
justify-content: space-between;
align-items: center;
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color);
}
}