Better voice message animation
This commit is contained in:
parent
05f9d93cfa
commit
7f9c298a05
@ -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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svg.insertAdjacentHTML('beforeend', html);
|
const container = document.createElement('div');
|
||||||
const rects = Array.from(svg.children) as HTMLElement[];
|
container.classList.add('audio-waveform');
|
||||||
|
container.append(svg);
|
||||||
|
|
||||||
let progress = audioEl.querySelector('.audio-waveform') as HTMLDivElement;
|
svg.insertAdjacentHTML('beforeend', html);
|
||||||
|
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 = 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;
|
||||||
|
@ -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…
Reference in New Issue
Block a user