Skip to content

Commit

Permalink
Merge pull request #14 from Muzikie/5-implement-audio-streaming-logic
Browse files Browse the repository at this point in the history
Implement audio streaming logic - Closes #5
  • Loading branch information
haghighatcs committed Nov 19, 2022
2 parents a6ecd90 + bc299c1 commit b52491f
Show file tree
Hide file tree
Showing 35 changed files with 337 additions and 281 deletions.
6 changes: 3 additions & 3 deletions app/components/Entity/EntityRow/Wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { memo, ReactNode, MouseEvent, useContext } from 'react';
import { PlayerContext } from '~/context/playerContextProvider';
import { PlayerContext } from '~/context/playerContext/playerContextProvider';
import { Link } from '@remix-run/react';
import { Entity, TrackType } from '../types';

Expand All @@ -14,7 +14,7 @@ interface WrapperProps {
const Wrapper = ({
entity, data, children, className,
}: WrapperProps) => {
const { setCurrent, current } = useContext(PlayerContext);
const { setCurrent } = useContext(PlayerContext);

if (entity === 'track') {
const play = (e: MouseEvent) => {
Expand All @@ -27,7 +27,7 @@ const Wrapper = ({
return (
<section
onClick={play}
className={`${className} ${current?.id === data.id ? 'isPlaying' : ''}`}
className={className}
>
{children}
</section>
Expand Down
11 changes: 6 additions & 5 deletions app/components/Entity/EntityThumbnail/entityThumbnail.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.component.entity.thumbnail {
--size-xs: 59px;
--size-s: 62px;
--size-m: 70px;
--size-l: 80px;
Expand Down Expand Up @@ -42,20 +43,20 @@
}
}

&.empty,
&.artist {
& figure {
border-radius: var(--border-radius-xs);
}
}
}

.circle {
&.component.entity.thumbnail {
&.track {
& figure {
width: 59px;
height: 59px;
width: var(--size-xs);
height: var(--size-xs);
border-radius: var(--border-radius-r);
border: solid 2px var(--white);
box-shadow: 0 8px 8px 0 var(--color-primary-opaque);
}
}
}
3 changes: 2 additions & 1 deletion app/components/Entity/EntityThumbnail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';

import { EntityRowProps, Entity, entityThemes } from '../types';
import { getEntity } from '../utils';

Expand All @@ -12,7 +13,7 @@ const EntityThumbnail = ({
return (
<section className={wrapper}>
<figure>
<img src={data.image} alt={ data.name } />
<img src={data.image} alt={data.name} />
</figure>
</section>
);
Expand Down
2 changes: 1 addition & 1 deletion app/components/Logo/Extended.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ExtendedLogo = () => (
<div className="component logo extended">
<Link to="/">
<figure>
<img src="./images/logo.svg" alt="Muzikie" />
<img src="/images/logo.svg" alt="Muzikie" />
</figure>
<h1>Muzikie</h1>
<sub>Decentralized Music streaming</sub>
Expand Down
23 changes: 23 additions & 0 deletions app/components/MainHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* External dependencies */
import React from 'react';
import { useLocation } from 'react-router-dom';

/* Internal dependencies */
import ExtendedLogo from '../Logo/Extended';
import MainMenu from '../MainMenu';

const MainHeader = () => {
const { pathname } = useLocation();
const pageType = pathname === '/login' || pathname === '/register' ? 'public' : 'private';

return (
<header className={`component mainHeader ${pageType}`}>
<div className="container">
<ExtendedLogo />
<MainMenu />
</div>
</header>
);
};

export default MainHeader;
22 changes: 22 additions & 0 deletions app/components/MainHeader/mainHeader.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.component.mainHeader {
width: 100vw;
height: 70px;
padding: 20px 28px 0;
box-sizing: border-box;

& .container {
display: flex;
justify-content: space-between;
flex-flow: row nowrap;
align-items: center;
}

&.public {
margin-bottom: -70px;
}

& .component.logo.extended {
position: relative;
z-index: 9;
}
}
2 changes: 1 addition & 1 deletion app/components/MainMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useContext } from 'react';

import { IconLink } from '~/components/common/Link';
import { IconButton } from '~/components/common/Button';
import { ProfileContext } from '~/context/profileContextProvider';
import { ProfileContext } from '~/context/profileContext/profileContextProvider';

const MainMenu = () => {
const [isActive, setIsActive] = useState(false);
Expand Down
3 changes: 0 additions & 3 deletions app/components/MainMenu/mainMenu.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@

height: var(--menu-button);
height: var(--menu-button);
position: absolute;
top: 50px;
right: var(--padding-m);
box-sizing: border-box;
z-index: 5;

Expand Down
7 changes: 7 additions & 0 deletions app/components/Player/PlaceHolderImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const PlaceHolderImage = () => (
<span className="component imagePlaceHolder" />
);

export default PlaceHolderImage;
31 changes: 22 additions & 9 deletions app/components/Player/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React from 'react';
import React, { ChangeEvent } from 'react';
import { ProgressBarProps } from './type';

const range = {
max: 100,
min: 0,
};

const zeroPad = (num: number) => num.toString().padStart(2, '0');

const formatCurrentTime = (currentTime: number) => {
Expand All @@ -14,18 +19,26 @@ const formatCurrentTime = (currentTime: number) => {
};

const updateProgress = (currentTime: number, duration: number) =>
duration > 0 ? `${(currentTime / duration) * 100}%` : '0%';
duration > 0 ? 100 * currentTime / duration : 0;

const ProgressBar = ({ progress, duration, setProgress }: ProgressBarProps) => {
const percentage = updateProgress(progress, duration);

const onSeek = (e: ChangeEvent<HTMLAudioElement>) => {
setProgress(e.target.value * duration / range.max);
};

const ProgressBar = ({ currentTime, duration }: ProgressBarProps) => {
const percentage = updateProgress(currentTime, duration);
return (
<section className="seek">
<div className="bar">
<span style={{ width: percentage }}></span>
<i style={{ left: percentage }}></i>
</div>
<input
type="range"
min={range.min}
max={range.max}
onChange={onSeek}
value={percentage}
/>
<time>
<span>{ formatCurrentTime(currentTime) }</span>
<span>{ formatCurrentTime(progress) }</span>
</time>
</section>
);
Expand Down
101 changes: 30 additions & 71 deletions app/components/Player/index.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,61 @@
import React, { useEffect, useRef, useState } from 'react';
import { useContext, MouseEvent } from 'react';
/* External dependencies */
import React, { useContext, useRef, MutableRefObject } from 'react';
import { Link } from '@remix-run/react';
import { PlayerContext } from '~/context/playerContextProvider';

/* Internal dependencies */
import { useAudio } from '~/hooks/useAudio/useAudio';
import { PlayerContext } from '~/context/playerContext/playerContextProvider';
import { ProfileContext } from '~/context/profileContext/profileContextProvider';
import { IconButton } from '~/components/common/Button';
import EntityThumbnail from '~/components/Entity/EntityThumbnail';
import { API_URLS } from '~/constants/api';
import PlaceHolderImage from './PlaceHolderImage';
import ProgressBar from './ProgressBar';

const Player = () => {
const audioRef = useRef() as MutableRefObject<HTMLAudioElement>;
const {
playPause,
onTimeUpdate,
progress,
setProgress,
} = useAudio(audioRef);
const {
current,
queue,
isPlaying,
setIsPlaying,
prevTrack,
nextTrack,
} = useContext(PlayerContext);
const [init, setInit] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const audio = useRef();

const updateProgress = () => {
setCurrentTime(audio.current.currentTime);
};

const playPause = (e: MouseEvent) => {
e.preventDefault();
if (!audio.current) {
console.log('No music is selected yet.');
} else {
if (audio.current.paused) {
audio.current.play();
} else {
audio.current.pause();
}
setIsPlaying(!audio.current.paused);
}
};

useEffect(() => {
if (init) {
audio.current.play();
setIsPlaying(true);
}
}, [current?.id]);

useEffect(() => {
audio.current.addEventListener('timeupdate', updateProgress);
setInit(true);
}, [audio.current]);

useEffect(() => () => {
audio.current.removeEventListener('timeupdate', updateProgress);
}, []);
const { info } = useContext(ProfileContext);

return (
<section className={`component player ${current ? 'playing' : ''}`}>
<section className={`component player ${current && info.address ? 'playing' : ''}`}>
<section className="playingMusic">
{
current && (
<Link to={`/album/${current.id}`}>
<EntityThumbnail data={current} className="circle" />
</Link>
)
}
<Link to={`/album/${current?.id ?? ''}`}>
{ current
? <EntityThumbnail data={current} />
: <PlaceHolderImage />
}
</Link>
<header>
<h5>{ current?.name ?? '-' }</h5>
<span>{ current?.artistName ?? '-' }</span>
<h5>{ current?.name ?? '...' }</h5>
<span>{ current?.artistName ?? '...' }</span>
</header>
</section>

<audio
src={`${API_URLS.STREAMER}/${current?.id}`}
ref={audio}
src={`${API_URLS.STREAMER}/${current?.id}?publicKey=${info.publicKey}`}
ref={audioRef}
onTimeUpdate={onTimeUpdate}
/>
<ProgressBar
duration={Number(current?.duration) ?? 0}
currentTime={currentTime}
progress={progress}
setProgress={setProgress}
/>

<section className="controls">
<IconButton
icon="rewind"
className="rewind"
onClick={prevTrack}
disabled={!prevTrack || queue.length === 0}
/>
<IconButton
icon={isPlaying ? 'pause' : 'play'}
className="play"
onClick={playPause}
/>
<IconButton
disabled={!nextTrack || queue.length === 0}
icon="fast-forward"
className="fastForward"
onClick={prevTrack}
/>
</section>
</section>
);
Expand Down
Loading

0 comments on commit b52491f

Please sign in to comment.