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:
parent
529a5a6b10
commit
f5a5a960ba
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
@ -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}`);
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -21,6 +21,6 @@ export default withTracker(() => {
|
||||
isModerator: Service.isModerator(),
|
||||
timeOffset: Service.getTimeOffset(),
|
||||
timer: Service.getTimer(),
|
||||
isMusicActive: Service.isMusicActive(),
|
||||
currentTrack: Service.getCurrentTrack(),
|
||||
};
|
||||
})(TimerContainer);
|
||||
|
@ -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,
|
||||
|
@ -13,5 +13,6 @@ export default withTracker(() => ({
|
||||
isModerator: TimerService.isModerator(),
|
||||
isTimerActive: TimerService.isActive(),
|
||||
isMusicActive: TimerService.isMusicActive(),
|
||||
currentTrack: TimerService.getCurrentTrack(),
|
||||
hidden: Session.get('openPanel') !== '',
|
||||
}))(IndicatorContainer);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
};
|
@ -519,7 +519,9 @@ public:
|
||||
music:
|
||||
enabled: false
|
||||
volume: 0.4
|
||||
track: "pianoChords"
|
||||
track1: "pianoChords"
|
||||
track2: "littleBossa"
|
||||
track3: "aristocratDrums"
|
||||
interval:
|
||||
clock: 100
|
||||
offset: 60000
|
||||
|
@ -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",
|
||||
|
BIN
bigbluebutton-html5/public/resources/sounds/aristocratDrums.mp3
Normal file
BIN
bigbluebutton-html5/public/resources/sounds/aristocratDrums.mp3
Normal file
Binary file not shown.
BIN
bigbluebutton-html5/public/resources/sounds/littleBossa.mp3
Normal file
BIN
bigbluebutton-html5/public/resources/sounds/littleBossa.mp3
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user