check if browser is allowing us to use AudioContext and tell user if not

This commit is contained in:
HF 2021-06-15 21:13:15 +02:00
parent 1b33260520
commit 059931e202
6 changed files with 293 additions and 287 deletions

View File

@ -6,12 +6,29 @@ import ToggleButton from 'react-toggle-button';
import { MdCheck, MdClose } from 'react-icons/md';
const MdToggleButton = ({ value, onToggle }) => (
const MdToggleButton = ({ value, onToggle, deactivated }) => (
<ToggleButton
inactiveLabel={<MdClose />}
activeLabel={<MdCheck />}
value={value}
onToggle={onToggle}
colors={(deactivated)
? {
activeThumb: {
base: '#b2b2b2',
},
inactiveTumb: {
base: '#b2b2b2',
},
active: {
base: '#cbcbcb',
},
inactive: {
base: '#cbcbcb',
},
}
: {}}
thumbStyleHover={{ backgroundColor: '#ededed' }}
/>
);

View File

@ -1,33 +0,0 @@
/**
*/
import React from 'react';
import MdToggleButton from './MdToggleButton';
const MdToggleButtonHover = ({ value, onToggle }) => (
<MdToggleButton
value={value}
onToggle={onToggle}
thumbStyle={{
position: 'absolute',
width: 30,
height: 30,
boxShadow: '0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24)',
display: 'flex',
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
}}
thumbStyleHover={{
width: 32,
height: 32,
}}
animateThumbStyleHover={(n) => ({
// eslint-disable-next-line max-len
boxShadow: `0 0 ${2 + (4 * n)}px rgba(0,0,0,.16),0 ${2 + (3 * n)}px ${4 + (8 * n)}px rgba(0,0,0,.32)`,
})}
/>
);
export default MdToggleButtonHover;

View File

@ -11,7 +11,7 @@ import {
setBlockingDm,
setUserBlock,
} from '../actions';
import MdToggleButtonHover from './MdToggleButtonHover';
import MdToggleButton from './MdToggleButton';
const SocialSettings = ({ done }) => {
const blocked = useSelector((state) => state.chat.blocked);
@ -37,7 +37,7 @@ const SocialSettings = ({ done }) => {
>
{t`Block all Private Messages`}
</span>
<MdToggleButtonHover
<MdToggleButton
value={blockDm}
onToggle={() => {
if (!fetching) {

View File

@ -8,7 +8,7 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { c, t } from 'ttag';
import LanguageSelect from '../LanguageSelect';
import MdToggleButtonHover from '../MdToggleButtonHover';
import MdToggleButton from '../MdToggleButton';
import {
toggleGrid,
togglePixelNotify,
@ -48,7 +48,7 @@ const rowStyles = {
};
const SettingsItemSelect = ({
title, description, values, selected, onSelect, icon,
title, values, selected, onSelect, icon, children,
}) => (
<div style={itemStyles}>
<div style={rowStyles}>
@ -72,13 +72,13 @@ const SettingsItemSelect = ({
}
</select>
</div>
{description && <div className="modaldesc">{description} </div>}
<div className="modaldesc">{children}</div>
<div className="modaldivider" />
</div>
);
const SettingsItem = ({
title, description, keyBind, value, onToggle,
const SettingsItem = React.memo(({
title, keyBind, value, onToggle, children, deactivated,
}) => (
<div style={itemStyles}>
<div style={rowStyles}>
@ -88,12 +88,16 @@ const SettingsItem = ({
>
{title} {keyBind && <kbd>{keyBind}</kbd>}
</h3>
<MdToggleButtonHover value={value} onToggle={onToggle} />
<MdToggleButton
value={value}
onToggle={onToggle}
deactivated={deactivated}
/>
</div>
{description && <div className="modaldesc">{description} </div>}
<div className="modaldesc">{children}</div>
<div className="modaldivider" />
</div>
);
), (prevProps, nextProps) => prevProps.value === nextProps.value);
function Settings() {
const [
@ -121,79 +125,98 @@ function Settings() {
], shallowEqual);
const dispatch = useDispatch();
const audioAvailable = window.AudioContext || window.webkitAudioContext;
return (
<div style={{ paddingLeft: '5%', paddingRight: '5%', paddingTop: 30 }}>
<SettingsItem
title={t`Show Grid`}
description={t`Turn on grid to highlight pixel borders.`}
keyBind={c('keybinds').t`G`}
value={isGridShown}
onToggle={() => dispatch(toggleGrid())}
/>
>
{t`Turn on grid to highlight pixel borders.`}
</SettingsItem>
<SettingsItem
title={t`Show Pixel Activity`}
description={t`Show circles where pixels are placed.`}
keyBind={c('keybinds').t`X`}
value={isPixelNotifyShown}
onToggle={() => dispatch(togglePixelNotify())}
/>
>
{t`Show circles where pixels are placed.`}
</SettingsItem>
<SettingsItem
title={t`Disable Game Sounds`}
// eslint-disable-next-line max-len
description={t`All sound effects will be disabled.`}
keyBind={c('keybinds').t`M`}
value={isMuted}
deactivated={(!audioAvailable)}
value={!audioAvailable || isMuted}
onToggle={() => dispatch(toggleMute())}
/>
>
{[t`All sound effects will be disabled.`,
(!audioAvailable) && (
<p className="warn">
{/* eslint-disable-next-line max-len */}
{t`Your Browser doesn't allow us to use AudioContext to play sounds. Do you have some privacy feature blocking us?`}
</p>
),
]}
</SettingsItem>
<SettingsItem
title={t`Enable chat notifications`}
description={t`Play a sound when new chat messages arrive`}
value={chatNotify}
onToggle={() => dispatch(toggleChatNotify())}
/>
>
{t`Play a sound when new chat messages arrive`}
</SettingsItem>
<SettingsItem
title={t`Auto Zoom In`}
// eslint-disable-next-line max-len
description={t`Zoom in instead of placing a pixel when you tap the canvas and your zoom is small.`}
value={autoZoomIn}
onToggle={() => dispatch(toggleAutoZoomIn())}
/>
>
{/* eslint-disable-next-line max-len */}
{t`Zoom in instead of placing a pixel when you tap the canvas and your zoom is small.`}
</SettingsItem>
<SettingsItem
title={t`Compact Palette`}
// eslint-disable-next-line max-len
description={t`Display Palette in a compact form that takes less screen space.`}
value={compactPalette}
onToggle={() => dispatch(toggleCompactPalette())}
/>
>
{t`Display Palette in a compact form that takes less screen space.`}
</SettingsItem>
<SettingsItem
title={t`Potato Mode`}
description={t`For when you are playing on a potato.`}
value={isPotato}
onToggle={() => dispatch(togglePotatoMode())}
/>
>
{t`For when you are playing on a potato.`}
</SettingsItem>
<SettingsItem
title={t`Light Grid`}
description={t`Show Grid in white instead of black.`}
value={isLightGrid}
onToggle={() => dispatch(toggleLightGrid())}
/>
>
{t`Show Grid in white instead of black.`}
</SettingsItem>
{(window.ssv && window.ssv.backupurl) && (
<SettingsItem
title={t`Historical View`}
description={t`Check out past versions of the canvas.`}
value={isHistoricalView}
keyBind={c('keybinds').t`H`}
onToggle={() => dispatch(toggleHistoricalView())}
/>
>
{t`Check out past versions of the canvas.`}
</SettingsItem>
)}
{(window.ssv && window.ssv.availableStyles) && (
<SettingsItemSelect
title={t`Themes`}
description={t`How pixelplanet should look like.`}
values={Object.keys(window.ssv.availableStyles)}
selected={selectedStyle}
onSelect={(style) => dispatch(selectStyle(style))}
/>
>
{t`How pixelplanet should look like.`}
</SettingsItemSelect>
)}
{(window.ssv && navigator.cookieEnabled && window.ssv.langs) && (
<div style={itemStyles}>

View File

@ -7,245 +7,240 @@
// iPhone needs this
const AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();
const context = AudioContext && new AudioContext();
export default (store) => (next) => (action) => {
const state = store.getState();
const { mute, chatNotify } = state.audio;
switch (action.type) {
case 'SELECT_COLOR': {
if (mute) break;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
if (!mute && context) {
switch (action.type) {
case 'SELECT_COLOR': {
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.detune.value = -600;
oscillatorNode.type = 'sine';
oscillatorNode.detune.value = -600;
oscillatorNode.frequency.setValueAtTime(600, context.currentTime);
oscillatorNode.frequency.setValueAtTime(700, context.currentTime + 0.1);
oscillatorNode.frequency.setValueAtTime(600, context.currentTime);
oscillatorNode.frequency.setValueAtTime(700, context.currentTime + 0.1);
gainNode.gain.setValueAtTime(0.3, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
gainNode.gain.setValueAtTime(0.3, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.2);
break;
}
case 'SET_NOTIFICATION': {
if (mute) break;
const { notification } = action;
if (typeof notification !== 'string') {
break;
}
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.detune.value = -1200;
oscillatorNode.frequency.setValueAtTime(500, context.currentTime);
oscillatorNode.frequency.setValueAtTime(600, context.currentTime + 0.1);
gainNode.gain.setValueAtTime(0.3, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.2);
break;
}
case 'PIXEL_WAIT': {
if (mute) break;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
// oscillatorNode.detune.value = -600
oscillatorNode.frequency.setValueAtTime(1479.98, context.currentTime);
oscillatorNode.frequency.exponentialRampToValueAtTime(
493.88,
context.currentTime + 0.01,
);
gainNode.gain.setValueAtTime(0.5, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.1);
break;
}
case 'PIXEL_FAILURE': {
if (mute) break;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.detune.value = -900;
oscillatorNode.frequency.setValueAtTime(600, context.currentTime);
oscillatorNode.frequency.setValueAtTime(
1400,
context.currentTime + 0.025,
);
oscillatorNode.frequency.setValueAtTime(
1200,
context.currentTime + 0.05,
);
oscillatorNode.frequency.setValueAtTime(
900,
context.currentTime + 0.075,
);
const lfo = context.createOscillator();
lfo.type = 'sine';
lfo.frequency.value = 2.0;
lfo.connect(gainNode.gain);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
lfo.start();
oscillatorNode.stop(context.currentTime + 0.3);
break;
}
case 'PLACED_PIXELS': {
if (mute) break;
const { palette, selectedColor: color } = state.canvas;
const colorsAmount = palette.colors.length;
const clrFreq = 100 + Math.log(color / colorsAmount + 1) * 300;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(clrFreq, context.currentTime);
oscillatorNode.frequency.exponentialRampToValueAtTime(
1400,
context.currentTime + 0.2,
);
gainNode.gain.setValueAtTime(0.5, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.1);
break;
}
case 'COOLDOWN_END': {
if (mute) break;
// do not play sound if last cooldown end was <5s ago
const { lastCoolDownEnd } = state.user;
if (lastCoolDownEnd && lastCoolDownEnd.getTime() + 5000 > Date.now()) {
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.2);
break;
}
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
case 'SET_NOTIFICATION': {
const { notification } = action;
if (typeof notification !== 'string') {
break;
}
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(349.23, context.currentTime);
oscillatorNode.frequency.setValueAtTime(
523.25,
context.currentTime + 0.1,
);
oscillatorNode.frequency.setValueAtTime(
698.46,
context.currentTime + 0.2,
);
oscillatorNode.type = 'sine';
oscillatorNode.detune.value = -1200;
gainNode.gain.setValueAtTime(0.5, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.15,
);
oscillatorNode.frequency.setValueAtTime(500, context.currentTime);
oscillatorNode.frequency.setValueAtTime(600, context.currentTime + 0.1);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
gainNode.gain.setValueAtTime(0.3, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.3);
break;
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.2);
break;
}
case 'PIXEL_WAIT': {
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
// oscillatorNode.detune.value = -600
oscillatorNode.frequency.setValueAtTime(1479.98, context.currentTime);
oscillatorNode.frequency.exponentialRampToValueAtTime(
493.88,
context.currentTime + 0.01,
);
gainNode.gain.setValueAtTime(0.5, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.1);
break;
}
case 'PIXEL_FAILURE': {
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.detune.value = -900;
oscillatorNode.frequency.setValueAtTime(600, context.currentTime);
oscillatorNode.frequency.setValueAtTime(
1400,
context.currentTime + 0.025,
);
oscillatorNode.frequency.setValueAtTime(
1200,
context.currentTime + 0.05,
);
oscillatorNode.frequency.setValueAtTime(
900,
context.currentTime + 0.075,
);
const lfo = context.createOscillator();
lfo.type = 'sine';
lfo.frequency.value = 2.0;
lfo.connect(gainNode.gain);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
lfo.start();
oscillatorNode.stop(context.currentTime + 0.3);
break;
}
case 'PLACED_PIXELS': {
const { palette, selectedColor: color } = state.canvas;
const colorsAmount = palette.colors.length;
const clrFreq = 100 + Math.log(color / colorsAmount + 1) * 300;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(clrFreq, context.currentTime);
oscillatorNode.frequency.exponentialRampToValueAtTime(
1400,
context.currentTime + 0.2,
);
gainNode.gain.setValueAtTime(0.5, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.1);
break;
}
case 'COOLDOWN_END': {
// do not play sound if last cooldown end was <5s ago
const { lastCoolDownEnd } = state.user;
if (lastCoolDownEnd && lastCoolDownEnd.getTime() + 5000 > Date.now()) {
break;
}
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(349.23, context.currentTime);
oscillatorNode.frequency.setValueAtTime(
523.25,
context.currentTime + 0.1,
);
oscillatorNode.frequency.setValueAtTime(
698.46,
context.currentTime + 0.2,
);
gainNode.gain.setValueAtTime(0.5, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.2,
context.currentTime + 0.15,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.3);
break;
}
case 'RECEIVE_CHAT_MESSAGE': {
if (chatNotify) break;
const { isPing, channel } = action;
const { mute: muteCh, chatChannel } = state.chatRead;
if (muteCh.includes(channel)) break;
if (muteCh.includes(`${channel}`)) break;
const { channels } = state.chat;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(310, context.currentTime);
/*
* ping if user mention or
* message in DM channel that is not currently open
*/
const freq = (isPing
|| (
channels[channel]
&& channels[channel][1] === 1
// eslint-disable-next-line eqeqeq
&& channel != chatChannel
)
) ? 540 : 355;
oscillatorNode.frequency.exponentialRampToValueAtTime(
freq,
context.currentTime + 0.025,
);
gainNode.gain.setValueAtTime(0.1, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.1,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.075);
break;
}
default:
// nothing
}
case 'RECEIVE_CHAT_MESSAGE': {
if (mute || !chatNotify) break;
const { isPing, channel } = action;
const { mute: muteCh, chatChannel } = state.chatRead;
if (muteCh.includes(channel)) break;
if (muteCh.includes(`${channel}`)) break;
const { channels } = state.chat;
const oscillatorNode = context.createOscillator();
const gainNode = context.createGain();
oscillatorNode.type = 'sine';
oscillatorNode.frequency.setValueAtTime(310, context.currentTime);
/*
* ping if user mention or
* message in DM channel that is not currently open
*/
const freq = (isPing
|| (
channels[channel]
&& channels[channel][1] === 1
// eslint-disable-next-line eqeqeq
&& channel != chatChannel
)
) ? 540 : 355;
oscillatorNode.frequency.exponentialRampToValueAtTime(
freq,
context.currentTime + 0.025,
);
gainNode.gain.setValueAtTime(0.1, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(
0.1,
context.currentTime + 0.1,
);
oscillatorNode.connect(gainNode);
gainNode.connect(context.destination);
oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.075);
break;
}
default:
// nothing
}
return next(action);

View File

@ -57,6 +57,10 @@ a:hover {
font-weight: bold;
}
.warn {
color: #e53737;
}
.modallink {
text-decoration: none;
color: #428bca;