Added a playback speed button on voice notes

This commit is contained in:
Alvaro 2022-08-18 09:43:44 -06:00 committed by GitHub
parent bb9a7113f1
commit 13046dc020
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 21 deletions

View file

@ -6223,6 +6223,22 @@
"message": "Playback time of audio attachment",
"description": "Aria label for audio attachment's playback time slider"
},
"MessageAudio--playbackRate1x": {
"message": "1x",
"description": "Button in the voice note message widget that shows the current playback rate of 1x (regular speed) and allows the user to toggle to the next rate"
},
"MessageAudio--playbackRate1p5x": {
"message": "1.5x",
"description": "Button in the voice note message widget that shows the current playback rate of 1.5x (%50 faster) and allows the user to toggle to the next rate"
},
"MessageAudio--playbackRate2x": {
"message": "2x",
"description": "Button in the voice note message widget that shows the current playback rate of 2x (double speed) and allows the user to toggle to the next rate"
},
"MessageAudio--playbackRatep5x": {
"message": ".5x",
"description": "Button in the voice note message widget that shows the current playback rate of .5x (half speed) and allows the user to toggle to the next rate"
},
"emptyInboxMessage": {
"message": "Click the $composeIcon$ above and search for your contacts or groups to message.",
"description": "Shown in the left-pane when the inbox is empty",

View file

@ -28,6 +28,7 @@ $audio-attachment-button-margin-small: 4px;
@include light-theme {
border-color: $color-black-alpha-20;
}
@include dark-theme {
border-color: $color-white-alpha-20;
}
@ -42,6 +43,25 @@ $audio-attachment-button-margin-small: 4px;
margin-top: 6px;
}
.module-message__audio-attachment__controls {
display: flex;
flex: 1;
justify-content: right;
padding: 0 4px;
}
.module-message__audio-attachment__playback-rate-button {
@include button-reset;
@include font-body-2-bold;
border-radius: 4px;
font-size: 12px; /* tiny override */
padding: 1px 7px;
margin: -1px 4px;
background: $color-white-alpha-20;
line-height: 16px;
}
.module-message__audio-attachment__button,
.module-message__audio-attachment__spinner {
@include button-reset;
@ -91,6 +111,7 @@ $audio-attachment-button-margin-small: 4px;
@include all-audio-icons($color-gray-60);
}
@include dark-theme {
background: $color-gray-60;
@ -115,6 +136,7 @@ $audio-attachment-button-margin-small: 4px;
}
.module-message__audio-attachment__button,
.module-message__audio-attachment__playback-rate-button,
.module-message__audio-attachment__spinner,
.module-message__audio-attachment__waveform {
&:focus {
@ -146,12 +168,15 @@ $audio-attachment-button-margin-small: 4px;
.module-message__audio-attachment--incoming & {
@include light-theme {
background: $color-black-alpha-40;
&--active {
background: $color-black-alpha-80;
}
}
@include dark-theme {
background: $color-white-alpha-40;
&--active {
background: $color-white-alpha-70;
}
@ -160,6 +185,7 @@ $audio-attachment-button-margin-small: 4px;
.module-message__audio-attachment--outgoing & {
background: $color-white-alpha-40;
&--active {
background: $color-white-alpha-80;
}
@ -205,13 +231,16 @@ $audio-attachment-button-margin-small: 4px;
@include light-theme {
$color: $color-black-alpha-60;
color: $color;
&--unplayed:after {
background: $color;
}
}
@include dark-theme {
$color: $color-white-alpha-80;
color: $color;
&--unplayed:after {
background: $color;
}

View file

@ -85,6 +85,8 @@ const REWIND_BAR_COUNT = 2;
const SMALL_INCREMENT = 1;
const BIG_INCREMENT = 5;
const PLAYBACK_RATES = [1, 1.5, 2, 0.5];
// Utils
const timeToText = (time: number): string => {
@ -144,6 +146,7 @@ type StateType = Readonly<{
isPlaying: boolean;
currentTime: number;
lastAriaTime: number;
playbackRate: number;
}>;
type ActionType = Readonly<
@ -155,6 +158,10 @@ type ActionType = Readonly<
type: 'SET_CURRENT_TIME';
value: number;
}
| {
type: 'SET_PLAYBACK_RATE';
value: number;
}
>;
function reducer(state: StateType, action: ActionType): StateType {
@ -168,6 +175,9 @@ function reducer(state: StateType, action: ActionType): StateType {
if (action.type === 'SET_CURRENT_TIME') {
return { ...state, currentTime: action.value };
}
if (action.type === 'SET_PLAYBACK_RATE') {
return { ...state, playbackRate: action.value };
}
throw missingCaseError(action);
}
@ -222,14 +232,13 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
activeAudioID === id && activeAudioContext === renderingContext;
const waveformRef = useRef<HTMLDivElement | null>(null);
const [{ isPlaying, currentTime, lastAriaTime }, dispatch] = useReducer(
reducer,
{
const [{ isPlaying, currentTime, lastAriaTime, playbackRate }, dispatch] =
useReducer(reducer, {
isPlaying: isActive && !(audio.paused || audio.ended),
currentTime: isActive ? audio.currentTime : 0,
lastAriaTime: isActive ? audio.currentTime : 0,
}
);
playbackRate: isActive ? audio.playbackRate : 1,
});
const setIsPlaying = useCallback(
(value: boolean) => {
@ -245,6 +254,13 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
[dispatch]
);
const setPlaybackRate = useCallback(
(value: number) => {
dispatch({ type: 'SET_PLAYBACK_RATE', value });
},
[dispatch]
);
// NOTE: Avoid division by zero
const [duration, setDuration] = useState(1e-23);
@ -397,6 +413,8 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
return;
}
audio.playbackRate = playbackRate;
if (isPlaying) {
if (!audio.paused) {
return;
@ -411,7 +429,7 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
log.info('MessageAudio: pausing playback for', id);
audio.pause();
}
}, [id, audio, isActive, isPlaying, currentTime]);
}, [id, audio, isActive, isPlaying, currentTime, playbackRate]);
const toggleIsPlaying = () => {
setIsPlaying(!isPlaying);
@ -590,6 +608,20 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
const countDown = Math.max(0, duration - currentTime);
const nextPlaybackRate = (currentRate: number): number => {
// cycle through the rates
return PLAYBACK_RATES[
(PLAYBACK_RATES.indexOf(currentRate) + 1) % PLAYBACK_RATES.length
];
};
const playbackRateLabels: { [key: number]: string } = {
1: i18n('MessageAudio--playbackRate1x'),
1.5: i18n('MessageAudio--playbackRate1p5x'),
2: i18n('MessageAudio--playbackRate2x'),
0.5: i18n('MessageAudio--playbackRatep5x'),
};
const metadata = (
<div className={`${CSS_BASE}__metadata`}>
<div
@ -602,21 +634,38 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
{timeToText(countDown)}
</div>
{!withContentBelow && !collapseMetadata && (
<MessageMetadata
direction={direction}
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
hasText={withContentBelow}
i18n={i18n}
id={id}
isShowingImage={false}
isSticker={false}
isTapToViewExpired={false}
showMessageDetail={showMessageDetail}
status={status}
textPending={textPending}
timestamp={timestamp}
/>
<>
<div className={`${CSS_BASE}__controls`}>
{isPlaying && (
<button
type="button"
className={classNames(`${CSS_BASE}__playback-rate-button`)}
onClick={ev => {
ev.stopPropagation();
setPlaybackRate(nextPlaybackRate(playbackRate));
}}
tabIndex={0}
>
{playbackRateLabels[playbackRate]}
</button>
)}
</div>
<MessageMetadata
direction={direction}
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
hasText={withContentBelow}
i18n={i18n}
id={id}
isShowingImage={false}
isSticker={false}
isTapToViewExpired={false}
showMessageDetail={showMessageDetail}
status={status}
textPending={textPending}
timestamp={timestamp}
/>
</>
)}
</div>
);