Audio fixes
Fix loading round video when auto download is off Added unread status for video messages
This commit is contained in:
parent
22328cc2af
commit
a15b0807c4
@ -56,6 +56,7 @@ class AppMediaPlaybackController {
|
||||
[mid: string]: CancellablePromise<void>
|
||||
}
|
||||
} = {};
|
||||
private waitingDocumentsForLoad: {[docId: string]: Set<HTMLMediaElement>} = {};
|
||||
|
||||
public willBePlayedMedia: HTMLMediaElement;
|
||||
private searchContext: SearchSuperContext;
|
||||
@ -88,6 +89,15 @@ class AppMediaPlaybackController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootScope.addEventListener('document_downloaded', (doc) => {
|
||||
const set = this.waitingDocumentsForLoad[doc.id];
|
||||
if(set) {
|
||||
for(const media of set) {
|
||||
this.onMediaDocumentLoad(media);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public seekBackward = (details: MediaSessionActionDetails) => {
|
||||
@ -124,6 +134,7 @@ class AppMediaPlaybackController {
|
||||
//media.muted = true;
|
||||
}
|
||||
|
||||
media.dataset.docId = '' + doc.id;
|
||||
media.dataset.peerId = '' + peerId;
|
||||
media.dataset.mid = '' + mid;
|
||||
media.dataset.type = doc.type;
|
||||
@ -137,6 +148,13 @@ class AppMediaPlaybackController {
|
||||
media.addEventListener('play', this.onPlay);
|
||||
media.addEventListener('pause', this.onPause);
|
||||
media.addEventListener('ended', this.onEnded);
|
||||
|
||||
const message: Message.message = appMessagesManager.getMessageByPeer(peerId, mid);
|
||||
if(doc.type !== 'audio' && message?.pFlags.media_unread && message.fromId !== rootScope.myId) {
|
||||
media.addEventListener('timeupdate', () => {
|
||||
appMessagesManager.readMessages(peerId, [mid]);
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
/* const onError = (e: Event) => {
|
||||
//console.log('appMediaPlaybackController: video onError', e);
|
||||
@ -164,21 +182,44 @@ class AppMediaPlaybackController {
|
||||
//media.autoplay = true;
|
||||
//console.log('will set media url:', media, doc, doc.type, doc.url);
|
||||
|
||||
((!doc.supportsStreaming ? appDocsManager.downloadDoc(doc) : Promise.resolve()) as Promise<any>).then(() => {
|
||||
if(doc.type === 'audio' && doc.supportsStreaming && SHOULD_USE_SAFARI_FIX) {
|
||||
this.handleSafariStreamable(media);
|
||||
const cacheContext = appDownloadManager.getCacheContext(doc);
|
||||
if(doc.supportsStreaming || cacheContext.url) {
|
||||
this.onMediaDocumentLoad(media);
|
||||
} else {
|
||||
let set = this.waitingDocumentsForLoad[doc.id];
|
||||
if(!set) {
|
||||
set = this.waitingDocumentsForLoad[doc.id] = new Set();
|
||||
}
|
||||
|
||||
// setTimeout(() => {
|
||||
const cacheContext = appDownloadManager.getCacheContext(doc);
|
||||
media.src = cacheContext.url;
|
||||
// }, doc.supportsStreaming ? 500e3 : 0);
|
||||
});
|
||||
|
||||
set.add(media);
|
||||
appDocsManager.downloadDoc(doc);
|
||||
}
|
||||
}/* , onError */);
|
||||
|
||||
return storage[mid] = media;
|
||||
}
|
||||
|
||||
private onMediaDocumentLoad = (media: HTMLMediaElement) => {
|
||||
const doc = appDocsManager.getDoc(media.dataset.docId);
|
||||
if(doc.type === 'audio' && doc.supportsStreaming && SHOULD_USE_SAFARI_FIX) {
|
||||
this.handleSafariStreamable(media);
|
||||
}
|
||||
|
||||
// setTimeout(() => {
|
||||
const cacheContext = appDownloadManager.getCacheContext(doc);
|
||||
media.src = cacheContext.url;
|
||||
// }, doc.supportsStreaming ? 500e3 : 0);
|
||||
|
||||
const set = this.waitingDocumentsForLoad[doc.id];
|
||||
if(set) {
|
||||
set.delete(media);
|
||||
|
||||
if(!set.size) {
|
||||
delete this.waitingDocumentsForLoad[doc.id];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// safari подгрузит последний чанк и песня включится,
|
||||
// при этом этот чанк нельзя руками отдать из SW, потому что браузер тогда теряется
|
||||
private handleSafariStreamable(media: HTMLMediaElement) {
|
||||
@ -432,10 +473,10 @@ class AppMediaPlaybackController {
|
||||
media.autoplay = true;
|
||||
} */
|
||||
|
||||
this.resolveWaitingForLoadMedia(peerId, mid);
|
||||
|
||||
media.play();
|
||||
|
||||
setTimeout(() => {
|
||||
media.play()//.catch(() => {});
|
||||
this.resolveWaitingForLoadMedia(peerId, mid);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
@ -720,7 +720,8 @@ export default class AppSearchSuper {
|
||||
voiceAsMusic: true,
|
||||
showSender,
|
||||
searchContext: this.copySearchContext(inputFilter),
|
||||
lazyLoadQueue: this.lazyLoadQueue
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
noAutoDownload: true
|
||||
});
|
||||
|
||||
if((['audio', 'voice', 'round'] as MyDocument['type'][]).includes(message.media.document.type)) {
|
||||
|
@ -17,7 +17,7 @@ import rootScope from "../lib/rootScope";
|
||||
import './middleEllipsis';
|
||||
import { SearchSuperContext } from "./appSearchSuper.";
|
||||
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
||||
import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent";
|
||||
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
||||
import LazyLoadQueue from "./lazyLoadQueue";
|
||||
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
||||
import ListenerSetter, { Listener } from "../helpers/listenerSetter";
|
||||
@ -28,10 +28,12 @@ import { MiddleEllipsisElement } from "./middleEllipsis";
|
||||
import htmlToSpan from "../helpers/dom/htmlToSpan";
|
||||
import { formatFullSentTime } from "../helpers/date";
|
||||
import { formatBytes } from "../helpers/number";
|
||||
import throttleWithRaf from "../helpers/schedulers/throttleWithRaf";
|
||||
|
||||
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
|
||||
mids.forEach(mid => {
|
||||
(Array.from(document.querySelectorAll('audio-element[data-mid="' + mid + '"][data-peer-id="' + peerId + '"].is-unread')) as AudioElement[]).forEach(elem => {
|
||||
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 => {
|
||||
elem.classList.remove('is-unread');
|
||||
});
|
||||
});
|
||||
@ -80,11 +82,6 @@ function wrapVoiceMessage(audioEl: AudioElement) {
|
||||
|
||||
const message = audioEl.message;
|
||||
const doc = (message.media.document || message.media.webpage.document) as MyDocument;
|
||||
const isOut = message.fromId === rootScope.myId && message.peerId !== rootScope.myId;
|
||||
let isUnread = message && message.pFlags.media_unread;
|
||||
if(isUnread) {
|
||||
audioEl.classList.add('is-unread');
|
||||
}
|
||||
|
||||
if(message.pFlags.out) {
|
||||
audioEl.classList.add('is-out');
|
||||
@ -154,113 +151,76 @@ function wrapVoiceMessage(audioEl: AudioElement) {
|
||||
let progress = audioEl.querySelector('.audio-waveform') as HTMLDivElement;
|
||||
|
||||
const onLoad = () => {
|
||||
let interval = 0;
|
||||
let lastIndex = 0;
|
||||
|
||||
let audio = audioEl.audio;
|
||||
|
||||
if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) {
|
||||
lastIndex = Math.round(audio.currentTime / audio.duration * barCount);
|
||||
rects.slice(0, lastIndex + 1).forEach(node => node.classList.add('active'));
|
||||
}
|
||||
|
||||
let start = () => {
|
||||
clearInterval(interval);
|
||||
interval = window.setInterval(() => {
|
||||
if(lastIndex > svg.childElementCount || isNaN(audio.duration) || audio.paused) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
lastIndex = Math.round(audio.currentTime / audio.duration * barCount);
|
||||
const onTimeUpdate = () => {
|
||||
const lastIndex = audio.currentTime === audio.duration ? 0 : Math.ceil(audio.currentTime / audio.duration * barCount);
|
||||
|
||||
//svg.children[lastIndex].setAttributeNS(null, 'fill', '#000');
|
||||
//svg.children[lastIndex].classList.add('active'); #Иногда пропускает полоски..
|
||||
rects.slice(0, lastIndex + 1).forEach(node => node.classList.add('active'));
|
||||
//++lastIndex;
|
||||
//console.log('lastIndex:', lastIndex, audio.currentTime);
|
||||
//}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */);
|
||||
}, 20);
|
||||
//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) {
|
||||
start();
|
||||
if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) {
|
||||
onTimeUpdate();
|
||||
}
|
||||
|
||||
audioEl.addAudioListener('play', () => {
|
||||
if(isUnread && !isOut && audioEl.classList.contains('is-unread')) {
|
||||
audioEl.classList.remove('is-unread');
|
||||
appMessagesManager.readMessages(audioEl.message.peerId, [audioEl.message.mid]);
|
||||
isUnread = false;
|
||||
}
|
||||
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate);
|
||||
audioEl.addAudioListener('timeupdate', throttledTimeUpdate);
|
||||
audioEl.addAudioListener('ended', throttledTimeUpdate);
|
||||
|
||||
//rects.forEach(node => node.classList.remove('active'));
|
||||
start();
|
||||
});
|
||||
|
||||
audioEl.addAudioListener('pause', () => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
audioEl.addAudioListener('ended', () => {
|
||||
clearInterval(interval);
|
||||
rects.forEach(node => node.classList.remove('active'));
|
||||
});
|
||||
|
||||
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 !== 1) return;
|
||||
if(!audio.paused) {
|
||||
audio.pause();
|
||||
}
|
||||
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);
|
||||
});
|
||||
|
||||
scrub(e);
|
||||
mousedown = true;
|
||||
});
|
||||
progress.addEventListener('mouseup', (e) => {
|
||||
if (mousemove && mousedown) {
|
||||
audio.play();
|
||||
mousedown = false;
|
||||
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;
|
||||
}
|
||||
});
|
||||
attachClickEvent(progress, (e) => {
|
||||
cancelEvent(e);
|
||||
if(!audio.paused) scrub(e);
|
||||
});
|
||||
|
||||
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;
|
||||
lastIndex = Math.round(scrubTime / audio.duration * barCount);
|
||||
|
||||
rects.slice(0, lastIndex + 1).forEach(node => node.classList.add('active'));
|
||||
for(let i = lastIndex + 1; i < rects.length; ++i) {
|
||||
rects[i].classList.remove('active')
|
||||
}
|
||||
audio.currentTime = scrubTime;
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
progress.remove();
|
||||
progress = null;
|
||||
audio = null;
|
||||
@ -367,8 +327,11 @@ function wrapAudio(audioEl: AudioElement) {
|
||||
function constructDownloadPreloader(tryAgainOnFail = true) {
|
||||
const preloader = new ProgressivePreloader({cancelable: true, tryAgainOnFail});
|
||||
preloader.construct();
|
||||
preloader.circle.setAttributeNS(null, 'r', '23');
|
||||
preloader.totalLength = 143.58203125;
|
||||
|
||||
if(!tryAgainOnFail) {
|
||||
preloader.circle.setAttributeNS(null, 'r', '23');
|
||||
preloader.totalLength = 143.58203125;
|
||||
}
|
||||
|
||||
return preloader;
|
||||
}
|
||||
@ -388,7 +351,7 @@ export default class AudioElement extends HTMLElement {
|
||||
private listenerSetter = new ListenerSetter();
|
||||
private onTypeDisconnect: () => void;
|
||||
public onLoad: (autoload?: boolean) => void;
|
||||
private readyPromise: CancellablePromise<void>;
|
||||
public readyPromise: CancellablePromise<void>;
|
||||
|
||||
public render() {
|
||||
this.classList.add('audio');
|
||||
@ -414,6 +377,11 @@ export default class AudioElement extends HTMLElement {
|
||||
const downloadDiv = document.createElement('div');
|
||||
downloadDiv.classList.add('audio-download');
|
||||
|
||||
const isUnread = doc.type !== 'audio' && this.message && this.message.pFlags.media_unread;
|
||||
if(isUnread) {
|
||||
this.classList.add('is-unread');
|
||||
}
|
||||
|
||||
if(uploading) {
|
||||
this.classList.add('is-outgoing');
|
||||
this.append(downloadDiv);
|
||||
@ -424,11 +392,17 @@ export default class AudioElement extends HTMLElement {
|
||||
const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement;
|
||||
audioTimeDiv.innerHTML = durationStr;
|
||||
|
||||
const onLoad = this.onLoad = (autoload = true) => {
|
||||
const onLoad = this.onLoad = (autoload: boolean) => {
|
||||
this.onLoad = undefined;
|
||||
|
||||
const audio = this.audio = appMediaPlaybackController.addMedia(this.message.peerId, this.message.media.document || this.message.media.webpage.document, this.message.mid, autoload);
|
||||
|
||||
this.readyPromise = deferredPromise<void>();
|
||||
if(this.audio.readyState >= 2) this.readyPromise.resolve();
|
||||
else {
|
||||
this.addAudioListener('canplay', () => this.readyPromise.resolve(), {once: true});
|
||||
}
|
||||
|
||||
this.onTypeDisconnect = onTypeLoad();
|
||||
|
||||
const getTimeStr = () => String(audio.currentTime | 0).toHHMMSS() + (isVoice ? (' / ' + durationStr) : '');
|
||||
@ -492,174 +466,129 @@ export default class AudioElement extends HTMLElement {
|
||||
if(!isOutgoing) {
|
||||
let preloader: ProgressivePreloader = this.preloader;
|
||||
|
||||
const getDownloadPromise = () => appDocsManager.downloadDoc(doc);
|
||||
onLoad(doc.type !== 'audio' && !this.noAutoDownload);
|
||||
|
||||
if(isRealVoice) {
|
||||
if(!preloader) {
|
||||
preloader = constructDownloadPreloader();
|
||||
if(doc.thumbs) {
|
||||
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'));
|
||||
}
|
||||
|
||||
const r = (shouldPlay: boolean) => {
|
||||
if(this.audio.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
const load = () => {
|
||||
const download = getDownloadPromise();
|
||||
preloader.attach(downloadDiv, false, download);
|
||||
appMediaPlaybackController.resolveWaitingForLoadMedia(this.message.peerId, this.message.mid);
|
||||
|
||||
if(!downloadDiv.parentElement) {
|
||||
this.append(downloadDiv);
|
||||
}
|
||||
|
||||
(download as Promise<any>).then(() => {
|
||||
detachClickEvent(this, onClick);
|
||||
onLoad();
|
||||
|
||||
downloadDiv.classList.add('downloaded');
|
||||
setTimeout(() => {
|
||||
downloadDiv.remove();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
return {download};
|
||||
};
|
||||
|
||||
// preloader.construct();
|
||||
preloader.setManual();
|
||||
preloader.attach(downloadDiv);
|
||||
preloader.setDownloadFunction(load);
|
||||
|
||||
const onClick = (e?: Event) => {
|
||||
preloader.onClick(e);
|
||||
};
|
||||
|
||||
attachClickEvent(this, onClick);
|
||||
|
||||
if(!this.noAutoDownload) {
|
||||
onClick();
|
||||
}
|
||||
} else {
|
||||
// if(doc.supportsStreaming) {
|
||||
onLoad(false);
|
||||
// }
|
||||
|
||||
if(doc.thumbs) {
|
||||
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'));
|
||||
}
|
||||
|
||||
//if(appMediaPlaybackController.mediaExists(mid)) { // чтобы показать прогресс, если аудио уже было скачано
|
||||
//onLoad();
|
||||
//} else {
|
||||
const r = (e: Event) => {
|
||||
if(!this.audio) {
|
||||
const togglePlay = onLoad(false);
|
||||
}
|
||||
|
||||
if(this.audio.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
appMediaPlaybackController.resolveWaitingForLoadMedia(this.message.peerId, this.message.mid);
|
||||
const onDownloadInit = () => {
|
||||
if(shouldPlay) {
|
||||
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
|
||||
|
||||
if(IS_SAFARI) {
|
||||
|
||||
if(IS_SAFARI && !this.audio.autoplay) {
|
||||
this.audio.autoplay = true;
|
||||
}
|
||||
|
||||
// togglePlay(undefined, true);
|
||||
|
||||
this.readyPromise = deferredPromise<void>();
|
||||
if(this.audio.readyState >= 2) this.readyPromise.resolve();
|
||||
else {
|
||||
this.addAudioListener('canplay', () => this.readyPromise.resolve(), {once: true});
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/* 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();
|
||||
|
||||
const load = () => {
|
||||
const download = getDownloadPromise();
|
||||
preloader.attach(downloadDiv, false, download);
|
||||
return {download};
|
||||
};
|
||||
|
||||
preloader.setDownloadFunction(load);
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
this.append(downloadDiv);
|
||||
|
||||
this.classList.add('downloading');
|
||||
|
||||
this.readyPromise.then(() => {
|
||||
this.classList.remove('downloading');
|
||||
downloadDiv.classList.add('downloaded');
|
||||
setTimeout(() => {
|
||||
downloadDiv.remove();
|
||||
}, 200);
|
||||
|
||||
//setTimeout(() => {
|
||||
// release loaded audio
|
||||
if(appMediaPlaybackController.willBePlayedMedia === this.audio) {
|
||||
this.audio.play();
|
||||
appMediaPlaybackController.willBePlayed(undefined);
|
||||
}
|
||||
//}, 10e3);
|
||||
});
|
||||
};
|
||||
|
||||
if(!this.audio?.src) {
|
||||
attachClickEvent(toggle, r, {once: true, capture: true, passive: false});
|
||||
}
|
||||
//}
|
||||
};
|
||||
|
||||
onDownloadInit();
|
||||
|
||||
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();
|
||||
|
||||
const load = () => {
|
||||
onDownloadInit();
|
||||
|
||||
const download = appDocsManager.downloadDoc(doc);
|
||||
preloader.attach(downloadDiv, false, download);
|
||||
return {download};
|
||||
};
|
||||
|
||||
preloader.setDownloadFunction(load);
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
this.append(downloadDiv);
|
||||
|
||||
this.classList.add('downloading');
|
||||
|
||||
this.readyPromise.then(() => {
|
||||
this.classList.remove('downloading');
|
||||
downloadDiv.classList.add('downloaded');
|
||||
setTimeout(() => {
|
||||
downloadDiv.remove();
|
||||
}, 200);
|
||||
|
||||
//setTimeout(() => {
|
||||
// release loaded audio
|
||||
if(appMediaPlaybackController.willBePlayedMedia === this.audio) {
|
||||
this.audio.play();
|
||||
appMediaPlaybackController.willBePlayed(undefined);
|
||||
}
|
||||
//}, 10e3);
|
||||
});
|
||||
};
|
||||
|
||||
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});
|
||||
}
|
||||
}
|
||||
} else if(uploading) {
|
||||
this.preloader.attach(downloadDiv, false);
|
||||
|
@ -12,7 +12,7 @@ import { toast } from "../toast";
|
||||
import { prepareAlbum, wrapDocument } from "../wrappers";
|
||||
import CheckboxField from "../checkboxField";
|
||||
import SendContextMenu from "../chat/sendContextMenu";
|
||||
import { createPosterFromVideo, onVideoLoad } from "../../helpers/files";
|
||||
import { createPosterFromVideo, onMediaLoad } from "../../helpers/files";
|
||||
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||
import I18n, { i18n, LangPackKey } from "../../lib/langPack";
|
||||
import appDownloadManager from "../../lib/appManagers/appDownloadManager";
|
||||
@ -262,7 +262,7 @@ export default class PopupNewMedia extends PopupElement {
|
||||
video.pause();
|
||||
}, {once: true});
|
||||
|
||||
onVideoLoad(video).then(() => {
|
||||
onMediaLoad(video).then(() => {
|
||||
params.width = video.videoWidth;
|
||||
params.height = video.videoHeight;
|
||||
params.duration = Math.floor(video.duration);
|
||||
|
@ -179,7 +179,7 @@ export default class ProgressivePreloader {
|
||||
}
|
||||
} else {
|
||||
if(this.tryAgainOnFail) {
|
||||
SetTransition(this.preloader, '', true, TRANSITION_TIME);
|
||||
this.attach(this.preloader.parentElement);
|
||||
fastRaf(() => {
|
||||
this.setManual();
|
||||
});
|
||||
@ -211,47 +211,38 @@ export default class ProgressivePreloader {
|
||||
}
|
||||
|
||||
public attach(elem: Element, reset = false, promise?: CancellablePromise<any>) {
|
||||
//return;
|
||||
if(this.construct) {
|
||||
this.construct();
|
||||
}
|
||||
|
||||
if(this.preloader.parentElement) {
|
||||
this.preloader.classList.remove('manual');
|
||||
}
|
||||
|
||||
this.detached = false;
|
||||
/* fastRaf(() => {
|
||||
if(this.detached) return;
|
||||
this.detached = false; */
|
||||
|
||||
if(this.construct) {
|
||||
this.construct();
|
||||
}
|
||||
|
||||
if(this.preloader.parentElement) {
|
||||
this.preloader.classList.remove('manual');
|
||||
}
|
||||
if(promise/* && false */) {
|
||||
this.attachPromise(promise);
|
||||
}
|
||||
|
||||
if(this.detached || this.preloader.parentElement !== elem) {
|
||||
const useRafs = isInDOM(this.preloader) ? 1 : 2;
|
||||
if(this.preloader.parentElement !== elem) {
|
||||
elem[this.attachMethod](this.preloader);
|
||||
}
|
||||
|
||||
if(promise/* && false */) {
|
||||
this.attachPromise(promise);
|
||||
}
|
||||
SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME, undefined, useRafs);
|
||||
}
|
||||
|
||||
// fastRaf(() => {
|
||||
//console.log('[PP]: attach after rAF', this.detached, performance.now());
|
||||
|
||||
// if(this.detached) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME, undefined, useRafs);
|
||||
// });
|
||||
|
||||
if(this.cancelable && reset) {
|
||||
this.setProgress(0);
|
||||
}
|
||||
//});
|
||||
if(this.cancelable && reset) {
|
||||
this.setProgress(0);
|
||||
}
|
||||
}
|
||||
|
||||
public detach() {
|
||||
if(this.detached) {
|
||||
return;
|
||||
}
|
||||
//return;
|
||||
|
||||
this.detached = true;
|
||||
@ -263,17 +254,17 @@ export default class ProgressivePreloader {
|
||||
/* if(!this.detached) return;
|
||||
this.detached = true; */
|
||||
|
||||
fastRaf(() => {
|
||||
// fastRaf(() => {
|
||||
//console.log('[PP]: detach after rAF', this.detached, performance.now());
|
||||
|
||||
if(!this.detached || !this.preloader.parentElement) {
|
||||
return;
|
||||
}
|
||||
// if(!this.detached || !this.preloader.parentElement) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
SetTransition(this.preloader, 'is-visible', false, TRANSITION_TIME, () => {
|
||||
this.preloader.remove();
|
||||
});
|
||||
});
|
||||
}, 1);
|
||||
// });
|
||||
//})/* , 5e3) */;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import { formatFullSentTime } from '../helpers/date';
|
||||
import mediaSizes, { ScreenSize } from '../helpers/mediaSizes';
|
||||
import { formatBytes } from '../helpers/number';
|
||||
import { IS_SAFARI } from '../environment/userAgent';
|
||||
import { PhotoSize, StickerSet } from '../layer';
|
||||
import { Message, PhotoSize, StickerSet } from '../layer';
|
||||
import appDocsManager, { MyDocument } from "../lib/appManagers/appDocsManager";
|
||||
import appMessagesManager from '../lib/appManagers/appMessagesManager';
|
||||
import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager';
|
||||
@ -31,7 +31,7 @@ import RichTextProcessor from '../lib/richtextprocessor';
|
||||
import appImManager from '../lib/appManagers/appImManager';
|
||||
import { SearchSuperContext } from './appSearchSuper.';
|
||||
import rootScope from '../lib/rootScope';
|
||||
import { onVideoLoad } from '../helpers/files';
|
||||
import { onMediaLoad } from '../helpers/files';
|
||||
import { animateSingle } from '../helpers/animation';
|
||||
import renderImageFromUrl from '../helpers/dom/renderImageFromUrl';
|
||||
import sequentialDom from '../helpers/sequentialDom';
|
||||
@ -77,7 +77,7 @@ mediaSizes.addEventListener('changeScreen', (from, to) => {
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group, onlyPreview, withoutPreloader, loadPromises, noPlayButton, noAutoDownload, size, searchContext}: {
|
||||
doc: MyDocument,
|
||||
container?: HTMLElement,
|
||||
message?: any,
|
||||
message?: Message.message,
|
||||
boxWidth?: number,
|
||||
boxHeight?: number,
|
||||
withTail?: boolean,
|
||||
@ -172,6 +172,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
|
||||
const divRound = document.createElement('div');
|
||||
divRound.classList.add('media-round', 'z-depth-1');
|
||||
divRound.dataset.mid = '' + message.mid;
|
||||
divRound.dataset.peerId = '' + message.peerId;
|
||||
|
||||
const size = mediaSizes.active.round;
|
||||
const halfSize = size.width / 2;
|
||||
@ -190,6 +192,11 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
|
||||
spanTime.classList.add('tgico');
|
||||
|
||||
const isUnread = message.pFlags.media_unread;
|
||||
if(isUnread) {
|
||||
divRound.classList.add('is-unread');
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = canvas.height = doc.w/* * window.devicePixelRatio */;
|
||||
|
||||
@ -239,6 +246,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
video.classList.add('hide');
|
||||
divRound.classList.remove('is-paused');
|
||||
animateSingle(onFrame, canvas);
|
||||
|
||||
if(preloader && preloader.preloader && preloader.preloader.classList.contains('manual')) {
|
||||
preloader.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
const onPaused = () => {
|
||||
@ -352,10 +363,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
|
||||
const cacheContext = appDownloadManager.getCacheContext(doc);
|
||||
|
||||
const isUpload = !!message?.media?.preloader;
|
||||
const isUpload = !!(message?.media as any)?.preloader;
|
||||
let preloader: ProgressivePreloader;
|
||||
if(isUpload) { // means upload
|
||||
preloader = message.media.preloader as ProgressivePreloader;
|
||||
preloader = (message.media as any).preloader as ProgressivePreloader;
|
||||
preloader.attach(container, false);
|
||||
noAutoDownload = undefined;
|
||||
} else if(!cacheContext.downloaded && !doc.supportsStreaming) {
|
||||
@ -369,6 +380,44 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
});
|
||||
}
|
||||
|
||||
const renderDeferred = deferredPromise<void>();
|
||||
video.addEventListener('error', (e) => {
|
||||
if(video.error.code !== 4) {
|
||||
console.error("Error " + video.error.code + "; details: " + video.error.message);
|
||||
}
|
||||
|
||||
if(preloader && !isUpload) {
|
||||
preloader.detach();
|
||||
}
|
||||
|
||||
if(!renderDeferred.isFulfilled) {
|
||||
renderDeferred.resolve();
|
||||
}
|
||||
}, {once: true});
|
||||
|
||||
onMediaLoad(video).then(() => {
|
||||
if(group) {
|
||||
animationIntersector.addAnimation(video, group);
|
||||
}
|
||||
|
||||
if(preloader && !isUpload) {
|
||||
preloader.detach();
|
||||
}
|
||||
|
||||
renderDeferred.resolve();
|
||||
});
|
||||
|
||||
if(doc.type === 'video') {
|
||||
video.addEventListener('timeupdate', () => {
|
||||
spanTime.innerText = (video.duration - video.currentTime + '').toHHMMSS(false);
|
||||
});
|
||||
}
|
||||
|
||||
video.muted = true;
|
||||
video.loop = true;
|
||||
//video.play();
|
||||
video.autoplay = true;
|
||||
|
||||
let loadPhotoThumbFunc = noAutoDownload && photoRes?.preloader?.loadFunc;
|
||||
const load = () => {
|
||||
if(preloader && noAutoDownload && !withoutPreloader) {
|
||||
@ -393,20 +442,6 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
}
|
||||
}
|
||||
|
||||
video.addEventListener('error', (e) => {
|
||||
if(video.error.code !== 4) {
|
||||
console.error("Error " + video.error.code + "; details: " + video.error.message);
|
||||
}
|
||||
|
||||
if(preloader && !isUpload) {
|
||||
preloader.detach();
|
||||
}
|
||||
|
||||
if(!deferred.isFulfilled) {
|
||||
deferred.resolve();
|
||||
}
|
||||
}, {once: true});
|
||||
|
||||
if(!noAutoDownload && loadPhotoThumbFunc) {
|
||||
loadPhotoThumbFunc();
|
||||
loadPhotoThumbFunc = null;
|
||||
@ -414,10 +449,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
|
||||
noAutoDownload = undefined;
|
||||
|
||||
const deferred = deferredPromise<void>();
|
||||
loadPromise.then(() => {
|
||||
if(middleware && !middleware()) {
|
||||
deferred.resolve();
|
||||
renderDeferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -425,33 +459,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
appMediaPlaybackController.resolveWaitingForLoadMedia(message.peerId, message.mid);
|
||||
}
|
||||
|
||||
onVideoLoad(video).then(() => {
|
||||
if(group) {
|
||||
animationIntersector.addAnimation(video, group);
|
||||
}
|
||||
|
||||
if(preloader && !isUpload) {
|
||||
preloader.detach();
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
if(doc.type === 'video') {
|
||||
video.addEventListener('timeupdate', () => {
|
||||
spanTime.innerText = (video.duration - video.currentTime + '').toHHMMSS(false);
|
||||
});
|
||||
}
|
||||
|
||||
video.muted = true;
|
||||
video.loop = true;
|
||||
//video.play();
|
||||
video.autoplay = true;
|
||||
|
||||
renderImageFromUrl(video, cacheContext.url);
|
||||
}, () => {});
|
||||
|
||||
return {download: loadPromise, render: deferred};
|
||||
return {download: loadPromise, render: renderDeferred};
|
||||
};
|
||||
|
||||
if(preloader && !isUpload) {
|
||||
|
@ -69,14 +69,14 @@ export async function createPosterForVideo(url: string) {
|
||||
]);
|
||||
}
|
||||
|
||||
export function onVideoLoad(video: HTMLVideoElement) {
|
||||
export function onMediaLoad(media: HTMLMediaElement, readyState = media.HAVE_METADATA, useCanplayOnIos?: boolean) {
|
||||
return new Promise<void>((resolve) => {
|
||||
if(video.readyState >= video.HAVE_METADATA) {
|
||||
if(media.readyState >= readyState) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
video.addEventListener(IS_APPLE_MOBILE ? 'loadeddata' : 'canplay', () => resolve(), {once: true});
|
||||
media.addEventListener(IS_APPLE_MOBILE && !useCanplayOnIos ? 'loadeddata' : 'canplay', () => resolve(), {once: true});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7,12 +7,7 @@
|
||||
// * Jolly Cobra's schedulers
|
||||
import { NoneToVoidFunction } from "../types";
|
||||
|
||||
//type Scheduler = typeof requestAnimationFrame | typeof onTickEnd | typeof runNow;
|
||||
|
||||
/* export function throttleWithRaf<F extends AnyToVoidFunction>(fn: F) {
|
||||
return throttleWith(fastRaf, fn);
|
||||
}
|
||||
|
||||
/*
|
||||
export function throttleWithTickEnd<F extends AnyToVoidFunction>(fn: F) {
|
||||
return throttleWith(onTickEnd, fn);
|
||||
}
|
||||
@ -21,25 +16,6 @@ export function throttleWithNow<F extends AnyToVoidFunction>(fn: F) {
|
||||
return throttleWith(runNow, fn);
|
||||
}
|
||||
|
||||
export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) {
|
||||
let waiting = false;
|
||||
let args: Parameters<F>;
|
||||
|
||||
return (..._args: Parameters<F>) => {
|
||||
args = _args;
|
||||
|
||||
if (!waiting) {
|
||||
waiting = true;
|
||||
|
||||
schedulerFn(() => {
|
||||
waiting = false;
|
||||
// @ts-ignore
|
||||
fn(...args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function onTickEnd(cb: NoneToVoidFunction) {
|
||||
Promise.resolve().then(cb);
|
||||
}
|
||||
|
22
src/helpers/schedulers/throttleWith.ts
Normal file
22
src/helpers/schedulers/throttleWith.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// * Jolly Cobra's schedulers
|
||||
|
||||
import { AnyToVoidFunction } from "../../types";
|
||||
|
||||
export default function throttleWith<F extends AnyToVoidFunction>(schedulerFn: AnyToVoidFunction, fn: F) {
|
||||
let waiting = false;
|
||||
let args: Parameters<F>;
|
||||
|
||||
return (..._args: Parameters<F>) => {
|
||||
args = _args;
|
||||
|
||||
if (!waiting) {
|
||||
waiting = true;
|
||||
|
||||
schedulerFn(() => {
|
||||
waiting = false;
|
||||
// @ts-ignore
|
||||
fn(...args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
9
src/helpers/schedulers/throttleWithRaf.ts
Normal file
9
src/helpers/schedulers/throttleWithRaf.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// * Jolly Cobra's schedulers
|
||||
|
||||
import { AnyToVoidFunction } from "../../types";
|
||||
import { fastRaf } from "../schedulers";
|
||||
import throttleWith from "./throttleWith";
|
||||
|
||||
export default function throttleWithRaf<F extends AnyToVoidFunction>(fn: F) {
|
||||
return throttleWith(fastRaf, fn);
|
||||
}
|
@ -369,6 +369,10 @@ export class AppDocsManager {
|
||||
});
|
||||
}
|
||||
|
||||
download.then(() => {
|
||||
rootScope.dispatchEvent('document_downloaded', doc);
|
||||
});
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import appMediaPlaybackController from "../components/appMediaPlaybackController
|
||||
import { IS_APPLE_MOBILE } from "../environment/userAgent";
|
||||
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
|
||||
import RangeSelector from "../components/rangeSelector";
|
||||
import { onVideoLoad } from "../helpers/files";
|
||||
import { onMediaLoad } from "../helpers/files";
|
||||
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
||||
import ListenerSetter from "../helpers/listenerSetter";
|
||||
import ButtonMenu from "../components/buttonMenu";
|
||||
@ -427,7 +427,7 @@ export default class VideoPlayer extends EventListenerBase<{
|
||||
if(video.duration || initDuration) {
|
||||
timeDuration.innerHTML = String(Math.round(video.duration || initDuration)).toHHMMSS();
|
||||
} else {
|
||||
onVideoLoad(video).then(() => {
|
||||
onMediaLoad(video).then(() => {
|
||||
timeDuration.innerHTML = String(Math.round(video.duration)).toHHMMSS();
|
||||
});
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ export type BroadcastEvents = {
|
||||
|
||||
'download_start': string,
|
||||
'download_progress': any,
|
||||
'document_downloaded': MyDocument,
|
||||
|
||||
'context_menu_toggle': boolean
|
||||
};
|
||||
|
@ -402,7 +402,7 @@
|
||||
}
|
||||
|
||||
&.active,
|
||||
.audio.is-unread:not(.is-out) & {
|
||||
.audio.is-unread:not(.is-out) .audio-toggle:not(.playing) + & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -1244,15 +1244,30 @@ middle-ellipsis-element {
|
||||
.video-time {
|
||||
padding: 0 .375rem;
|
||||
background-color: var(--message-highlightning-color) !important;
|
||||
|
||||
&:before, &:after {
|
||||
margin-left: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-unread .video-time {
|
||||
&:before {
|
||||
order: 1;
|
||||
width: .5rem;
|
||||
height: .5rem;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
content: " ";
|
||||
}
|
||||
}
|
||||
|
||||
&.is-paused .video-time {
|
||||
&:after {
|
||||
content: $tgico-nosound;
|
||||
padding-left: .25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.125rem;
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user