feat(timer): adds more songs

Adds 2 more songs to the timer, which can be switched using the radio
buttons inside timer panel.
Changed the music loop logic to make it gapless.
This commit is contained in:
Arthurk12 2022-02-01 19:05:02 +00:00
parent 529a5a6b10
commit f5a5a960ba
16 changed files with 179 additions and 74 deletions

View File

@ -4,11 +4,22 @@ const TIMER_CONFIG = Meteor.settings.public.timer;
const MILLI_IN_MINUTE = 60000;
const TRACKS = [
'noTrack',
'track1',
'track2',
'track3',
];
const isEnabled = () => TIMER_CONFIG.enabled;
const getDefaultTime = () => TIMER_CONFIG.time * MILLI_IN_MINUTE;
const isTrackValid = (track) => TRACKS.includes(track);
export {
TRACKS,
isEnabled,
getDefaultTime,
isTrackValid,
};

View File

@ -7,7 +7,7 @@ import stopTimer from './methods/stopTimer';
import switchTimer from './methods/switchTimer';
import setTimer from './methods/setTimer';
import getServerTime from './methods/getServerTime';
import setMusic from './methods/setMusic';
import setTrack from './methods/setTrack';
import timerEnded from './methods/timerEnded';
Meteor.methods({
@ -19,6 +19,6 @@ Meteor.methods({
switchTimer,
setTimer,
getServerTime,
setMusic,
setTrack,
timerEnded,
});

View File

@ -1,15 +0,0 @@
import { check } from 'meteor/check';
import updateTimer from '/imports/api/timer/server/modifiers/updateTimer';
import { extractCredentials } from '/imports/api/common/server/helpers';
export default function setMusic(music) {
const { meetingId } = extractCredentials(this.userId);
check(meetingId, String);
//These are bogus values, won't update collection
const time = 0;
const stopwatch = false;
const accumulated = 0;
updateTimer('music', meetingId, time, stopwatch, accumulated, music);
}

View File

@ -0,0 +1,22 @@
import { check } from 'meteor/check';
import updateTimer from '/imports/api/timer/server/modifiers/updateTimer';
import Logger from '/imports/startup/server/logger';
import { extractCredentials } from '/imports/api/common/server/helpers';
import { isTrackValid } from '/imports/api/timer/server/helpers';
export default function setTrack(track) {
const { meetingId, requesterUserId } = extractCredentials(this.userId);
check(meetingId, String);
check(track, String);
//These are bogus values, won't update collection
const time = 0;
const stopwatch = false;
const accumulated = 0;
if (isTrackValid(track)) {
updateTimer('track', meetingId, time, stopwatch, accumulated, track);
} else {
Logger.warn(`User=${requesterUserId} tried to set invalid track '${track}' in meeting=${meetingId}`);
}
}

View File

@ -1,7 +1,7 @@
import { check } from 'meteor/check';
import Timer from '/imports/api/timer';
import Logger from '/imports/startup/server/logger';
import { getDefaultTime } from '/imports/api/timer/server/helpers';
import { TRACKS, getDefaultTime } from '/imports/api/timer/server/helpers';
// This method should only be used by the server
export default function addTimer(meetingId) {
@ -22,7 +22,7 @@ export default function addTimer(meetingId) {
time,
accumulated: 0,
timestamp: 0,
music: false,
track: TRACKS[0],
ended: 0,
};

View File

@ -2,7 +2,7 @@ import { check } from 'meteor/check';
import Timer from '/imports/api/timer';
import Logger from '/imports/startup/server/logger';
import Users from '/imports/api/users';
import { getDefaultTime } from '/imports/api/timer/server/helpers';
import { TRACKS, getDefaultTime } from '/imports/api/timer/server/helpers';
const getActivateModifier = () => {
const time = getDefaultTime();
@ -16,7 +16,7 @@ const getActivateModifier = () => {
time,
accumulated: 0,
timestamp: 0,
music: false,
track: TRACKS[0],
ended: 0,
},
};
@ -103,7 +103,7 @@ const getSwitchModifier = (stopwatch) => {
running: false,
accumulated: 0,
timestamp: 0,
music: false,
track: TRACKS[0],
ended: 0,
},
};
@ -120,10 +120,10 @@ const getSetModifier = (time) => {
};
};
const getMusicModifier = (music) => {
const getTrackModifier = (track) => {
return {
$set: {
music,
track,
},
};
};
@ -136,13 +136,13 @@ const getEndedModifier = () => {
};
};
export default function updateTimer(action, meetingId, time = 0, stopwatch = true, accumulated = 0, music = false) {
export default function updateTimer(action, meetingId, time = 0, stopwatch = true, accumulated = 0, track = TRACKS[0]) {
check(action, String);
check(meetingId, String);
check(time, Number);
check(stopwatch, Boolean);
check(accumulated, Number);
check(music, Boolean);
check(track, String);
const selector = {
meetingId,
@ -173,8 +173,8 @@ export default function updateTimer(action, meetingId, time = 0, stopwatch = tru
case 'set':
modifier = getSetModifier(time);
break;
case 'music':
modifier = getMusicModifier(music);
case 'track':
modifier = getTrackModifier(track);
break;
case 'ended':
modifier = getEndedModifier();

View File

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import Service from './service';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import Toggle from '/imports/ui/components/common/switch/component';
import Styled from './styles';
const intlMessages = defineMessages({
@ -47,9 +46,25 @@ const intlMessages = defineMessages({
id: 'app.timer.seconds',
description: 'Timer seconds label',
},
music: {
id: 'app.timer.music',
description: 'Music toggle label',
songs: {
id: 'app.timer.songs',
description: 'Songs title label',
},
noTrack: {
id: 'app.timer.noTrack',
description: 'No track radio label',
},
track1: {
id: 'app.timer.track1',
description: 'Track 1 radio label',
},
track2: {
id: 'app.timer.track2',
description: 'Track 2 radio label',
},
track3: {
id: 'app.timer.track3',
description: 'Track 3 radio label',
},
});
@ -60,6 +75,10 @@ const propTypes = {
};
class Timer extends Component {
static handleOnTrackChange(event) {
Service.setTrack(event.target.value);
}
constructor(props) {
super(props);
@ -192,11 +211,6 @@ class Timer extends Component {
}
}
handleOnMusicChange() {
const { isMusicActive } = this.props;
Service.setMusic(!isMusicActive);
}
renderControls() {
const {
intl,
@ -223,11 +237,11 @@ class Timer extends Component {
);
}
renderToggle() {
renderSongSelectorRadios() {
const {
intl,
timer,
isMusicActive,
currentTrack,
} = this.props;
const {
@ -237,20 +251,27 @@ class Timer extends Component {
return (
<Styled.TimerSongsWrapper>
<Styled.TimerRow>
<Styled.TimerCol
<Styled.TimerSongTitle
disabled={stopwatch}
>
{intl.formatMessage(intlMessages.music)}
</Styled.TimerCol>
<Styled.TimerCol>
<Toggle
onChange={() => this.handleOnMusicChange()}
icons={false}
disabled={stopwatch}
checked={isMusicActive}
/>
</Styled.TimerCol>
{intl.formatMessage(intlMessages.songs)}
</Styled.TimerSongTitle>
</Styled.TimerRow>
<Styled.TimerOptionsWrapper
disabled={stopwatch}
>
<Styled.TimerSongsContainer>
{Service.TRACKS.map((track) => (
<Styled.TimerRow>
<label htmlFor={track}>
<input type="radio" name="track" id={track} value={track} checked={currentTrack === track} onChange={Timer.handleOnTrackChange} />
{intl.formatMessage(intlMessages[track])}
</label>
</Styled.TimerRow>
))
}
</Styled.TimerSongsContainer>
</Styled.TimerOptionsWrapper>
</Styled.TimerSongsWrapper>
);
}
@ -308,7 +329,7 @@ class Timer extends Component {
/>
</Styled.StopwatchTime>
{ Service.isMusicEnabled()
? this.renderToggle() : null}
? this.renderSongSelectorRadios() : null}
{this.renderControls()}
</div>
);

View File

@ -21,6 +21,6 @@ export default withTracker(() => {
isModerator: Service.isModerator(),
timeOffset: Service.getTimeOffset(),
timer: Service.getTimer(),
isMusicActive: Service.isMusicActive(),
currentTrack: Service.getCurrentTrack(),
};
})(TimerContainer);

View File

@ -6,7 +6,7 @@ import Styled from './styles';
const CDN = Meteor.settings.public.app.cdn;
const BASENAME = Meteor.settings.public.app.basename;
const HOST = CDN + BASENAME;
const trackName = Meteor.settings.public.timer.music.track;
const trackName = Meteor.settings.public.timer.music;
class Indicator extends Component {
constructor(props) {
@ -71,12 +71,21 @@ class Indicator extends Component {
}
setUpMusic() {
this.music = new Audio(`${HOST}/resources/sounds/${trackName}.mp3`);
this.music.volume = TimerService.getMusicVolume();
this.music.addEventListener('ended', () => {
this.music.currentTime = 0;
this.music.play();
}, false);
const { currentTrack } = this.props;
if (trackName[currentTrack] !== undefined) {
this.music = new Audio(`${HOST}/resources/sounds/${trackName[currentTrack]}.mp3`);
this.music.track = currentTrack;
this.music.volume = TimerService.getMusicVolume();
this.music.addEventListener('timeupdate', () => {
const buffer = 0.19;
// Start playing the music before it ends to make the loop gapless
if (this.music.currentTime > this.music.duration - buffer) {
this.music.currentTime = 0;
this.music.play();
}
});
}
}
updateAlarmTrigger(prevTimer, timer) {
@ -196,19 +205,22 @@ class Indicator extends Component {
}
shouldStopMusic(time) {
const { timer } = this.props;
const {
timer,
isMusicActive,
} = this.props;
const {
running,
stopwatch,
} = timer;
const isActive = TimerService.isMusicActive();
const zero = time === 0;
const validMusic = this.music != null;
const reachedZeroOrStopped = (running && zero) || (!running);
return !isActive || !validMusic || stopwatch || reachedZeroOrStopped;
return !isMusicActive || !validMusic || stopwatch || reachedZeroOrStopped;
}
shoulNotifyTimerEnded(time) {
@ -275,12 +287,17 @@ class Indicator extends Component {
}
render() {
const { isTimerActive } = this.props;
const { isTimerActive, currentTrack } = this.props;
if (!isTimerActive) {
this.stopMusic();
return null;
}
if (this.music?.track !== currentTrack) {
this.stopMusic();
this.setUpMusic();
}
const {
isModerator,
hidden,

View File

@ -13,5 +13,6 @@ export default withTracker(() => ({
isModerator: TimerService.isModerator(),
isTimerActive: TimerService.isActive(),
isMusicActive: TimerService.isMusicActive(),
currentTrack: TimerService.getCurrentTrack(),
hidden: Session.get('openPanel') !== '',
}))(IndicatorContainer);

View File

@ -22,12 +22,21 @@ const MAX_TIME = 999
+ (59 * MILLI_IN_MINUTE)
+ (MAX_HOURS * MILLI_IN_HOUR);
const TRACKS = [
'noTrack',
'track1',
'track2',
'track3',
];
const getMaxHours = () => MAX_HOURS;
const isAlarmEnabled = () => isEnabled() && TIMER_CONFIG.alarm;
const isMusicEnabled = () => TIMER_CONFIG.music.enabled;
const isMusicActive = () => getCurrentTrack() !== TRACKS[0];
const getMusicVolume = () => TIMER_CONFIG.music.volume;
const getMusicTrack = () => TIMER_CONFIG.music.track;
@ -42,13 +51,13 @@ const isActive = () => {
return false;
};
const isMusicActive = () => {
const getCurrentTrack = () => {
const timer = Timer.findOne(
{ meetingId: Auth.meetingID },
{ fields: { music: 1 } },
{ fields: { track: 1 } },
);
if (timer) return isMusicEnabled() && timer.music;
if (timer) return isMusicEnabled() && timer.track;
return false;
};
@ -104,8 +113,8 @@ const deactivateTimer = () => makeCall('deactivateTimer');
const timerEnded = () => makeCall('timerEnded');
const setMusic = (music) => {
makeCall('setMusic', music);
const setTrack = (track) => {
makeCall('setTrack', track);
};
const fetchTimeOffset = () => {
@ -302,10 +311,12 @@ const setSeconds = (seconds, time) => {
export default {
OFFSET_INTERVAL,
TRACKS,
isActive,
isEnabled,
isMusicEnabled,
isMusicActive,
getCurrentTrack,
getMusicVolume,
getMusicTrack,
isRunning,
@ -321,7 +332,7 @@ export default {
activateTimer,
deactivateTimer,
fetchTimeOffset,
setMusic,
setTrack,
getTimeOffset,
getElapsedTime,
getInterval,

View File

@ -158,7 +158,7 @@ const TimerControlButton = styled(Button)`
const TimerSongsWrapper = styled.div`
display: flex;
flex-flow: column;
margin-top: 2rem;
margin-top: 4rem;
margin-bottom: -2rem;
`;
@ -168,12 +168,40 @@ const TimerRow = styled.div`
flex-grow: 1;
`;
const TimerCol = styled.div`
const TimerCol = `
display: flex;
flex-grow: 1;
flex-basis: 0;
justify-content: center;
${({ disabled }) => disabled && 'opacity: 50%'}
align-items: center;
`;
const TimerSongTitle = styled.div`
${TimerCol}
font-weight: bold;
font-size: 1.1rem;
opacity: ${({ disabled }) => (disabled ? '50%' : '100%')}
`;
const TimerOptionsWrapper = styled.div`
${TimerCol}
display: flex;
flex-flow: column;
margin-top: 0.8rem;
margin-bottom: 2rem;
label {
display: flex;
}
input {
margin: auto 0.5rem;
}
opacity: ${({ disabled }) => (disabled ? '50%' : '100%')}
`;
const TimerSongsContainer = styled.div`
width: 15rem;
`;
export default {
@ -193,6 +221,9 @@ export default {
TimerControls,
TimerControlButton,
TimerSongsWrapper,
TimerSongTitle,
TimerRow,
TimerCol,
TimerOptionsWrapper,
TimerSongsContainer,
};

View File

@ -519,7 +519,9 @@ public:
music:
enabled: false
volume: 0.4
track: "pianoChords"
track1: "pianoChords"
track2: "littleBossa"
track3: "aristocratDrums"
interval:
clock: 100
offset: 60000

View File

@ -66,7 +66,11 @@
"app.timer.hours": "hours",
"app.timer.minutes": "minutes",
"app.timer.seconds": "seconds",
"app.timer.music": "Music",
"app.timer.songs": "Songs",
"app.timer.noTrack": "No song",
"app.timer.track1": "Relaxing",
"app.timer.track2": "Calm",
"app.timer.track3": "Happy",
"app.captions.label": "Captions",
"app.captions.menu.close": "Close",
"app.captions.menu.start": "Start",