Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

319 lines
10 KiB

3 years ago
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import rootScope from "../lib/rootScope";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
import { isSafari } from "../helpers/userAgent";
import { MOUNT_CLASS_TO } from "../config/debug";
import appDownloadManager from "../lib/appManagers/appDownloadManager";
import simulateEvent from "../helpers/dom/dispatchEvent";
import type { SearchSuperContext } from "./appSearchSuper.";
import { copy, deepEqual } from "../helpers/object";
3 years ago
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда
// TODO: Safari: проверить стрим, включить его и сразу попробовать включить видео или другую песню
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
// TODO: Safari: пофиксить момент, когда заканчивается песня и пытаешься включить её заново - прогресс сразу в конце
type MediaItem = {mid: number, peerId: number};
3 years ago
type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;
const SHOULD_USE_SAFARI_FIX = (() => {
try {
return isSafari && +navigator.userAgent.match(/ Version\/(\d+)/)[1] < 14;
} catch(err) {
return false;
}
})();
3 years ago
class AppMediaPlaybackController {
private container: HTMLElement;
private media: {
[peerId: string]: {
[mid: string]: HTMLMediaElement
}
} = {};
public playingMedia: HTMLMediaElement;
3 years ago
private waitingMediaForLoad: {
[peerId: string]: {
[mid: string]: CancellablePromise<void>
}
} = {};
public willBePlayedMedia: HTMLMediaElement;
public searchContext: SearchSuperContext;
3 years ago
private currentPeerId: number;
private prevMid: number;
private nextMid: number;
private prev: MediaItem[] = [];
private next: MediaItem[] = [];
3 years ago
constructor() {
this.container = document.createElement('div');
//this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
this.container.style.cssText = 'display: none;';
document.body.append(this.container);
}
public addMedia(peerId: number, doc: MyDocument, mid: number, autoload = true): HTMLMediaElement {
const storage = this.media[peerId] ?? (this.media[peerId] = {});
if(storage[mid]) return storage[mid];
const media = document.createElement(doc.type === 'round' ? 'video' : 'audio');
//const source = document.createElement('source');
//source.type = doc.type === 'voice' && !opusDecodeController.isPlaySupported() ? 'audio/wav' : doc.mime_type;
if(doc.type === 'round') {
media.setAttribute('playsinline', 'true');
//media.muted = true;
}
media.dataset.peerId = '' + peerId;
3 years ago
media.dataset.mid = '' + mid;
media.dataset.type = doc.type;
//media.autoplay = true;
media.volume = 1;
//media.append(source);
this.container.append(media);
media.addEventListener('play', this.onPlay);
3 years ago
media.addEventListener('pause', this.onPause);
media.addEventListener('ended', this.onEnded);
/* const onError = (e: Event) => {
3 years ago
//console.log('appMediaPlaybackController: video onError', e);
if(this.nextMid === mid) {
this.loadSiblingsMedia(peerId, doc.type as MediaType, mid).then(() => {
if(this.nextMid && storage[this.nextMid]) {
storage[this.nextMid].play();
}
});
}
};
media.addEventListener('error', onError); */
3 years ago
const deferred = deferredPromise<void>();
if(autoload) {
deferred.resolve();
} else {
const waitingStorage = this.waitingMediaForLoad[peerId] ?? (this.waitingMediaForLoad[peerId] = {});
waitingStorage[mid] = deferred;
}
deferred.then(() => {
//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) {
3 years ago
this.handleSafariStreamable(media);
}
// setTimeout(() => {
3 years ago
const cacheContext = appDownloadManager.getCacheContext(doc);
media.src = cacheContext.url;
// }, doc.supportsStreaming ? 500e3 : 0);
3 years ago
});
}/* , onError */);
3 years ago
return storage[mid] = media;
}
// safari подгрузит последний чанк и песня включится,
// при этом этот чанк нельзя руками отдать из SW, потому что браузер тогда теряется
private handleSafariStreamable(media: HTMLMediaElement) {
media.addEventListener('play', () => {
/* if(media.readyState === 4) { // https://developer.mozilla.org/ru/docs/Web/API/XMLHttpRequest/readyState
return;
} */
//media.volume = 0;
const currentTime = media.currentTime;
//this.setSafariBuffering(media, true);
media.addEventListener('progress', () => {
media.currentTime = media.duration - 1;
media.addEventListener('progress', () => {
media.currentTime = currentTime;
//media.volume = 1;
//this.setSafariBuffering(media, false);
if(!media.paused) {
media.play()/* .catch(() => {}) */;
}
}, {once: true});
}, {once: true});
}/* , {once: true} */);
}
public resolveWaitingForLoadMedia(peerId: number, mid: number) {
const storage = this.waitingMediaForLoad[peerId];
if(!storage) {
return;
}
3 years ago
const promise = storage[mid];
if(promise) {
promise.resolve();
delete storage[mid];
}
}
/**
* Only for audio
*/
public isSafariBuffering(media: HTMLMediaElement) {
/// @ts-ignore
return !!media.safariBuffering;
}
private setSafariBuffering(media: HTMLMediaElement, value: boolean) {
// @ts-ignore
media.safariBuffering = value;
}
onPlay = (e?: Event) => {
const media = e.target as HTMLMediaElement;
const peerId = +media.dataset.peerId;
const mid = +media.dataset.mid;
this.currentPeerId = peerId;
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
const previousMedia = this.playingMedia;
if(previousMedia !== media) {
if(previousMedia) {
if(!previousMedia.paused) {
previousMedia.pause();
}
// reset media
previousMedia.currentTime = 0;
simulateEvent(previousMedia, 'ended');
}
this.playingMedia = media;
this.loadSiblingsMedia(peerId, mid);
}
// audio_pause не успеет сработать без таймаута
setTimeout(() => {
const message = appMessagesManager.getMessageByPeer(peerId, mid);
rootScope.dispatchEvent('audio_play', {peerId, doc: message.media.document, mid});
}, 0);
};
3 years ago
onPause = (e?: Event) => {
/* const target = e.target as HTMLMediaElement;
if(!isInDOM(target)) {
this.container.append(target);
target.play();
return;
} */
rootScope.dispatchEvent('audio_pause');
};
onEnded = (e?: Event) => {
if(!e.isTrusted) {
return;
}
3 years ago
this.onPause(e);
//console.log('on media end');
if(this.nextMid) {
const media = this.media[this.currentPeerId][this.nextMid];
/* if(isSafari) {
media.autoplay = true;
} */
this.resolveWaitingForLoadMedia(this.currentPeerId, this.nextMid);
setTimeout(() => {
media.play()//.catch(() => {});
}, 0);
}
};
private loadSiblingsMedia(offsetPeerId: number, offsetMid: number) {
const {playingMedia, searchContext} = this;
if(!searchContext) {
return;
}
3 years ago
return appMessagesManager.getSearch({
...searchContext,
maxId: offsetMid,
3 years ago
limit: 3,
backLimit: 2,
3 years ago
}).then(value => {
if(this.playingMedia !== playingMedia || this.searchContext !== searchContext) {
3 years ago
return;
}
const idx = Math.max(0, value.history.findIndex(message => message.peerId === offsetPeerId && message.mid === offsetMid));
const prev = value.history.slice(Math.max(0, idx));
const next = value.history.slice(0, idx);
3 years ago
[this.prevMid, this.nextMid].filter(Boolean).forEach(mid => {
const peerId = searchContext.peerId;
3 years ago
const message = appMessagesManager.getMessageByPeer(peerId, mid);
this.addMedia(peerId, message.media.document, mid, false);
});
//console.log('loadSiblingsAudio', audio, type, mid, value, this.prevMid, this.nextMid);
});
}
public toggle() {
if(!this.playingMedia) return;
if(this.playingMedia.paused) {
this.playingMedia.play();
} else {
this.playingMedia.pause();
}
}
public pause() {
if(!this.playingMedia || this.playingMedia.paused) return;
this.playingMedia.pause();
}
public willBePlayed(media: HTMLMediaElement) {
this.willBePlayedMedia = media;
}
public setSearchContext(context: SearchSuperContext) {
if(deepEqual(this.searchContext, context)) {
return;
}
this.searchContext = copy(context); // {_: type === 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'}
this.prev.length = 0;
this.next.length = 0;
}
3 years ago
}
const appMediaPlaybackController = new AppMediaPlaybackController();
MOUNT_CLASS_TO.appMediaPlaybackController = appMediaPlaybackController;
export default appMediaPlaybackController;