diff --git a/packages/app/src/components/EmbbededMediaPlayer/index.jsx b/packages/app/src/components/EmbbededMediaPlayer/index.jsx
deleted file mode 100755
index 6d1207aa..00000000
--- a/packages/app/src/components/EmbbededMediaPlayer/index.jsx
+++ /dev/null
@@ -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
-
{
- return `${Math.round(value * 100)}%`
- }
- }}
- vertical
- />
-
-}
-
-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
-
- {
- 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())
- }}
- />
-
-
-
- {this.state.timeText}
-
-
- {
- this.props.streamMode ?
Live :
{this.state.durationText}
- }
-
-
-
- }
-}
-
-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
-}
-
-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
-
-
}
- onClick={this.minimize}
- shape="circle"
- />
-
- {
- !this.state.syncModeLocked && !this.state.syncMode &&
}
- onClick={this.inviteSync}
- shape="circle"
- />
- }
-
-
}
- onClick={this.openVisualizer}
- shape="circle"
- />
-
-
}
- onClick={this.close}
- shape="square"
- />
-
-
-
-
-
-
-
- {
- currentPlaying?.title
- ? currentPlaying?.title
- : (loading ? "Loading..." : (currentPlaying?.title ?? "Untitled"))
- }
-
-
-
- {
- currentPlaying?.artist &&
-
- {currentPlaying?.artist ?? "Unknown"}
-
-
- }
-
-
-
-
-
-
-
-
-
}
- onClick={this.onClickPreviousButton}
- disabled={this.state.syncModeLocked}
- />
-
: playbackStatus === "playing" ?
:
}
- onClick={this.onClickPlayButton}
- className="playButton"
- disabled={this.state.syncModeLocked}
- >
- {
- loading &&
-
-
- }
-
-
}
- onClick={this.onClickNextButton}
- disabled={this.state.syncModeLocked}
- />
-
-
- {
- audioMuted
- ?
- :
- }
-
-
-
-
-
-
-
- }
-}
\ No newline at end of file
diff --git a/packages/app/src/components/Player/AudioVolume/index.jsx b/packages/app/src/components/Player/AudioVolume/index.jsx
new file mode 100644
index 00000000..22f211cb
--- /dev/null
+++ b/packages/app/src/components/Player/AudioVolume/index.jsx
@@ -0,0 +1,23 @@
+import React from "react"
+import * as antd from "antd"
+
+import "./index.less"
+
+export default (props) => {
+ return
+
{
+ return `${Math.round(value * 100)}%`
+ }
+ }}
+ vertical
+ />
+
+}
diff --git a/packages/app/src/components/Player/AudioVolume/index.less b/packages/app/src/components/Player/AudioVolume/index.less
new file mode 100644
index 00000000..fe25d856
--- /dev/null
+++ b/packages/app/src/components/Player/AudioVolume/index.less
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/components/BackgroundMediaPlayer/index.jsx b/packages/app/src/components/Player/BackgroundMediaPlayer/index.jsx
similarity index 100%
rename from packages/app/src/components/BackgroundMediaPlayer/index.jsx
rename to packages/app/src/components/Player/BackgroundMediaPlayer/index.jsx
diff --git a/packages/app/src/components/BackgroundMediaPlayer/index.less b/packages/app/src/components/Player/BackgroundMediaPlayer/index.less
similarity index 100%
rename from packages/app/src/components/BackgroundMediaPlayer/index.less
rename to packages/app/src/components/Player/BackgroundMediaPlayer/index.less
diff --git a/packages/app/src/components/Player/ChangeModeButton/index.jsx b/packages/app/src/components/Player/ChangeModeButton/index.jsx
new file mode 100644
index 00000000..a2a78f37
--- /dev/null
+++ b/packages/app/src/components/Player/ChangeModeButton/index.jsx
@@ -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
+}
diff --git a/packages/app/src/components/Player/Controls/index.jsx b/packages/app/src/components/Player/Controls/index.jsx
new file mode 100644
index 00000000..6ea0ce1d
--- /dev/null
+++ b/packages/app/src/components/Player/Controls/index.jsx
@@ -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
+
+
}
+ onClick={() => onClickActionsButton("previous")}
+ disabled={syncModeLocked}
+ />
+
: playbackStatus === "playing" ?
:
}
+ onClick={() => onClickActionsButton("toogle")}
+ className="playButton"
+ disabled={syncModeLocked}
+ >
+ {
+ loading &&
+
+
+ }
+
+
}
+ onClick={() => onClickActionsButton("next")}
+ disabled={syncModeLocked}
+ />
+
+
+ {
+ audioMuted
+ ?
+ :
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/components/Player/Controls/index.less b/packages/app/src/components/Player/Controls/index.less
new file mode 100644
index 00000000..31775cae
--- /dev/null
+++ b/packages/app/src/components/Player/Controls/index.less
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/components/Player/MediaPlayer/index.jsx b/packages/app/src/components/Player/MediaPlayer/index.jsx
new file mode 100755
index 00000000..d81768c4
--- /dev/null
+++ b/packages/app/src/components/Player/MediaPlayer/index.jsx
@@ -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
+
+
}
+ onClick={this.minimize}
+ shape="circle"
+ />
+
+ {
+ !this.state.syncModeLocked && !this.state.syncMode &&
}
+ onClick={this.inviteSync}
+ shape="circle"
+ />
+ }
+
+
}
+ onClick={this.openVisualizer}
+ shape="circle"
+ />
+
+
}
+ onClick={this.close}
+ shape="square"
+ />
+
+
+
+
+
+
+
+ {
+ currentPlaying?.title
+ ? currentPlaying?.title
+ : (loading ? "Loading..." : (currentPlaying?.title ?? "Untitled"))
+ }
+
+
+
+ {
+ currentPlaying?.artist &&
+
+ {currentPlaying?.artist ?? "Unknown"}
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/components/EmbbededMediaPlayer/index.less b/packages/app/src/components/Player/MediaPlayer/index.less
similarity index 59%
rename from packages/app/src/components/EmbbededMediaPlayer/index.less
rename to packages/app/src/components/Player/MediaPlayer/index.less
index e2d188af..10c5a885 100755
--- a/packages/app/src/components/EmbbededMediaPlayer/index.less
+++ b/packages/app/src/components/Player/MediaPlayer/index.less
@@ -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;
}
}
diff --git a/packages/app/src/components/Player/SeekBar/index.jsx b/packages/app/src/components/Player/SeekBar/index.jsx
new file mode 100644
index 00000000..3459a61a
--- /dev/null
+++ b/packages/app/src/components/Player/SeekBar/index.jsx
@@ -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
+
+ {
+ 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())
+ }}
+ />
+
+
+
+ {this.state.timeText}
+
+
+ {
+ this.props.streamMode ?
Live :
{this.state.durationText}
+ }
+
+
+
+ }
+}
diff --git a/packages/app/src/components/Player/SeekBar/index.less b/packages/app/src/components/Player/SeekBar/index.less
new file mode 100644
index 00000000..4cdcd156
--- /dev/null
+++ b/packages/app/src/components/Player/SeekBar/index.less
@@ -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);
+ }
+}
\ No newline at end of file