From 14e826e19eeba1bc356058134a563cec23159c7e Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Thu, 2 Mar 2023 17:24:15 +0400 Subject: [PATCH] Voice fixes: missing waveform, 0 duration, text --- src/components/audio.ts | 95 +++++++++++++---------- src/global.d.ts | 2 +- src/lib/appManagers/appMessagesManager.ts | 2 +- src/scss/partials/_audio.scss | 17 ++++ 4 files changed, 73 insertions(+), 43 deletions(-) diff --git a/src/components/audio.ts b/src/components/audio.ts index 1a523144..a94ddebb 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -73,39 +73,21 @@ export function decodeWaveform(waveform: Uint8Array | number[]) { result = new Uint8Array([]); } - /* var byteIndex = (valueCount - 1) / 8 | 0; - var bitShift = (valueCount - 1) % 8; - if(byteIndex === waveform.length - 1) { - var value = waveform[byteIndex]; - } else { - var value = dataView.getUint16(byteIndex, true); - } - console.log('decoded waveform, setting last value:', value, byteIndex, bitShift); - result[valueCount - 1] = (value >> bitShift) & 0b00011111; */ return result; } function createWaveformBars(waveform: Uint8Array, duration: number) { const barWidth = 2; - const barMargin = 2; // mediaSizes.isMobile ? 2 : 1; - const barHeightMin = 4; // mediaSizes.isMobile ? 3 : 2; + const barMargin = 2; + const barHeightMin = 4; const barHeightMax = mediaSizes.isMobile ? 16 : 23; - // const availW = 150; //mediaSizes.isMobile ? 152 : 190; const minW = mediaSizes.isMobile ? 152 : 190; const maxW = mediaSizes.isMobile ? 190 : 256; - const availW = clamp(duration / 60 * maxW, minW, maxW); // mediaSizes.isMobile ? 152 : 224; - - const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - svg.classList.add('audio-waveform-bars'); - svg.setAttributeNS(null, 'width', '' + availW); - svg.setAttributeNS(null, 'height', '' + barHeightMax); - svg.setAttributeNS(null, 'viewBox', `0 0 ${availW} ${barHeightMax}`); - - // console.log('decoded waveform:', waveform); + const availW = clamp(duration / 60 * maxW, minW, maxW); const normValue = Math.max(...waveform); - const wfSize = waveform.length ? waveform.length : 100; + const wfSize = waveform.length; const barCount = Math.min((availW / (barWidth + barMargin)) | 0, wfSize); let maxValue = 0; @@ -122,9 +104,7 @@ function createWaveformBars(waveform: Uint8Array, duration: number) { const bar_value = Math.max(((maxValue * maxDelta) + ((normValue + 1) / 2)) / (normValue + 1), barHeightMin); - const h = ` - - `; + const h = ``; html += h; barX += barWidth + barMargin; @@ -141,11 +121,24 @@ function createWaveformBars(waveform: Uint8Array, duration: number) { } } - const container = document.createElement('div'); - container.classList.add('audio-waveform'); - container.append(svg); + let container: HTMLElement, svg: SVGSVGElement; + + if(!html) { + + } else { + container = document.createElement('div'); + container.classList.add('audio-waveform'); + + svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.classList.add('audio-waveform-bars'); + svg.setAttributeNS(null, 'width', '' + availW); + svg.setAttributeNS(null, 'height', '' + barHeightMax); + svg.setAttributeNS(null, 'viewBox', `0 0 ${availW} ${barHeightMax}`); + svg.insertAdjacentHTML('beforeend', html); + + container.append(svg); + } - svg.insertAdjacentHTML('beforeend', html); return {svg, container, availW}; } @@ -164,13 +157,19 @@ async function wrapVoiceMessage(audioEl: AudioElement) { 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'); + let fakeSvgContainer: HTMLElement; + if(svgContainer) { + 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); + + if(svgContainer) { + waveformContainer.append(svgContainer, fakeSvgContainer); + } const timeDiv = document.createElement('div'); timeDiv.classList.add('audio-time'); @@ -224,7 +223,14 @@ async function wrapVoiceMessage(audioEl: AudioElement) { audioEl.append(speechRecognitionDiv); } - let progress = svg as any as HTMLElement; + let progress = svg as any as HTMLElement, progressLine: MediaProgressLine; + if(!progress) { + progressLine = new MediaProgressLine({ + streamable: doc.supportsStreaming + }); + + waveformContainer.append(progressLine.container); + } const onLoad = () => { let audio = audioEl.audio; @@ -238,7 +244,9 @@ async function wrapVoiceMessage(audioEl: AudioElement) { }; const onTimeUpdate = () => { - fakeSvgContainer.style.width = (audio.currentTime / audio.duration * 100) + '%'; + if(fakeSvgContainer) { + fakeSvgContainer.style.width = (audio.currentTime / audio.duration * 100) + '%'; + } }; if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) { @@ -250,7 +258,7 @@ async function wrapVoiceMessage(audioEl: AudioElement) { audioEl.addAudioListener('ended', throttledTimeUpdate); audioEl.addAudioListener('play', setAnimation); - audioEl.readyPromise.then(() => { + progress && audioEl.readyPromise.then(() => { let mousedown = false, mousemove = false; progress.addEventListener('mouseleave', (e) => { if(mousedown) { @@ -298,8 +306,10 @@ async function wrapVoiceMessage(audioEl: AudioElement) { } }, noop); + !progress && progressLine.setMedia(audio); + return () => { - progress.remove(); + progress?.remove(); progress = null; audio = null; }; @@ -495,7 +505,10 @@ export default class AudioElement extends HTMLElement { const isOutgoing = this.message.pFlags.is_outgoing; const uploadingFileName = this.message?.uploadingFileName; - const durationStr = toHHMMSS(doc.duration | 0); + const getDurationStr = () => { + const duration = this.audio && this.audio.readyState >= this.audio.HAVE_CURRENT_DATA ? this.audio.duration : doc.duration; + return toHHMMSS(duration | 0); + }; this.innerHTML = `
@@ -523,7 +536,7 @@ export default class AudioElement extends HTMLElement { const onTypeLoad = await (isVoice ? wrapVoiceMessage(this) : wrapAudio(this)); const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement; - audioTimeDiv.innerHTML = durationStr; + audioTimeDiv.textContent = getDurationStr(); const onLoad = this.onLoad = (autoload: boolean) => { this.onLoad = undefined; @@ -538,7 +551,7 @@ export default class AudioElement extends HTMLElement { this.onTypeDisconnect = onTypeLoad(); - const getTimeStr = () => toHHMMSS(audio.currentTime | 0) + (isVoice ? (' / ' + durationStr) : ''); + const getTimeStr = () => toHHMMSS(audio.currentTime | 0) + (isVoice ? (' / ' + getDurationStr()) : ''); const onPlay = () => { audioTimeDiv.innerText = getTimeStr(); @@ -573,7 +586,7 @@ export default class AudioElement extends HTMLElement { this.addAudioListener('ended', () => { toggle.classList.remove('playing'); - audioTimeDiv.innerText = durationStr; + audioTimeDiv.innerText = getDurationStr(); }); this.addAudioListener('timeupdate', () => { diff --git a/src/global.d.ts b/src/global.d.ts index 342aa822..2b8d6064 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -65,7 +65,7 @@ declare global { 'USER_ALREADY_PARTICIPANT' | 'USERNAME_INVALID' | 'USERNAME_PURCHASE_AVAILABLE' | 'USERNAMES_ACTIVE_TOO_MUCH' | 'BOT_INVALID' | 'USERNAME_NOT_OCCUPIED' | 'PINNED_TOO_MUCH' | 'LOCATION_INVALID' | 'FILE_ID_INVALID' | 'CHANNEL_FORUM_MISSING' | 'TRANSCRIPTION_FAILED' | 'USER_NOT_PARTICIPANT' | - 'PEER_ID_INVALID'; + 'PEER_ID_INVALID' | 'MSG_VOICE_MISSING'; type ErrorType = LocalErrorType | ServerErrorType; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 49b45ce9..f243ddd2 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -549,7 +549,7 @@ export class AppMessagesManager extends AppManager { return result; }, processError: (error) => { - if(error.type === 'TRANSCRIPTION_FAILED') { + if(error.type === 'TRANSCRIPTION_FAILED' || error.type === 'MSG_VOICE_MISSING') { process({ _: 'messages.transcribedAudio', transcription_id: 0, diff --git a/src/scss/partials/_audio.scss b/src/scss/partials/_audio.scss index f4342833..afad156b 100644 --- a/src/scss/partials/_audio.scss +++ b/src/scss/partials/_audio.scss @@ -406,6 +406,8 @@ position: relative; height: 23px; margin-top: 1px; + display: flex; + align-items: center; } &-bar { @@ -576,6 +578,21 @@ align-items: center; } + .progress-line { + margin-top: 4px; + margin-inline-start: 0; + + &__filled:after { + opacity: 0; + } + } + + // .audio-toggle:not(.playing) + .audio-waveform-container { + // .progress-line__filled:after { + // opacity: 0; + // } + // } + @include respond-to(handhelds) { .audio-time { margin-top: .3125rem;