2021-04-08 17:52:31 +04:00
|
|
|
/*
|
|
|
|
* https://github.com/morethanwords/tweb
|
|
|
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
|
|
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
|
|
|
*/
|
|
|
|
|
2020-09-17 22:33:23 +03:00
|
|
|
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
|
2021-09-26 20:03:45 +04:00
|
|
|
import { wrapPhoto } from "./wrappers";
|
2020-08-25 12:39:39 +03:00
|
|
|
import ProgressivePreloader from "./preloader";
|
2020-06-13 11:19:39 +03:00
|
|
|
import { MediaProgressLine } from "../lib/mediaPlayer";
|
2021-10-06 00:40:07 +04:00
|
|
|
import appMediaPlaybackController, { MediaItem, MediaSearchContext } from "./appMediaPlaybackController";
|
|
|
|
import { DocumentAttribute, Message } from "../layer";
|
2020-09-23 23:29:53 +03:00
|
|
|
import mediaSizes from "../helpers/mediaSizes";
|
2021-09-26 17:59:10 +04:00
|
|
|
import { IS_SAFARI } from "../environment/userAgent";
|
2020-10-08 23:49:08 +03:00
|
|
|
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
2020-11-15 05:33:47 +02:00
|
|
|
import rootScope from "../lib/rootScope";
|
2020-11-07 05:48:07 +02:00
|
|
|
import './middleEllipsis';
|
2021-05-08 21:46:50 +04:00
|
|
|
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
2021-09-28 17:07:56 +04:00
|
|
|
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
2021-09-23 17:41:02 +04:00
|
|
|
import LazyLoadQueue from "./lazyLoadQueue";
|
2021-09-24 19:33:33 +04:00
|
|
|
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
2021-09-23 17:41:02 +04:00
|
|
|
import ListenerSetter, { Listener } from "../helpers/listenerSetter";
|
|
|
|
import noop from "../helpers/noop";
|
2021-09-24 19:33:33 +04:00
|
|
|
import findUpClassName from "../helpers/dom/findUpClassName";
|
2021-09-26 20:03:45 +04:00
|
|
|
import { joinElementsWith } from "../lib/langPack";
|
|
|
|
import { MiddleEllipsisElement } from "./middleEllipsis";
|
|
|
|
import htmlToSpan from "../helpers/dom/htmlToSpan";
|
|
|
|
import { formatFullSentTime } from "../helpers/date";
|
2021-10-29 03:14:03 +03:00
|
|
|
import { clamp, formatBytes } from "../helpers/number";
|
2021-09-28 17:07:56 +04:00
|
|
|
import throttleWithRaf from "../helpers/schedulers/throttleWithRaf";
|
2021-11-03 22:21:06 +04:00
|
|
|
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
|
2020-10-08 23:49:08 +03:00
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
|
2020-10-08 23:49:08 +03:00
|
|
|
mids.forEach(mid => {
|
2021-09-28 17:07:56 +04:00
|
|
|
const attr = `[data-mid="${mid}"][data-peer-id="${peerId}"]`;
|
|
|
|
(Array.from(document.querySelectorAll(`audio-element.is-unread${attr}, .media-round.is-unread${attr}`)) as AudioElement[]).forEach(elem => {
|
2020-10-08 23:49:08 +03:00
|
|
|
elem.classList.remove('is-unread');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2020-06-13 11:19:39 +03:00
|
|
|
|
|
|
|
// https://github.com/LonamiWebs/Telethon/blob/4393ec0b83d511b6a20d8a20334138730f084375/telethon/utils.py#L1285
|
|
|
|
export function decodeWaveform(waveform: Uint8Array | number[]) {
|
|
|
|
if(!(waveform instanceof Uint8Array)) {
|
|
|
|
waveform = new Uint8Array(waveform);
|
|
|
|
}
|
|
|
|
|
2020-12-19 03:44:41 +02:00
|
|
|
const bitCount = waveform.length * 8;
|
|
|
|
const valueCount = bitCount / 5 | 0;
|
2020-06-13 11:19:39 +03:00
|
|
|
if(!valueCount) {
|
|
|
|
return new Uint8Array([]);
|
|
|
|
}
|
|
|
|
|
2020-12-19 03:44:41 +02:00
|
|
|
let result: Uint8Array;
|
2020-10-26 02:09:42 +02:00
|
|
|
try {
|
2020-12-19 03:44:41 +02:00
|
|
|
const dataView = new DataView(waveform.buffer);
|
|
|
|
result = new Uint8Array(valueCount);
|
|
|
|
for(let i = 0; i < valueCount; i++) {
|
|
|
|
const byteIndex = i * 5 / 8 | 0;
|
|
|
|
const bitShift = i * 5 % 8;
|
|
|
|
const value = dataView.getUint16(byteIndex, true);
|
2020-10-26 02:09:42 +02:00
|
|
|
result[i] = (value >> bitShift) & 0b00011111;
|
|
|
|
}
|
|
|
|
} catch(err) {
|
2020-12-19 03:44:41 +02:00
|
|
|
result = new Uint8Array([]);
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* var byteIndex = (valueCount - 1) / 8 | 0;
|
|
|
|
var bitShift = (valueCount - 1) % 8;
|
2021-02-04 02:30:23 +02:00
|
|
|
if(byteIndex === waveform.length - 1) {
|
2020-06-13 11:19:39 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-12-19 03:07:24 +02:00
|
|
|
function wrapVoiceMessage(audioEl: AudioElement) {
|
2020-06-13 11:19:39 +03:00
|
|
|
audioEl.classList.add('is-voice');
|
|
|
|
|
2020-12-19 03:07:24 +02:00
|
|
|
const message = audioEl.message;
|
2021-10-06 00:40:07 +04:00
|
|
|
const doc = appMessagesManager.getMediaFromMessage(message) as MyDocument;
|
2020-10-08 23:49:08 +03:00
|
|
|
|
2021-04-02 18:32:10 +04:00
|
|
|
if(message.pFlags.out) {
|
|
|
|
audioEl.classList.add('is-out');
|
|
|
|
}
|
|
|
|
|
2020-06-21 15:25:17 +03:00
|
|
|
const barWidth = 2;
|
2021-04-02 18:32:10 +04:00
|
|
|
const barMargin = 2; //mediaSizes.isMobile ? 2 : 1;
|
|
|
|
const barHeightMin = 4; //mediaSizes.isMobile ? 3 : 2;
|
2021-04-04 22:46:09 +04:00
|
|
|
const barHeightMax = mediaSizes.isMobile ? 16 : 23;
|
2021-10-29 03:14:03 +03:00
|
|
|
// const availW = 150; //mediaSizes.isMobile ? 152 : 190;
|
|
|
|
|
|
|
|
const minW = mediaSizes.isMobile ? 152 : 190;
|
|
|
|
const maxW = mediaSizes.isMobile ? 190 : 256;
|
|
|
|
const duration = doc.duration;
|
|
|
|
const availW = clamp(duration / 60 * maxW, minW, maxW); // mediaSizes.isMobile ? 152 : 224;
|
2020-06-21 15:25:17 +03:00
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
svg.classList.add('audio-waveform');
|
2020-06-21 15:25:17 +03:00
|
|
|
svg.setAttributeNS(null, 'width', '' + availW);
|
|
|
|
svg.setAttributeNS(null, 'height', '' + barHeightMax);
|
|
|
|
svg.setAttributeNS(null, 'viewBox', `0 0 ${availW} ${barHeightMax}`);
|
2020-06-13 11:19:39 +03:00
|
|
|
|
|
|
|
const timeDiv = document.createElement('div');
|
|
|
|
timeDiv.classList.add('audio-time');
|
|
|
|
audioEl.append(svg, timeDiv);
|
|
|
|
|
2021-02-04 02:30:23 +02:00
|
|
|
let waveform = (doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio).waveform || new Uint8Array([]);
|
2020-10-26 02:09:42 +02:00
|
|
|
waveform = decodeWaveform(waveform.slice(0, 63));
|
2020-06-13 11:19:39 +03:00
|
|
|
|
|
|
|
//console.log('decoded waveform:', waveform);
|
|
|
|
|
|
|
|
const normValue = Math.max(...waveform);
|
|
|
|
const wfSize = waveform.length ? waveform.length : 100;
|
|
|
|
const barCount = Math.min((availW / (barWidth + barMargin)) | 0, wfSize);
|
|
|
|
|
|
|
|
let maxValue = 0;
|
|
|
|
const maxDelta = barHeightMax - barHeightMin;
|
|
|
|
|
|
|
|
let html = '';
|
|
|
|
for(let i = 0, barX = 0, sumI = 0; i < wfSize; ++i) {
|
|
|
|
const value = waveform[i] || 0;
|
|
|
|
if((sumI + barCount) >= wfSize) { // draw bar
|
|
|
|
sumI = sumI + barCount - wfSize;
|
|
|
|
if(sumI < (barCount + 1) / 2) {
|
|
|
|
if(maxValue < value) maxValue = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bar_value = Math.max(((maxValue * maxDelta) + ((normValue + 1) / 2)) / (normValue + 1), barHeightMin);
|
|
|
|
|
|
|
|
const h = `
|
2021-04-02 18:32:10 +04:00
|
|
|
<rect x="${barX}" y="${barHeightMax - bar_value}" width="${barWidth}" height="${bar_value}" rx="1" ry="1"></rect>
|
2020-06-13 11:19:39 +03:00
|
|
|
`;
|
|
|
|
html += h;
|
|
|
|
|
|
|
|
barX += barWidth + barMargin;
|
|
|
|
|
|
|
|
if(sumI < (barCount + 1) / 2) {
|
|
|
|
maxValue = 0;
|
|
|
|
} else {
|
|
|
|
maxValue = value;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(maxValue < value) maxValue = value;
|
|
|
|
|
|
|
|
sumI += barCount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
svg.insertAdjacentHTML('beforeend', html);
|
|
|
|
const rects = Array.from(svg.children) as HTMLElement[];
|
|
|
|
|
|
|
|
let progress = audioEl.querySelector('.audio-waveform') as HTMLDivElement;
|
|
|
|
|
|
|
|
const onLoad = () => {
|
|
|
|
let audio = audioEl.audio;
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const onTimeUpdate = () => {
|
|
|
|
const lastIndex = audio.currentTime === audio.duration ? 0 : Math.ceil(audio.currentTime / audio.duration * barCount);
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
//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 */);
|
2020-06-16 23:48:08 +03:00
|
|
|
};
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) {
|
|
|
|
onTimeUpdate();
|
2020-06-16 23:48:08 +03:00
|
|
|
}
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate);
|
|
|
|
audioEl.addAudioListener('timeupdate', throttledTimeUpdate);
|
|
|
|
audioEl.addAudioListener('ended', throttledTimeUpdate);
|
2020-10-08 23:49:08 +03:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
audioEl.readyPromise.then(() => {
|
|
|
|
let mousedown = false, mousemove = false;
|
|
|
|
progress.addEventListener('mouseleave', (e) => {
|
|
|
|
if(mousedown) {
|
|
|
|
audio.play();
|
|
|
|
mousedown = false;
|
|
|
|
}
|
|
|
|
mousemove = false;
|
|
|
|
})
|
|
|
|
progress.addEventListener('mousemove', (e) => {
|
|
|
|
mousemove = true;
|
|
|
|
if(mousedown) scrub(e);
|
|
|
|
});
|
|
|
|
progress.addEventListener('mousedown', (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
if(e.button !== 0) return;
|
|
|
|
if(!audio.paused) {
|
|
|
|
audio.pause();
|
|
|
|
}
|
|
|
|
|
|
|
|
scrub(e);
|
|
|
|
mousedown = true;
|
|
|
|
});
|
|
|
|
progress.addEventListener('mouseup', (e) => {
|
|
|
|
if(mousemove && mousedown) {
|
|
|
|
audio.play();
|
|
|
|
mousedown = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
attachClickEvent(progress, (e) => {
|
|
|
|
cancelEvent(e);
|
|
|
|
if(!audio.paused) scrub(e);
|
|
|
|
});
|
2021-04-18 15:55:56 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
function scrub(e: MouseEvent | TouchEvent) {
|
|
|
|
let offsetX: number;
|
|
|
|
if(e instanceof MouseEvent) {
|
|
|
|
offsetX = e.offsetX;
|
|
|
|
} else { // touch
|
|
|
|
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
|
|
|
offsetX = e.targetTouches[0].pageX - rect.left;
|
|
|
|
}
|
|
|
|
|
|
|
|
const scrubTime = offsetX / availW /* width */ * audio.duration;
|
|
|
|
audio.currentTime = scrubTime;
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
2021-10-06 00:40:07 +04:00
|
|
|
}, noop);
|
2020-06-13 11:19:39 +03:00
|
|
|
|
|
|
|
return () => {
|
|
|
|
progress.remove();
|
|
|
|
progress = null;
|
|
|
|
audio = null;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
return onLoad;
|
|
|
|
}
|
|
|
|
|
2020-12-19 03:07:24 +02:00
|
|
|
function wrapAudio(audioEl: AudioElement) {
|
|
|
|
const withTime = audioEl.withTime;
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2020-12-25 14:53:20 +02:00
|
|
|
const message = audioEl.message;
|
2021-10-06 00:40:07 +04:00
|
|
|
const doc: MyDocument = appMessagesManager.getMediaFromMessage(message);
|
2020-12-25 14:53:20 +02:00
|
|
|
|
2021-09-26 20:03:45 +04:00
|
|
|
const isVoice = doc.type === 'voice' || doc.type === 'round';
|
|
|
|
const descriptionEl = document.createElement('div');
|
|
|
|
descriptionEl.classList.add('audio-description');
|
2020-12-25 14:53:20 +02:00
|
|
|
|
2021-09-26 20:03:45 +04:00
|
|
|
if(!isVoice) {
|
|
|
|
const parts: (Node | string)[] = [];
|
|
|
|
if(doc.audioPerformer) {
|
|
|
|
parts.push(htmlToSpan(doc.audioPerformer));
|
|
|
|
}
|
|
|
|
|
2020-12-25 14:53:20 +02:00
|
|
|
if(withTime) {
|
2021-10-07 01:45:41 +04:00
|
|
|
parts.push(formatFullSentTime(message.date));
|
2021-09-26 20:03:45 +04:00
|
|
|
} else if(!parts.length) {
|
|
|
|
parts.push(formatBytes(doc.size));
|
2020-12-25 14:53:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if(audioEl.showSender) {
|
2021-09-26 20:03:45 +04:00
|
|
|
parts.push(appMessagesManager.wrapSenderToPeer(message));
|
2020-12-25 14:53:20 +02:00
|
|
|
}
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2021-09-26 20:03:45 +04:00
|
|
|
descriptionEl.append(...joinElementsWith(parts, ' • '));
|
2020-12-25 14:53:20 +02:00
|
|
|
}
|
2020-12-22 00:38:26 +02:00
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
const html = `
|
|
|
|
<div class="audio-details">
|
2021-09-26 20:03:45 +04:00
|
|
|
<div class="audio-title"></div>
|
|
|
|
<div class="audio-subtitle"><div class="audio-time"></div></div>
|
2020-06-13 11:19:39 +03:00
|
|
|
</div>`;
|
|
|
|
audioEl.insertAdjacentHTML('beforeend', html);
|
|
|
|
|
2021-09-26 20:03:45 +04:00
|
|
|
const titleEl = audioEl.querySelector('.audio-title') as HTMLElement;
|
|
|
|
|
|
|
|
const middleEllipsisEl = new MiddleEllipsisElement();
|
|
|
|
middleEllipsisEl.dataset.fontWeight = audioEl.dataset.fontWeight;
|
|
|
|
if(isVoice) {
|
|
|
|
middleEllipsisEl.append(appMessagesManager.wrapSenderToPeer(message));
|
|
|
|
} else {
|
|
|
|
middleEllipsisEl.innerHTML = doc.audioTitle || doc.fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
titleEl.append(middleEllipsisEl);
|
|
|
|
|
|
|
|
if(audioEl.showSender) {
|
|
|
|
titleEl.append(appMessagesManager.wrapSentTime(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
const subtitleDiv = audioEl.querySelector('.audio-subtitle') as HTMLDivElement;
|
|
|
|
subtitleDiv.append(descriptionEl);
|
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
const onLoad = () => {
|
|
|
|
let launched = false;
|
|
|
|
|
2020-08-22 19:53:59 +03:00
|
|
|
let progressLine = new MediaProgressLine(audioEl.audio, doc.supportsStreaming);
|
2020-06-13 11:19:39 +03:00
|
|
|
|
|
|
|
audioEl.addAudioListener('ended', () => {
|
|
|
|
audioEl.classList.remove('audio-show-progress');
|
|
|
|
// Reset subtitle
|
2021-09-26 20:03:45 +04:00
|
|
|
subtitleDiv.lastChild.replaceWith(descriptionEl);
|
2020-06-13 11:19:39 +03:00
|
|
|
launched = false;
|
|
|
|
});
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
const onPlay = () => {
|
2020-06-13 11:19:39 +03:00
|
|
|
if(!launched) {
|
|
|
|
audioEl.classList.add('audio-show-progress');
|
|
|
|
launched = true;
|
|
|
|
|
|
|
|
if(progressLine) {
|
2020-12-22 00:38:26 +02:00
|
|
|
subtitleDiv.lastChild.replaceWith(progressLine.container);
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
audioEl.addAudioListener('play', onPlay);
|
2020-06-13 11:19:39 +03:00
|
|
|
|
|
|
|
if(!audioEl.audio.paused || audioEl.audio.currentTime > 0) {
|
2021-09-23 17:41:02 +04:00
|
|
|
onPlay();
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
progressLine.removeListeners();
|
|
|
|
progressLine.container.remove();
|
|
|
|
progressLine = null;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
return onLoad;
|
|
|
|
}
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
function constructDownloadPreloader(tryAgainOnFail = true) {
|
|
|
|
const preloader = new ProgressivePreloader({cancelable: true, tryAgainOnFail});
|
|
|
|
preloader.construct();
|
2021-09-28 17:07:56 +04:00
|
|
|
|
|
|
|
if(!tryAgainOnFail) {
|
|
|
|
preloader.circle.setAttributeNS(null, 'r', '23');
|
|
|
|
preloader.totalLength = 143.58203125;
|
|
|
|
}
|
2021-09-23 17:41:02 +04:00
|
|
|
|
|
|
|
return preloader;
|
|
|
|
}
|
|
|
|
|
2021-11-03 22:21:06 +04:00
|
|
|
export const findMediaTargets = (anchor: HTMLElement/* , useSearch: boolean */) => {
|
2021-10-06 00:40:07 +04:00
|
|
|
let prev: MediaItem[], next: MediaItem[];
|
|
|
|
// if(anchor.classList.contains('search-super-item') || !useSearch) {
|
2021-11-03 22:21:06 +04:00
|
|
|
const isBubbles = !anchor.classList.contains('search-super-item');
|
|
|
|
const container = findUpClassName(anchor, !isBubbles ? 'tabs-tab' : 'bubbles-inner');
|
2021-10-06 00:40:07 +04:00
|
|
|
if(container) {
|
|
|
|
const attr = `:not([data-is-outgoing="1"])`;
|
|
|
|
const justAudioSelector = `.audio:not(.is-voice)${attr}`;
|
2021-11-03 22:21:06 +04:00
|
|
|
let selectors: string[];
|
2021-10-06 00:40:07 +04:00
|
|
|
if(!anchor.matches(justAudioSelector)) {
|
2021-11-03 22:21:06 +04:00
|
|
|
selectors = [`.audio.is-voice${attr}`, `.media-round${attr}`];
|
2021-10-06 00:40:07 +04:00
|
|
|
} else {
|
2021-11-03 22:21:06 +04:00
|
|
|
selectors = [justAudioSelector];
|
2021-10-06 00:40:07 +04:00
|
|
|
}
|
|
|
|
|
2021-11-03 22:21:06 +04:00
|
|
|
if(isBubbles) {
|
|
|
|
const prefix = '.bubble:not(.webpage) ';
|
|
|
|
selectors = selectors.map(s => prefix + s);
|
|
|
|
}
|
|
|
|
|
|
|
|
const selector = selectors.join(', ');
|
|
|
|
|
2021-10-06 00:40:07 +04:00
|
|
|
const elements = Array.from(container.querySelectorAll(selector)) as HTMLElement[];
|
|
|
|
const idx = elements.indexOf(anchor);
|
|
|
|
|
2021-10-21 17:16:43 +04:00
|
|
|
const mediaItems: MediaItem[] = elements.map(element => ({peerId: element.dataset.peerId.toPeerId(), mid: +element.dataset.mid}));
|
2021-10-06 00:40:07 +04:00
|
|
|
|
|
|
|
prev = mediaItems.slice(0, idx);
|
|
|
|
next = mediaItems.slice(idx + 1);
|
|
|
|
}
|
|
|
|
// }
|
|
|
|
|
|
|
|
return [prev, next];
|
|
|
|
};
|
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
export default class AudioElement extends HTMLElement {
|
|
|
|
public audio: HTMLAudioElement;
|
2020-06-20 04:11:24 +03:00
|
|
|
public preloader: ProgressivePreloader;
|
2021-10-06 00:40:07 +04:00
|
|
|
public message: Message.message;
|
2020-12-19 03:07:24 +02:00
|
|
|
public withTime = false;
|
2020-12-25 14:53:20 +02:00
|
|
|
public voiceAsMusic = false;
|
2021-10-06 00:40:07 +04:00
|
|
|
public searchContext: MediaSearchContext;
|
2020-12-25 14:53:20 +02:00
|
|
|
public showSender = false;
|
2021-03-13 14:12:24 +04:00
|
|
|
public noAutoDownload: boolean;
|
2021-09-23 17:41:02 +04:00
|
|
|
public lazyLoadQueue: LazyLoadQueue;
|
|
|
|
public loadPromises: Promise<any>[];
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
private listenerSetter = new ListenerSetter();
|
2020-06-13 11:19:39 +03:00
|
|
|
private onTypeDisconnect: () => void;
|
2021-01-18 22:34:41 +04:00
|
|
|
public onLoad: (autoload?: boolean) => void;
|
2021-09-28 17:07:56 +04:00
|
|
|
public readyPromise: CancellablePromise<void>;
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2020-12-24 10:19:56 +02:00
|
|
|
public render() {
|
2020-06-13 11:19:39 +03:00
|
|
|
this.classList.add('audio');
|
|
|
|
|
2021-10-06 00:40:07 +04:00
|
|
|
this.dataset.mid = '' + this.message.mid;
|
|
|
|
this.dataset.peerId = '' + this.message.peerId;
|
|
|
|
|
|
|
|
const doc: MyDocument = appMessagesManager.getMediaFromMessage(this.message);
|
2021-02-04 02:30:23 +02:00
|
|
|
const isRealVoice = doc.type === 'voice';
|
2020-12-25 19:38:32 +02:00
|
|
|
const isVoice = !this.voiceAsMusic && isRealVoice;
|
2021-03-10 19:37:50 +04:00
|
|
|
const isOutgoing = this.message.pFlags.is_outgoing;
|
|
|
|
const uploading = isOutgoing && this.preloader;
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2020-12-22 00:38:26 +02:00
|
|
|
const durationStr = String(doc.duration | 0).toHHMMSS();
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
this.innerHTML = `
|
|
|
|
<div class="audio-toggle audio-ico">
|
|
|
|
<div class="audio-play-icon">
|
|
|
|
<div class="part one" x="0" y="0" fill="#fff"></div>
|
|
|
|
<div class="part two" x="0" y="0" fill="#fff"></div>
|
|
|
|
</div>
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
const toggle = this.firstElementChild as HTMLElement;
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2020-08-22 19:53:59 +03:00
|
|
|
const downloadDiv = document.createElement('div');
|
|
|
|
downloadDiv.classList.add('audio-download');
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const isUnread = doc.type !== 'audio' && this.message && this.message.pFlags.media_unread;
|
|
|
|
if(isUnread) {
|
|
|
|
this.classList.add('is-unread');
|
|
|
|
}
|
|
|
|
|
2021-01-18 22:34:41 +04:00
|
|
|
if(uploading) {
|
2021-09-23 17:41:02 +04:00
|
|
|
this.classList.add('is-outgoing');
|
2020-08-22 19:53:59 +03:00
|
|
|
this.append(downloadDiv);
|
|
|
|
}
|
|
|
|
|
2020-12-22 00:38:26 +02:00
|
|
|
const onTypeLoad = isVoice ? wrapVoiceMessage(this) : wrapAudio(this);
|
2020-08-22 19:53:59 +03:00
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement;
|
|
|
|
audioTimeDiv.innerHTML = durationStr;
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const onLoad = this.onLoad = (autoload: boolean) => {
|
2021-01-18 22:34:41 +04:00
|
|
|
this.onLoad = undefined;
|
|
|
|
|
2021-10-06 00:40:07 +04:00
|
|
|
const audio = this.audio = appMediaPlaybackController.addMedia(this.message, autoload);
|
2020-06-13 11:19:39 +03:00
|
|
|
|
2021-10-07 01:18:56 +04:00
|
|
|
const readyPromise = this.readyPromise = deferredPromise<void>();
|
|
|
|
if(this.audio.readyState >= this.audio.HAVE_CURRENT_DATA) readyPromise.resolve();
|
2021-09-28 17:07:56 +04:00
|
|
|
else {
|
2021-10-07 01:18:56 +04:00
|
|
|
this.addAudioListener('canplay', () => readyPromise.resolve(), {once: true});
|
2021-09-28 17:07:56 +04:00
|
|
|
}
|
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
this.onTypeDisconnect = onTypeLoad();
|
|
|
|
|
2020-12-22 00:38:26 +02:00
|
|
|
const getTimeStr = () => String(audio.currentTime | 0).toHHMMSS() + (isVoice ? (' / ' + durationStr) : '');
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
const onPlay = () => {
|
2020-12-22 00:38:26 +02:00
|
|
|
audioTimeDiv.innerText = getTimeStr();
|
2021-01-18 22:34:41 +04:00
|
|
|
toggle.classList.toggle('playing', !audio.paused);
|
2020-06-13 11:19:39 +03:00
|
|
|
};
|
|
|
|
|
2021-02-04 02:30:23 +02:00
|
|
|
if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) {
|
2021-09-23 17:41:02 +04:00
|
|
|
onPlay();
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
const togglePlay = (e?: Event, paused = audio.paused) => {
|
|
|
|
e && cancelEvent(e);
|
|
|
|
|
|
|
|
if(paused) {
|
2021-11-03 22:21:06 +04:00
|
|
|
const hadSearchContext = !!this.searchContext;
|
|
|
|
if(appMediaPlaybackController.setSearchContext(this.searchContext || {
|
|
|
|
peerId: NULL_PEER_ID,
|
|
|
|
inputFilter: {_: 'inputMessagesFilterEmpty'},
|
|
|
|
useSearch: false
|
|
|
|
})) {
|
|
|
|
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(this/* , this.searchContext.useSearch */);
|
2021-09-24 19:33:33 +04:00
|
|
|
appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next);
|
|
|
|
}
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
audio.play().catch(() => {});
|
|
|
|
} else {
|
|
|
|
audio.pause();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
attachClickEvent(toggle, (e) => togglePlay(e), {listenerSetter: this.listenerSetter});
|
2020-11-24 06:13:16 +02:00
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
this.addAudioListener('ended', () => {
|
2021-01-18 22:34:41 +04:00
|
|
|
toggle.classList.remove('playing');
|
2021-09-23 17:41:02 +04:00
|
|
|
audioTimeDiv.innerText = durationStr;
|
2020-06-13 11:19:39 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
this.addAudioListener('timeupdate', () => {
|
2021-09-24 19:33:33 +04:00
|
|
|
if((!audio.currentTime && audio.paused) || appMediaPlaybackController.isSafariBuffering(audio)) return;
|
2020-12-22 00:38:26 +02:00
|
|
|
audioTimeDiv.innerText = getTimeStr();
|
2020-06-13 11:19:39 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
this.addAudioListener('pause', () => {
|
2021-01-18 22:34:41 +04:00
|
|
|
toggle.classList.remove('playing');
|
2020-06-13 11:19:39 +03:00
|
|
|
});
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
this.addAudioListener('play', onPlay);
|
|
|
|
|
|
|
|
return togglePlay;
|
2020-06-13 11:19:39 +03:00
|
|
|
};
|
|
|
|
|
2021-10-21 17:16:43 +04:00
|
|
|
if(doc.thumbs?.length) {
|
|
|
|
const imgs: HTMLImageElement[] = [];
|
|
|
|
const wrapped = wrapPhoto({
|
|
|
|
photo: doc,
|
|
|
|
message: null,
|
|
|
|
container: toggle,
|
|
|
|
boxWidth: 48,
|
|
|
|
boxHeight: 48,
|
|
|
|
loadPromises: this.loadPromises,
|
|
|
|
withoutPreloader: true,
|
|
|
|
lazyLoadQueue: this.lazyLoadQueue
|
|
|
|
});
|
|
|
|
toggle.style.width = toggle.style.height = '';
|
|
|
|
if(wrapped.images.thumb) imgs.push(wrapped.images.thumb);
|
|
|
|
if(wrapped.images.full) imgs.push(wrapped.images.full);
|
|
|
|
|
|
|
|
this.classList.add('audio-with-thumb');
|
|
|
|
imgs.forEach(img => img.classList.add('audio-thumb'));
|
|
|
|
}
|
|
|
|
|
2021-03-10 19:37:50 +04:00
|
|
|
if(!isOutgoing) {
|
2020-08-22 19:53:59 +03:00
|
|
|
let preloader: ProgressivePreloader = this.preloader;
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
onLoad(doc.type !== 'audio' && !this.noAutoDownload);
|
|
|
|
|
|
|
|
const r = (shouldPlay: boolean) => {
|
|
|
|
if(this.audio.src) {
|
|
|
|
return;
|
2021-01-18 22:34:41 +04:00
|
|
|
}
|
|
|
|
|
2021-10-06 00:40:07 +04:00
|
|
|
appMediaPlaybackController.resolveWaitingForLoadMedia(this.message.peerId, this.message.mid, this.message.pFlags.is_scheduled);
|
2021-01-18 22:34:41 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const onDownloadInit = () => {
|
|
|
|
if(shouldPlay) {
|
|
|
|
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
|
|
|
|
|
|
|
|
if(IS_SAFARI && !this.audio.autoplay) {
|
|
|
|
this.audio.autoplay = true;
|
|
|
|
}
|
2020-11-24 06:13:16 +02:00
|
|
|
}
|
2020-08-22 19:53:59 +03:00
|
|
|
};
|
2021-03-13 14:12:24 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
onDownloadInit();
|
2021-09-23 17:41:02 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
if(!preloader) {
|
|
|
|
if(doc.supportsStreaming) {
|
|
|
|
this.classList.add('corner-download');
|
|
|
|
|
|
|
|
let pauseListener: Listener;
|
|
|
|
const onPlay = () => {
|
|
|
|
const preloader = constructDownloadPreloader(false);
|
|
|
|
const deferred = deferredPromise<void>();
|
|
|
|
deferred.notifyAll({done: 75, total: 100});
|
|
|
|
deferred.catch(() => {
|
|
|
|
this.audio.pause();
|
|
|
|
appMediaPlaybackController.willBePlayed(undefined);
|
|
|
|
});
|
|
|
|
deferred.cancel = () => {
|
|
|
|
deferred.cancel = noop;
|
|
|
|
const err = new Error();
|
|
|
|
(err as any).type = 'CANCELED';
|
|
|
|
deferred.reject(err);
|
|
|
|
};
|
|
|
|
preloader.attach(downloadDiv, false, deferred);
|
|
|
|
|
|
|
|
pauseListener = this.addAudioListener('pause', () => {
|
|
|
|
deferred.cancel();
|
|
|
|
}, {once: true}) as any;
|
|
|
|
|
|
|
|
onDownloadInit();
|
|
|
|
};
|
|
|
|
|
|
|
|
/* if(!this.audio.paused) {
|
|
|
|
onPlay();
|
|
|
|
} */
|
|
|
|
|
|
|
|
const playListener: any = this.addAudioListener('play', onPlay);
|
|
|
|
this.readyPromise.then(() => {
|
|
|
|
this.listenerSetter.remove(playListener);
|
|
|
|
this.listenerSetter.remove(pauseListener);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
preloader = constructDownloadPreloader();
|
2021-09-23 17:41:02 +04:00
|
|
|
|
2021-10-07 01:18:56 +04:00
|
|
|
if(!shouldPlay) {
|
|
|
|
this.readyPromise = deferredPromise();
|
|
|
|
}
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const load = () => {
|
|
|
|
onDownloadInit();
|
2021-09-23 17:41:02 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
const download = appDocsManager.downloadDoc(doc);
|
2021-10-07 01:18:56 +04:00
|
|
|
|
|
|
|
if(!shouldPlay) {
|
|
|
|
download.then(() => {
|
|
|
|
this.readyPromise.resolve();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
preloader.attach(downloadDiv, false, download);
|
|
|
|
return {download};
|
|
|
|
};
|
2021-09-23 17:41:02 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
preloader.setDownloadFunction(load);
|
|
|
|
load();
|
|
|
|
}
|
|
|
|
}
|
2020-08-30 13:43:57 +03:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
this.append(downloadDiv);
|
2021-09-23 17:41:02 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
this.classList.add('downloading');
|
2021-09-26 20:03:45 +04:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
this.readyPromise.then(() => {
|
|
|
|
this.classList.remove('downloading');
|
|
|
|
downloadDiv.classList.add('downloaded');
|
|
|
|
setTimeout(() => {
|
|
|
|
downloadDiv.remove();
|
|
|
|
}, 200);
|
2020-08-28 15:37:15 +03:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
//setTimeout(() => {
|
|
|
|
// release loaded audio
|
|
|
|
if(appMediaPlaybackController.willBePlayedMedia === this.audio) {
|
|
|
|
this.audio.play();
|
|
|
|
appMediaPlaybackController.willBePlayed(undefined);
|
|
|
|
}
|
|
|
|
//}, 10e3);
|
|
|
|
});
|
|
|
|
};
|
2020-11-24 06:13:16 +02:00
|
|
|
|
2021-09-28 17:07:56 +04:00
|
|
|
if(!this.audio?.src) {
|
|
|
|
if(doc.type !== 'audio' && !this.noAutoDownload) {
|
|
|
|
r(false);
|
|
|
|
} else {
|
|
|
|
attachClickEvent(toggle, () => {
|
|
|
|
r(true);
|
|
|
|
}, {once: true, capture: true, passive: false, listenerSetter: this.listenerSetter});
|
|
|
|
}
|
2020-08-22 19:53:59 +03:00
|
|
|
}
|
2021-03-10 19:37:50 +04:00
|
|
|
} else if(uploading) {
|
2021-10-06 00:40:07 +04:00
|
|
|
this.dataset.isOutgoing = '1';
|
2020-08-22 19:53:59 +03:00
|
|
|
this.preloader.attach(downloadDiv, false);
|
2020-06-20 04:11:24 +03:00
|
|
|
//onLoad();
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
get addAudioListener() {
|
|
|
|
return this.listenerSetter.add(this.audio);
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
disconnectedCallback() {
|
2021-01-18 22:34:41 +04:00
|
|
|
if(this.isConnected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-13 11:19:39 +03:00
|
|
|
if(this.onTypeDisconnect) {
|
|
|
|
this.onTypeDisconnect();
|
|
|
|
this.onTypeDisconnect = null;
|
|
|
|
}
|
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
if(this.readyPromise) {
|
|
|
|
this.readyPromise.reject();
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
2020-06-20 04:11:24 +03:00
|
|
|
|
2021-09-23 17:41:02 +04:00
|
|
|
this.listenerSetter.removeAll();
|
|
|
|
this.listenerSetter = null;
|
|
|
|
|
2020-06-20 04:11:24 +03:00
|
|
|
this.preloader = null;
|
2020-06-13 11:19:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-10 19:37:50 +04:00
|
|
|
customElements.define("audio-element", AudioElement);
|