Skip to content

Commit

Permalink
Mobile Audio Player (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
thabti authored Dec 31, 2016
1 parent c0d6c04 commit 73b8c31
Show file tree
Hide file tree
Showing 15 changed files with 579 additions and 167 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ dist/
webpack-assets.json
webpack-stats.json
npm-debug.log
npm-debug.log.*
npm-debug.log.*
.vscode
2 changes: 1 addition & 1 deletion api/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"username": "quran_dev",
"password": null,
"database": "audio_quran",
"host": "127.0.0.1",
"host": "localhost",
"dialect": "postgres"
},
"test": {
Expand Down
85 changes: 85 additions & 0 deletions src/components/Audioplayer/CommonAudio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
export default class CommonAudio {

static handleTrackChange(fraction) {
const { file, update } = this.props; // eslint-disable-line no-shadow

update({
progress: fraction * 100,
currentTime: fraction * file.duration
});

file.currentTime = fraction * file.duration;
}

static handleFileLoad(file) {
const { update } = this.props; // eslint-disable-line no-shadow

// Preload file
file.setAttribute('preload', 'auto');

const onLoadeddata = () => {
// Default current time to zero. This will change
file.currentTime = 0; // eslint-disable-line no-param-reassign

update({
duration: file.duration
});
};

const onTimeupdate = () => {
const progress = (
file.currentTime /
file.duration * 100
);

update({
progress,
currentTime: file.currentTime,
isPlaying: !file.paused
});
};

const onEnded = () => {
const { shouldRepeat, shouldContinuous, shouldRandom} = this.props;

if (shouldRepeat) {
file.pause();
file.currentTime = 0; // eslint-disable-line no-param-reassign
file.play();
} else if (shouldContinuous) {
const { surah, surahs, qari } = this.props; // eslint-disable-line no-shadow
this.props.load({ surah: Object.values(surahs)[surah.id], qari: qari });
} else if (shouldRandom) {
const {surahs, qari } = this.props; // eslint-disable-line no-shadow
const randomSurah = Math.floor(Math.random() * (113 + 1));
this.props.load({ surah: Object.values(surahs)[randomSurah], qari: qari });
} else {
if (file.readyState >= 3 && file.paused) {
file.pause();
}

update({
surah: null,
isPlaying: false
});
}
};

const onPlay = () => { };

file.onloadeddata = onLoadeddata;
file.ontimeupdate = onTimeupdate;
file.onplay = onPlay;
file.onended = onEnded;
}

static handleRemoveFileListeneres(file) {
if (file) {
file.pause();
file.onloadeddata = null;
file.ontimeupdate = null;
file.onplay = null;
file.onended = null;
}
}
}
201 changes: 201 additions & 0 deletions src/components/Audioplayer/MobilePlayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import Track from './Track';
import Common from './CommonAudio';
import {
load,
play,
pause,
playPause,
update,
repeat,
previous,
next,
continuous,
random
} from 'actions/audioplayer';

import formatSeconds from 'utils/formatSeconds';
import style from './mobile.scss';
class MobilePlayer extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
surahs: PropTypes.object.isRequired,
qaris: PropTypes.object,
surah: PropTypes.shape({
id: PropTypes.number.isRequired,
ayat: PropTypes.number.isRequired,
bismillahPre: PropTypes.bool.isRequired,
name: PropTypes.object.isRequired,
revelation: PropTypes.object.isRequired,
page: PropTypes.array.isRequired
}),
playPause: PropTypes.func.isRequired,
update: PropTypes.func.isRequired,
repeat: PropTypes.func.isRequired,
continuous: PropTypes.func.isRequired,
random: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
previous: PropTypes.func.isRequired,
file: PropTypes.object,
qari: PropTypes.object,
isPlaying: PropTypes.bool.isRequired,
shouldRepeat: PropTypes.bool.isRequired,
shouldContinuous: PropTypes.bool.isRequired,
shouldRandom: PropTypes.bool.isRequired,
surahPage: PropTypes.bool,
progress: PropTypes.number,
currentTime: PropTypes.number,
duration: PropTypes.number
};

constructor(props) {
super(props);
this.handleTrackChange = Common.handleTrackChange.bind(this);
this.handleFileLoad = Common.handleFileLoad.bind(this);
this.handleRemoveFileListeneres = Common.handleRemoveFileListeneres.bind(this);
}

state = {
open: false
};
componentWillReceiveProps(nextProps) {
if (this.props.surah !== nextProps.surah || this.props.qari !== nextProps.qari) {
this.handleFileLoad(nextProps.file);
this.handleRemoveFileListeneres(this.props.file);
}
}

renderPlayStopButtons() {
const { isPlaying, playPause, file } = this.props; // eslint-disable-line no-shadow
if (file.readyState < 4) {
return (<i className={`text-primary pointer loading is-loading ${style.isLoading}`}></i>);
}

if (isPlaying && file.readyState >= 4) {
return <i onClick={playPause} className={`text-primary pointer fa fa-pause-circle ${!file && style.disabled} ${style.playPause}`} />;
}

return <i onClick={playPause} className={`text-primary pointer fa fa-play-circle ${!file && style.disabled} ${style.playPause}`} />;
}

