mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 03:24:16 +00:00
improve media player
This commit is contained in:
parent
7606d3c92c
commit
a89228171a
184
packages/app/src/components/BackgroundMediaPlayer/index.jsx
Normal file
184
packages/app/src/components/BackgroundMediaPlayer/index.jsx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import React from "react"
|
||||||
|
import Ticker from "react-ticker"
|
||||||
|
import { FastAverageColor } from "fast-average-color"
|
||||||
|
import classnames from "classnames"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const useEventBus = (events) => {
|
||||||
|
const registerEvents = () => {
|
||||||
|
for (const [event, handler] of Object.entries(events)) {
|
||||||
|
app.eventBus.on(event, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unregisterEvents = () => {
|
||||||
|
for (const [event, handler] of Object.entries(events)) {
|
||||||
|
app.eventBus.off(event, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
registerEvents()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unregisterEvents()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const fac = new FastAverageColor()
|
||||||
|
|
||||||
|
const bruh = (props) => {
|
||||||
|
const [thumbnailAnalysis, setThumbnailAnalysis] = React.useState("#000000")
|
||||||
|
const [currentPlaying, setCurrentPlaying] = React.useState(app.cores.player.getState("currentAudioManifest"))
|
||||||
|
const [plabackState, setPlaybackState] = React.useState(app.cores.player.getState("playbackStatus") ?? "stopped")
|
||||||
|
|
||||||
|
const onClickMinimize = () => {
|
||||||
|
app.cores.player.minimize()
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateAverageCoverColor = async () => {
|
||||||
|
if (currentPlaying) {
|
||||||
|
const color = await fac.getColorAsync(currentPlaying.thumbnail)
|
||||||
|
setThumbnailAnalysis(color)
|
||||||
|
updateBackgroundItem(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateBackgroundItem = () => {
|
||||||
|
app.SidebarController.updateBackgroundItem(undefined, {
|
||||||
|
icon: <Icons.MdMusicNote />,
|
||||||
|
style: {
|
||||||
|
backgroundColor: thumbnailAnalysis?.hex
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEventBus({
|
||||||
|
"player.current.update": (data) => {
|
||||||
|
console.log("player.current.update", data)
|
||||||
|
setCurrentPlaying(data)
|
||||||
|
updateBackgroundItem()
|
||||||
|
},
|
||||||
|
"player.playback.update": (data) => {
|
||||||
|
setPlaybackState(data)
|
||||||
|
updateBackgroundItem()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
calculateAverageCoverColor()
|
||||||
|
}, [currentPlaying])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className="background_media_player"
|
||||||
|
onClick={onClickMinimize}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
currentPlaying && <div
|
||||||
|
className={classnames(
|
||||||
|
"background_media_player__title",
|
||||||
|
{
|
||||||
|
["lightBackground"]: thumbnailAnalysis.isLight,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
{currentPlaying.title} - {currentPlaying.artist}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BackgroundMediaPlayer extends React.Component {
|
||||||
|
state = {
|
||||||
|
thumbnailAnalysis: null,
|
||||||
|
currentPlaying: app.cores.player.getState("currentAudioManifest"),
|
||||||
|
plabackState: app.cores.player.getState("playbackStatus") ?? "stopped",
|
||||||
|
}
|
||||||
|
|
||||||
|
events = {
|
||||||
|
"player.current.update": (data) => {
|
||||||
|
this.calculateAverageCoverColor()
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
currentPlaying: data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
"player.playback.update": (data) => {
|
||||||
|
this.updateBackgroundItem()
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
plabackState: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickMinimize = () => {
|
||||||
|
app.cores.player.minimize()
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateAverageCoverColor = async () => {
|
||||||
|
if (this.state.currentPlaying) {
|
||||||
|
const color = await fac.getColorAsync(this.state.currentPlaying.thumbnail)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
thumbnailAnalysis: color
|
||||||
|
})
|
||||||
|
|
||||||
|
this.updateBackgroundItem(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBackgroundItem = (analysis) => {
|
||||||
|
app.SidebarController.updateBackgroundItem(undefined, {
|
||||||
|
icon: <Icons.MdMusicNote />,
|
||||||
|
style: {
|
||||||
|
backgroundColor: analysis?.hex ?? this.state.thumbnailAnalysis?.hex,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
this.calculateAverageCoverColor()
|
||||||
|
|
||||||
|
for (const [event, handler] of Object.entries(this.events)) {
|
||||||
|
app.eventBus.on(event, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
for (const [event, handler] of Object.entries(this.events)) {
|
||||||
|
app.eventBus.off(event, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div
|
||||||
|
className="background_media_player"
|
||||||
|
onClick={this.onClickMinimize}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
this.state.currentPlaying && <div
|
||||||
|
className={classnames(
|
||||||
|
"background_media_player__title",
|
||||||
|
{
|
||||||
|
["lightBackground"]: this.state.thumbnailAnalysis?.isLight,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h4>
|
||||||
|
{this.state.currentPlaying?.title} - {this.state.currentPlaying?.artist}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
36
packages/app/src/components/BackgroundMediaPlayer/index.less
Normal file
36
packages/app/src/components/BackgroundMediaPlayer/index.less
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
.background_media_player {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
.background_media_player__title {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.lightBackground {
|
||||||
|
h4 {
|
||||||
|
color: var(--text-color-black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
font-family: "Space Grotesk", sans-serif;
|
||||||
|
|
||||||
|
color: var(--text-color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import React from "react"
|
|||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
import Slider from "@mui/material/Slider"
|
import Slider from "@mui/material/Slider"
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
|
import Ticker from "react-ticker"
|
||||||
|
|
||||||
import { Icons, createIconRender } from "components/Icons"
|
import { Icons, createIconRender } from "components/Icons"
|
||||||
|
|
||||||
@ -163,7 +164,7 @@ class SeekBar extends React.Component {
|
|||||||
|
|
||||||
this.calculateTime()
|
this.calculateTime()
|
||||||
this.updateAll()
|
this.updateAll()
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tick = () => {
|
tick = () => {
|
||||||
@ -273,6 +274,7 @@ export default class AudioPlayer extends React.Component {
|
|||||||
audioVolume: app.cores.player.getState("audioVolume") ?? 0.3,
|
audioVolume: app.cores.player.getState("audioVolume") ?? 0.3,
|
||||||
bpm: app.cores.player.getState("trackBPM") ?? 0,
|
bpm: app.cores.player.getState("trackBPM") ?? 0,
|
||||||
showControls: false,
|
showControls: false,
|
||||||
|
minimized: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
events = {
|
events = {
|
||||||
@ -294,6 +296,13 @@ export default class AudioPlayer extends React.Component {
|
|||||||
"player.volume.update": (data) => {
|
"player.volume.update": (data) => {
|
||||||
this.setState({ audioVolume: data })
|
this.setState({ audioVolume: data })
|
||||||
},
|
},
|
||||||
|
"player.minimized.update": (minimized) => {
|
||||||
|
console.log(`Player minimized updated: ${minimized}`)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
minimized
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount = async () => {
|
componentDidMount = async () => {
|
||||||
@ -319,7 +328,7 @@ export default class AudioPlayer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
minimize = () => {
|
minimize = () => {
|
||||||
|
app.cores.player.minimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVolume = (value) => {
|
updateVolume = (value) => {
|
||||||
@ -356,6 +365,7 @@ export default class AudioPlayer extends React.Component {
|
|||||||
"embbededMediaPlayerWrapper",
|
"embbededMediaPlayerWrapper",
|
||||||
{
|
{
|
||||||
["hovering"]: this.state.showControls,
|
["hovering"]: this.state.showControls,
|
||||||
|
["minimized"]: this.state.minimized,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
onMouseEnter={this.onMouse}
|
onMouseEnter={this.onMouse}
|
||||||
|
@ -18,6 +18,15 @@
|
|||||||
|
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
||||||
|
pointer-events: initial;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
&.minimized {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.hovering {
|
&.hovering {
|
||||||
.actions_wrapper {
|
.actions_wrapper {
|
||||||
.actions {
|
.actions {
|
||||||
@ -88,7 +97,7 @@
|
|||||||
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
transition: all 150ms ease-in-out;
|
||||||
@ -97,7 +106,7 @@
|
|||||||
|
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
background-color: var(--background-color-accent);
|
background-color: var(--background-color-accent);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
@ -231,6 +240,7 @@
|
|||||||
|
|
||||||
.muteButton {
|
.muteButton {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,34 @@ export default class Sidebar extends React.Component {
|
|||||||
isExpanded: () => this.state.expanded,
|
isExpanded: () => this.state.expanded,
|
||||||
setCustomRender: this.setRender,
|
setCustomRender: this.setRender,
|
||||||
closeCustomRender: this.closeRender,
|
closeCustomRender: this.closeRender,
|
||||||
|
updateBackgroundItem: (children, props) => {
|
||||||
|
let updatedValue = this.state.backgroundItem
|
||||||
|
|
||||||
|
if (typeof children !== "undefined") {
|
||||||
|
updatedValue.children = children
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props !== "undefined") {
|
||||||
|
updatedValue.props = props
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
backgroundItem: updatedValue
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setBackgroundItem: (children, props) => {
|
||||||
|
this.setState({
|
||||||
|
backgroundItem: {
|
||||||
|
children: children,
|
||||||
|
props: props,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearBackgroundItem: () => {
|
||||||
|
this.setState({
|
||||||
|
backgroundItem: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -130,6 +158,7 @@ export default class Sidebar extends React.Component {
|
|||||||
|
|
||||||
customRenderTitle: null,
|
customRenderTitle: null,
|
||||||
customRender: null,
|
customRender: null,
|
||||||
|
backgroundItem: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle sidedrawer open/close
|
// handle sidedrawer open/close
|
||||||
@ -228,6 +257,10 @@ export default class Sidebar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
|
if (e.item.props.ignoreClick) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (e.item.props.override_event) {
|
if (e.item.props.override_event) {
|
||||||
return app.eventBus.emit(e.item.props.override_event, e.item.props.override_event_props)
|
return app.eventBus.emit(e.item.props.override_event, e.item.props.override_event_props)
|
||||||
}
|
}
|
||||||
@ -341,6 +374,16 @@ export default class Sidebar extends React.Component {
|
|||||||
|
|
||||||
<div key="bottom" className={classnames("app_sidebar_menu_wrapper", "bottom")}>
|
<div key="bottom" className={classnames("app_sidebar_menu_wrapper", "bottom")}>
|
||||||
<Menu selectable={false} mode="inline" onClick={this.handleClick}>
|
<Menu selectable={false} mode="inline" onClick={this.handleClick}>
|
||||||
|
<Menu.Item
|
||||||
|
{...this.state.backgroundItem?.props ?? {}}
|
||||||
|
key="background_item"
|
||||||
|
className={classnames("background_item", { ["active"]: this.state.backgroundItem })}
|
||||||
|
ignoreClick
|
||||||
|
>
|
||||||
|
{
|
||||||
|
this.state.backgroundItem?.children
|
||||||
|
}
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item key="search" icon={<Icons.Search />} >
|
<Menu.Item key="search" icon={<Icons.Search />} >
|
||||||
<Translation>
|
<Translation>
|
||||||
{(t) => t("Search")}
|
{(t) => t("Search")}
|
||||||
|
@ -182,6 +182,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.background_item {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
background-color: unset!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active{
|
||||||
|
background-color: unset!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.render_content_wrapper {
|
.render_content_wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
import React from "react"
|
||||||
import Core from "evite/src/core"
|
import Core from "evite/src/core"
|
||||||
import { Observable } from "object-observer"
|
import { Observable } from "object-observer"
|
||||||
import store from "store"
|
import store from "store"
|
||||||
// import { createRealTimeBpmProcessor } from "realtime-bpm-analyzer"
|
// import { createRealTimeBpmProcessor } from "realtime-bpm-analyzer"
|
||||||
|
|
||||||
import { EmbbededMediaPlayer } from "components"
|
import EmbbededMediaPlayer from "components/EmbbededMediaPlayer"
|
||||||
|
import BackgroundMediaPlayer from "components/BackgroundMediaPlayer"
|
||||||
|
|
||||||
import { DOMWindow } from "components/RenderWindow"
|
import { DOMWindow } from "components/RenderWindow"
|
||||||
|
|
||||||
class AudioPlayerStorage {
|
class AudioPlayerStorage {
|
||||||
@ -47,6 +50,7 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
state = Observable.from({
|
state = Observable.from({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
minimized: false,
|
||||||
audioMuted: AudioPlayerStorage.get("mute") ?? false,
|
audioMuted: AudioPlayerStorage.get("mute") ?? false,
|
||||||
playbackMode: AudioPlayerStorage.get("mode") ?? "repeat",
|
playbackMode: AudioPlayerStorage.get("mode") ?? "repeat",
|
||||||
audioVolume: AudioPlayerStorage.get("volume") ?? 0.3,
|
audioVolume: AudioPlayerStorage.get("volume") ?? 0.3,
|
||||||
@ -63,6 +67,7 @@ export default class Player extends Core {
|
|||||||
attachPlayerComponent: this.attachPlayerComponent.bind(this),
|
attachPlayerComponent: this.attachPlayerComponent.bind(this),
|
||||||
detachPlayerComponent: this.detachPlayerComponent.bind(this),
|
detachPlayerComponent: this.detachPlayerComponent.bind(this),
|
||||||
toogleMute: this.toogleMute.bind(this),
|
toogleMute: this.toogleMute.bind(this),
|
||||||
|
minimize: this.toogleMinimize.bind(this),
|
||||||
volume: this.volume.bind(this),
|
volume: this.volume.bind(this),
|
||||||
start: this.start.bind(this),
|
start: this.start.bind(this),
|
||||||
startPlaylist: this.startPlaylist.bind(this),
|
startPlaylist: this.startPlaylist.bind(this),
|
||||||
@ -198,6 +203,17 @@ export default class Player extends Core {
|
|||||||
case "playbackStatus": {
|
case "playbackStatus": {
|
||||||
app.eventBus.emit("player.status.update", change.object.playbackStatus)
|
app.eventBus.emit("player.status.update", change.object.playbackStatus)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "minimized": {
|
||||||
|
if (change.object.minimized) {
|
||||||
|
app.SidebarController.setBackgroundItem(React.createElement(BackgroundMediaPlayer))
|
||||||
|
} else {
|
||||||
|
app.SidebarController.setBackgroundItem(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.eventBus.emit("player.minimized.update", change.object.minimized)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,6 +649,12 @@ export default class Player extends Core {
|
|||||||
return this.state.audioMuted
|
return this.state.audioMuted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toogleMinimize(to) {
|
||||||
|
this.state.minimized = to ?? !this.state.minimized
|
||||||
|
|
||||||
|
return this.state.minimized
|
||||||
|
}
|
||||||
|
|
||||||
volume(volume) {
|
volume(volume) {
|
||||||
if (typeof volume !== "number") {
|
if (typeof volume !== "number") {
|
||||||
return this.state.audioVolume
|
return this.state.audioVolume
|
||||||
@ -716,4 +738,15 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
return this.state.velocity
|
return this.state.velocity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collapse(to) {
|
||||||
|
if (typeof to !== "boolean") {
|
||||||
|
console.warn("Collapse must be a boolean")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.collapsed = to ?? !this.state.collapsed
|
||||||
|
|
||||||
|
return this.state.collapsed
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user