use new audio player for mobile

This commit is contained in:
SrGooglo 2023-10-12 19:57:18 +00:00
parent 2c48f6e3dc
commit f16c3bed9b
4 changed files with 317 additions and 549 deletions

View File

@ -1,265 +0,0 @@
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 { WithPlayerContext, Context } from "contexts/WithPlayerContext"
import "./index.less"
export default (props) => {
return <WithPlayerContext>
<AudioPlayer
{...props}
/>
</WithPlayerContext>
}
const ServiceIndicator = (props) => {
if (!props.service) {
return null
}
switch (props.service) {
case "tidal": {
return <div className="service_indicator">
<Icons.SiTidal /> Playing from Tidal
</div>
}
default: {
return null
}
}
}
const MemeDancer = (props) => {
const defaultBpm = 120
const [currentBpm, setCurrentBpm] = React.useState(defaultBpm)
const videoRef = React.useRef()
const togglePlayback = (to) => {
videoRef.current[to ? "play" : "pause"]()
}
React.useEffect(() => {
app.cores.player.eventBus.on("bpm.change", (bpm) => {
setCurrentBpm(bpm)
})
app.cores.player.eventBus.on("player.state.update:playback_status", (status) => {
if (status === "playing") {
togglePlayback(true)
}else {
togglePlayback(false)
}
})
}, [])
React.useEffect(() => {
if (typeof currentBpm === "number" && isFinite(currentBpm)) {
let playbackRate = currentBpm / 120;
playbackRate = Math.min(4.0, Math.max(0.1, playbackRate)); // Limit the range between 0.1 and 4.0
videoRef.current.playbackRate = playbackRate;
}
}, [currentBpm])
return <div className="meme_dancer">
<video
ref={videoRef}
muted
autoPlay
loop
controls={false}
>
<source
src="https://media.tenor.com/-VG9cLwSYTcAAAPo/dancing-triangle-dancing.mp4"
/>
</video>
</div>
}
// TODO: Queue view
export class AudioPlayer extends React.Component {
static contextType = Context
state = {
showControls: false,
showDancer: false,
}
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.location.push("/lyrics")
}
inviteSync = () => {
app.cores.sync.music.createSyncRoom()
}
updateVolume = (value) => {
app.cores.player.volume(value)
}
toggleMute = () => {
app.cores.player.toggleMute()
}
onClickPlayButton = () => {
if (this.context.sync_mode) {
return app.cores.player.playback.stop()
}
app.cores.player.playback.toggle()
}
onClickPreviousButton = () => {
app.cores.player.playback.previous()
}
onClickNextButton = () => {
app.cores.player.playback.next()
}
toggleDancer = () => {
this.setState({ showDancer: !this.state.showDancer })
}
render() {
return <div
className={classnames(
"embbededMediaPlayerWrapper",
{
["hovering"]: this.props.frame !== false && this.state.showControls,
["minimized"]: !app.isMobile && this.context.minimized,
["no-frame"]: this.props.frame === false,
["minimal"]: this.props.minimal,
}
)}
onMouseEnter={this.onMouse}
onMouseLeave={this.onMouse}
>
{
!app.isMobile && <div className="top_controls">
<antd.Button
icon={<Icons.MdFirstPage />}
onClick={this.minimize}
shape="circle"
/>
{
!this.context.control_locked && !this.context.sync_mode && <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">
{
this.state.showDancer && <MemeDancer />
}
<ServiceIndicator
service={this.context.track_manifest?.service}
/>
<div
className="cover"
style={{
backgroundImage: `url(${(this.context.track_manifest?.cover ?? this.context.track_manifest?.thumbnail) ?? "/assets/no_song.png"})`,
}}
/>
<div className="header">
<div className="info">
<div className="title">
<h2 onDoubleClick={this.toggleDancer}>
{
this.context.track_manifest?.title
? this.context.track_manifest?.title
: (this.context.loading ? "Loading..." : (this.context.track_manifest?.metadata?.title ?? "Untitled"))
}
</h2>
</div>
<div className="subTitle">
{
this.context.track_manifest?.metadata?.artist && <div className="artist">
<h3>
{this.context.track_manifest?.metadata?.artist ?? "Unknown"}
</h3>
</div>
}
{
!app.isMobile && this.context.playback_status !== "stopped" && <LikeButton
//onClick={app.cores.player.toggleCurrentTrackLike}
liked={this.context.track_manifest?.metadata?.liked}
/>
}
</div>
</div>
</div>
<Controls
syncModeLocked={this.context.control_locked}
syncMode={this.context.sync_mode}
playbackStatus={this.context.playback_status}
audioMuted={this.context.muted}
audioVolume={this.context.volume}
onVolumeUpdate={this.updateVolume}
onMuteUpdate={this.toggleMute}
controls={{
previous: this.onClickPreviousButton,
toggle: this.onClickPlayButton,
next: this.onClickNextButton,
//like: app.cores.player.toggleCurrentTrackLike,
}}
liked={this.context.track_manifest?.metadata?.liked}
/>
<SeekBar
stopped={this.context.playbackStatus === "stopped"}
playing={this.context.playbackStatus === "playing"}
streamMode={this.context.streamMode}
disabled={this.context.syncModeLocked}
/>
</div>
</div>
}
}

View File

@ -1,277 +0,0 @@
@top_controls_height: 55px;
html {
&.mobile {
.embbededMediaPlayerWrapper {
.player {
.service_indicator {
background-color: var(--background-color-accent);
font-size: 0.9rem;
}
.cover {
width: 100%;
height: 100%;
min-height: 40vh;
min-width: 100%;
}
}
}
}
}
.embbededMediaPlayerWrapper {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
height: fit-content;
pointer-events: initial;
transition: all 150ms ease-in-out;
&.no-frame {
width: 100%;
height: 100%;
flex-direction: column;
background-color: transparent;
.player {
background-color: transparent;
border-radius: 0;
box-shadow: none;
}
.top_controls {
position: relative;
opacity: 1;
height: @top_controls_height;
width: 100%;
background-color: transparent;
box-shadow: none;
}
}
&.minimized {
pointer-events: none;
animation: minimize 150ms ease-in-out forwards;
}
&.hovering {
.top_controls {
height: @top_controls_height;
opacity: 1;
// translate y negative height of @top_controls_height minus 10px
transform: translateY(calc(-1 * (@top_controls_height - 5px)));
}
margin-top: 45px;
}
.top_controls {
position: absolute;
top: 0;
z-index: 300;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 20px;
width: 100%;
height: 0px;
background-color: var(--background-color-accent);
border-radius: 8px 8px 0 0;
padding: 10px 10px 15px 10px;
opacity: 0;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
transition: all 150ms ease-in-out;
}
.player {
position: relative;
z-index: 310;
display: inline-flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
height: 100%;
//padding: 10px;
gap: 10px;
border-radius: 8px;
background-color: var(--background-color-accent);
// animate in from bottom to top
animation: fadeIn 150ms ease-out forwards;
transition: all 150ms ease-out;
//box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
.service_indicator {
color: var(--text-color);
background-color: var(--background-color-primary);
padding: 7px;
border-radius: 8px;
font-size: 0.8rem;
}
.cover {
position: relative;
z-index: 320;
margin: auto;
width: 250px;
height: 250px;
min-height: 250px;
border-radius: 10px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
transition: all 0.3s ease-in-out;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
}
.header {
position: relative;
display: flex;
flex-direction: row;
width: 100%;
.info {
display: flex;
flex-direction: column;
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
margin: 0;
color: var(--text-color);
}
width: 100%;
.title {
font-size: 1rem;
font-weight: 600;
color: var(--text-color);
word-break: break-all;
font-family: "Space Grotesk", sans-serif;
}
.subTitle {
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-between;
.likeButton {
margin-right: 20px;
}
.artist {
font-size: 0.6rem;
font-weight: 400;
color: var(--text-color);
}
}
}
}
}
}
.meme_dancer {
width: 100%;
video {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 12px;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes minimize {
0% {
opacity: 1;
}
99% {
opacity: 0;
//transform: translateX(100%);
}
100% {
opacity: 0;
height: 0px;
}
}

View File

@ -1,12 +1,131 @@
import React from "react"
import MediaPlayer from "components/Player/MediaPlayer"
import { Button } from "antd"
import { Icons } from "components/Icons"
import classnames from "classnames"
import RGBStringToValues from "utils/rgbToValues"
import SeekBar from "components/Player/SeekBar"
import Controls from "components/Player/Controls"
import { WithPlayerContext, Context } from "contexts/WithPlayerContext"
import "./index.less"
export default () => {
return <div className="__mobile-player-view">
<MediaPlayer
frame={false}
/>
const ServiceIndicator = (props) => {
if (!props.service) {
return null
}
switch (props.service) {
case "tidal": {
return <div className="service_indicator">
<Icons.SiTidal /> Playing from Tidal
</div>
}
default: {
return null
}
}
}
const AudioPlayer = (props) => {
return <WithPlayerContext>
<AudioPlayerComponent
{...props}
/>
</WithPlayerContext>
}
const AudioPlayerComponent = (props) => {
const ctx = React.useContext(Context)
React.useEffect(() => {
if (app.currentDragger) {
app.currentDragger.setBackgroundColorValues(RGBStringToValues(ctx.track_manifest?.cover_analysis?.rgb))
}
}, [ctx.track_manifest?.cover_analysis])
const {
title,
album,
artist,
service,
lyricsEnabled,
cover_analysis,
cover,
} = ctx.track_manifest ?? {}
const playing = ctx.playback_status === "playing"
const stopped = ctx.playback_status === "stopped"
const titleText = (!playing && stopped) ? "Stopped" : (title ?? "Untitled")
const subtitleText = `${artist} | ${album?.title ?? album}`
return <div
className={classnames(
"mobile_media_player_wrapper",
{
"cover_light": cover_analysis?.isLight,
}
)}
style={{
"--cover_isLight": cover_analysis?.isLight,
}}
>
<div className="mobile_media_player">
<ServiceIndicator
service={service}
/>
<div
className="cover"
style={{
backgroundImage: `url(${cover ?? "/assets/no_song.png"})`,
}}
/>
<div className="header">
<div className="info">
<div className="title">
<h2>
{
titleText
}
</h2>
</div>
<div className="subTitle">
<div className="artist">
<h3>
{subtitleText}
</h3>
</div>
</div>
</div>
</div>
<Controls />
<SeekBar
stopped={ctx.playback_status === "stopped"}
playing={ctx.playback_status === "playing"}
streamMode={ctx.livestream_mode}
disabled={ctx.control_locked}
/>
<div className="extra_actions">
<Button
type="ghost"
icon={<Icons.MdLyrics />}
disabled={!lyricsEnabled}
/>
<Button
type="ghost"
icon={<Icons.MdQueueMusic />}
/>
</div>
</div>
</div>
}
export default AudioPlayer

View File

@ -1,8 +1,199 @@
.__mobile-player-view {
@top_controls_height: 55px;
.mobile_media_player_wrapper {
position: relative;
z-index: 320;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
.mobile_media_player_background {
position: absolute;
z-index: 320;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(var(--cover_averageValues), 0.4);
}
}
.mobile_media_player {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
height: 100%;
gap: 10px;
transition: all 150ms ease-out;
z-index: 330;
.service_indicator {
color: var(--text-color);
background-color: var(--background-color-accent);
padding: 7px;
border-radius: 8px;
font-size: 0.9rem;
}
.cover {
position: relative;
z-index: 320;
margin: auto;
width: 100%;
height: 100%;
min-height: 40vh;
min-width: 100%;
border-radius: 24px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
transition: all 0.3s ease-in-out;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
}
.header {
position: relative;
display: flex;
flex-direction: row;
width: 100%;
.info {
display: flex;
flex-direction: column;
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
margin: 0;
color: var(--text-color);
}
width: 100%;
.title {
font-size: 1rem;
font-weight: 600;
color: var(--text-color);
word-break: break-all;
font-family: "Space Grotesk", sans-serif;
}
.subTitle {
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-between;
.likeButton {
margin-right: 20px;
}
.artist {
font-size: 0.6rem;
font-weight: 400;
color: var(--text-color);
}
}
}
}
.player-controls {
.ant-btn {
min-width: 40px !important;
min-height: 40px !important;
}
svg {
font-size: 1.2rem;
}
.playButton {
min-width: 50px !important;
min-height: 50px !important;
svg {
font-size: 1.6rem;
}
}
}
.player-seek_bar {
.progress {
.MuiSlider-root {
.MuiSlider-rail {
height: 7px;
}
.MuiSlider-track {
height: 7px;
}
.MuiSlider-thumb {
width: 5px;
height: 13px;
border-radius: 2px;
background-color: var(--background-color-contrast);
}
}
}
}
.extra_actions {
padding: 5px 30px;
.ant-btn {
padding: 5px;
svg {
height: 23px;
min-width: 23px;
}
}
}
}