renderPreviousButton() {
const { previous, surah, surahs, qaris, surahPage, qari } = this.props; // eslint-disable-line no-shadow
const disableBasedOnSurah = surah ? surah.id === 114 && true : true;
const disabled = surahPage ? qari.id === Object.keys(qaris).length : disableBasedOnSurah;

return (
<i
onClick={() => !disabled && previous({ surahs: Object.values(surahs) })}
className={`pointer fa fa-fast-backward fa-lg ${disabled && style.disabled} ${style.previous}`}
/>
);
}
renderNextButton() {
const { next, surah, surahs, surahPage, qari } = this.props; // eslint-disable-line no-shadow
const disableBasedOnSurah = surah ? surah.id === 1 && true : true;
const disabled = surahPage ? qari.id === 1 && true : disableBasedOnSurah;

return (
<i
onClick={() => !disabled && next({ surahs: Object.values(surahs) })}
className={`pointer fa fa-fast-forward fa-lg ${disabled && style.disabled} ${style.next}`}
/>
);
}

renderRepeatButton() {
const { shouldRepeat, repeat } = this.props; // eslint-disable-line no-shadow

return (
<div className={`${style.toggle} ${shouldRepeat && style.active}`}>
<input type="checkbox" id="repeat" className="hidden" />
<label
htmlFor="repeat"
className={`pointer`}
onClick={repeat}
>
<i className={`fa fa-repeat ${style.repeat}`} />
</label>
</div>
);
}
renderRandomButton() {
const { shouldRandom, random } = this.props; // eslint-disable-line no-shadow

return (
<div className={`${style.toggle} ${shouldRandom && style.active}`}>
<input type="checkbox" id="random" className="hidden" />
<label
htmlFor="repeat"
className={`pointer`}
onClick={random}
>
<i className={`fa fa-random ${style.random}`} />
</label>
</div>
);
}

render() {
const { qari, surah, progress, file } = this.props; // eslint-disable-line no-shadow, react/prop-types
const { open } = this.state;
const isOpen = this.state.open ? style.active : false;
const validFileTiming = file && !isNaN(file.duration);
if (!surah) return false;
const openPlayer = () => {
if (open) {
document.querySelector('#content').style.overflowY = 'hidde';
} else {
document.querySelector('#content').style.overflowY = 'auto';
}
this.setState({ open: !open });
};
return (
<div className={`${style.audioplayer} ${isOpen}`}>
<i onClick={openPlayer} className={`fa fa-chevron-up ${style.chevron}`} aria-hidden="true"></i>
<h2 className={style.qariName}>{qari && qari.name}</h2>
<h3 className={style.surahName}>{`Surat ${surah.name.simple}`}</h3>
{open && <div className={style.controlersContainer}>
<div className={style.surahMisc}>
<p> سورة {surah.name.arabic}</p>
</div>
<div className={style.controls}>
<ul className={style.timeline}>
<li className={style.timelineItem}>{validFileTiming ? formatSeconds(file.currentTime) : '00:00'}</li>
<li className={style.track}><Track simple={false} progress={validFileTiming ? progress : 0} onTrackChange={this.handleTrackChange} style={{ background: '#5b5b5b' }} /></li>
<li className={style.timelineItem}>{validFileTiming ? formatSeconds(file.duration) : '00:00'}</li>
</ul>
<div className={style.controlActions}>
{this.renderRandomButton()}
{this.renderPreviousButton()}
{this.renderPlayStopButtons()}
{this.renderNextButton()}
{this.renderRepeatButton()}
</div>
</div>
</div>
}
</div>
);
}
}

export default connect(
state => ({
file: state.audioplayer.file,
surahs: state.surahs.entities,
qari: state.audioplayer.qari,
qaris: state.audioplayer.qaris,
surah: state.audioplayer.surah,
progress: state.audioplayer.progress,
duration: state.audioplayer.duration,
currentTime: state.audioplayer.currentTime,
isPlaying: state.audioplayer.isPlaying,
shouldRepeat: state.audioplayer.shouldRepeat,
surahPage: state.audioplayer.surahPage,
shouldContinuous: state.audioplayer.shouldContinuous,
shouldRandom: state.audioplayer.shouldRandom,
}),
{ load, play, pause, playPause, update, repeat, next, previous, continuous, random }
)(MobilePlayer);
9 changes: 5 additions & 4 deletions src/components/Audioplayer/Track/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export default class Track extends Component {
progress: PropTypes.number.isRequired,
onTrackChange: PropTypes.func,
simple: PropTypes.bool,
style: PropTypes.object
style: PropTypes.object,
progressStyle: PropTypes.object
};

handleClick = (event) => {
Expand All @@ -30,14 +31,14 @@ export default class Track extends Component {
}

render() {
const { progress, simple, style } = this.props;

const { progress, simple, style, progressStyle} = this.props;
const progressStyleObject = Object.assign({}, { width: `${progress}%` }, progressStyle);
return (
<div ref="container" className={styles.container}
onMouseEnter={this.handleMouse}
onMouseLeave={this.handleMouse}
onClick={this.handleClick} style={style}>
<div className={`${styles.progress} ${simple ? styles.simple : ''}`} style={{ width: `${progress}%` }} />
<div className={`${styles.progress} ${simple ? styles.simple : ''}`} style={progressStyleObject} />
</div>
);
}
Expand Down
7 changes: 6 additions & 1 deletion src/components/Audioplayer/Track/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "../../../styles/variables.scss";

.container{
height: 7px;
width: 100%;
Expand Down Expand Up @@ -30,11 +32,14 @@
top: -4px;
box-shadow: 0 1px 2px rgba(0,0,0,0.45);
transition:opacity 0.1s ease-in-out;
@include mobile() {
opacity: 1;
top: -6px;
}
}
}

.activeHover .progress:after {

opacity:1;
}

Expand Down
Loading

0 comments on commit 73b8c31

Please sign in to comment.