Browse Source

Better voice message animation

master
Eduard Kuzmenko 2 years ago
parent
commit
7f9c298a05
  1. 75
      src/components/audio.ts
  2. 53
      src/scss/partials/_audio.scss

75
src/components/audio.ts

@ -30,6 +30,7 @@ import throttleWithRaf from "../helpers/schedulers/throttleWithRaf";
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config"; import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
import formatBytes from "../helpers/formatBytes"; import formatBytes from "../helpers/formatBytes";
import { clamp } from "../helpers/number"; import { clamp } from "../helpers/number";
import { animateSingle } from "../helpers/animation";
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => { rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
mids.forEach(mid => { mids.forEach(mid => {
@ -78,16 +79,7 @@ export function decodeWaveform(waveform: Uint8Array | number[]) {
return result; return result;
} }
function wrapVoiceMessage(audioEl: AudioElement) { function createWaveformBars(waveform: Uint8Array, duration: number) {
audioEl.classList.add('is-voice');
const message = audioEl.message;
const doc = appMessagesManager.getMediaFromMessage(message) as MyDocument;
if(message.pFlags.out) {
audioEl.classList.add('is-out');
}
const barWidth = 2; const barWidth = 2;
const barMargin = 2; //mediaSizes.isMobile ? 2 : 1; const barMargin = 2; //mediaSizes.isMobile ? 2 : 1;
const barHeightMin = 4; //mediaSizes.isMobile ? 3 : 2; const barHeightMin = 4; //mediaSizes.isMobile ? 3 : 2;
@ -96,22 +88,14 @@ function wrapVoiceMessage(audioEl: AudioElement) {
const minW = mediaSizes.isMobile ? 152 : 190; const minW = mediaSizes.isMobile ? 152 : 190;
const maxW = mediaSizes.isMobile ? 190 : 256; const maxW = mediaSizes.isMobile ? 190 : 256;
const duration = doc.duration;
const availW = clamp(duration / 60 * maxW, minW, maxW); // mediaSizes.isMobile ? 152 : 224; const availW = clamp(duration / 60 * maxW, minW, maxW); // mediaSizes.isMobile ? 152 : 224;
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.classList.add('audio-waveform'); svg.classList.add('audio-waveform-bars');
svg.setAttributeNS(null, 'width', '' + availW); svg.setAttributeNS(null, 'width', '' + availW);
svg.setAttributeNS(null, 'height', '' + barHeightMax); svg.setAttributeNS(null, 'height', '' + barHeightMax);
svg.setAttributeNS(null, 'viewBox', `0 0 ${availW} ${barHeightMax}`); svg.setAttributeNS(null, 'viewBox', `0 0 ${availW} ${barHeightMax}`);
const timeDiv = document.createElement('div');
timeDiv.classList.add('audio-time');
audioEl.append(svg, timeDiv);
let waveform = (doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio).waveform || new Uint8Array([]);
waveform = decodeWaveform(waveform.slice(0, 63));
//console.log('decoded waveform:', waveform); //console.log('decoded waveform:', waveform);
const normValue = Math.max(...waveform); const normValue = Math.max(...waveform);
@ -151,23 +135,55 @@ function wrapVoiceMessage(audioEl: AudioElement) {
} }
} }
const container = document.createElement('div');
container.classList.add('audio-waveform');
container.append(svg);
svg.insertAdjacentHTML('beforeend', html); svg.insertAdjacentHTML('beforeend', html);
const rects = Array.from(svg.children) as HTMLElement[]; return {svg, container, availW};
}
function wrapVoiceMessage(audioEl: AudioElement) {
audioEl.classList.add('is-voice');
const message = audioEl.message;
const doc = appMessagesManager.getMediaFromMessage(message) as MyDocument;
if(message.pFlags.out) {
audioEl.classList.add('is-out');
}
let waveform = (doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio).waveform || new Uint8Array([]);
waveform = decodeWaveform(waveform.slice(0, 63));
const {svg, container: svgContainer, availW} = createWaveformBars(waveform, doc.duration);
const fakeSvgContainer = svgContainer.cloneNode(true) as HTMLElement;
fakeSvgContainer.classList.add('audio-waveform-fake');
svgContainer.classList.add('audio-waveform-background');
const waveformContainer = document.createElement('div');
waveformContainer.classList.add('audio-waveform-container');
waveformContainer.append(svgContainer, fakeSvgContainer);
const timeDiv = document.createElement('div');
timeDiv.classList.add('audio-time');
audioEl.append(waveformContainer, timeDiv);
let progress = audioEl.querySelector('.audio-waveform') as HTMLDivElement; let progress = svg as any as HTMLElement;
const onLoad = () => { const onLoad = () => {
let audio = audioEl.audio; let audio = audioEl.audio;
const setAnimation = () => {
animateSingle(() => {
onTimeUpdate();
return !audio.paused;
}, audioEl);
};
const onTimeUpdate = () => { const onTimeUpdate = () => {
const lastIndex = audio.currentTime === audio.duration ? 0 : Math.ceil(audio.currentTime / audio.duration * barCount); fakeSvgContainer.style.width = (audio.currentTime / audio.duration * 100) + '%';
//svg.children[lastIndex].setAttributeNS(null, 'fill', '#000');
//svg.children[lastIndex].classList.add('active'); #Иногда пропускает полоски..
rects.forEach((node, idx) => node.classList.toggle('active', idx < lastIndex));
//++lastIndex;
//console.log('lastIndex:', lastIndex, audio.currentTime);
//}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */);
}; };
if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) { if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) {
@ -177,6 +193,7 @@ function wrapVoiceMessage(audioEl: AudioElement) {
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate); const throttledTimeUpdate = throttleWithRaf(onTimeUpdate);
audioEl.addAudioListener('timeupdate', throttledTimeUpdate); audioEl.addAudioListener('timeupdate', throttledTimeUpdate);
audioEl.addAudioListener('ended', throttledTimeUpdate); audioEl.addAudioListener('ended', throttledTimeUpdate);
audioEl.addAudioListener('play', setAnimation);
audioEl.readyPromise.then(() => { audioEl.readyPromise.then(() => {
let mousedown = false, mousemove = false; let mousedown = false, mousemove = false;

53
src/scss/partials/_audio.scss

@ -379,29 +379,42 @@
} }
&-waveform { &-waveform {
height: 23px; height: 100%;
margin-top: 1px;
/* @include respond-to(handhelds) {
margin-top: -4px;
} */
//overflow: visible!important; &-background {
rect {
opacity: .3;
@include hover() {
opacity: 1;
}
}
}
&-fake {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 0;
overflow: hidden;
}
&-container {
position: relative;
height: 23px;
margin-top: 1px;
}
rect { rect {
fill: var(--primary-color); fill: var(--primary-color);
//overflow: visible!important; //overflow: visible!important;
opacity: .3;
@include hover() {
opacity: 1;
}
&.active,
.audio.is-unread:not(.is-out) .audio-toggle:not(.playing) + & {
opacity: 1;
}
} }
/* @include respond-to(handhelds) {
margin-top: -4px;
} */
} }
// //&.audio-54 { // //&.audio-54 {
@ -499,7 +512,7 @@
margin-top: .3125rem; margin-top: .3125rem;
} }
.audio-waveform { .audio-waveform-container {
height: 16px; height: 16px;
margin-top: 0; margin-top: 0;
} }
@ -516,6 +529,10 @@
margin-left: .375rem; margin-left: .375rem;
} }
} }
&:not(.is-out) .audio-toggle:not(.playing) + .audio-waveform-container .audio-waveform-background rect {
opacity: 1;
}
} }
} }

Loading…
Cancel
Save