diff --git a/src/components/appAudio.ts b/src/components/appAudio.ts index 8f278f29..27c1316d 100644 --- a/src/components/appAudio.ts +++ b/src/components/appAudio.ts @@ -4,6 +4,8 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appDocsManager from "../lib/appManagers/appDocsManager"; import opusDecodeController from "../lib/opusDecodeController"; +// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда + class AppAudio { private container: HTMLElement; private audios: {[mid: string]: HTMLAudioElement} = {}; @@ -133,6 +135,10 @@ class AppAudio { public willBePlayed(audio: HTMLAudioElement) { this.willBePlayedAudio = audio; } + + public audioExists(mid: number) { + return !!this.audios[mid]; + } } const appAudio = new AppAudio(); diff --git a/src/components/audio.ts b/src/components/audio.ts index 7e2b3b47..9daefbda 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -390,35 +390,39 @@ export default class AudioElement extends HTMLElement { this.addEventListener('click', onClick); this.click(); } else { - const r = () => { + if(appAudio.audioExists(mid)) { // чтобы показать прогресс, если аудио уже было скачано onLoad(); - - appAudio.willBePlayed(this.audio); // prepare for loading audio - - if(!preloader) { - preloader = new ProgressivePreloader(null, false); - } + } else { + const r = () => { + onLoad(); - preloader.attach(downloadDiv); - this.append(downloadDiv); + appAudio.willBePlayed(this.audio); // prepare for loading audio - new Promise((resolve) => { - if(this.audio.readyState >= 2) resolve(); - else this.addAudioListener('canplay', resolve); - }).then(() => { - downloadDiv.remove(); - - //setTimeout(() => { - // release loaded audio - if(appAudio.willBePlayedAudio == this.audio) { - this.audio.play(); - appAudio.willBePlayedAudio = null; - } - //}, 10e3); - }); - }; - - this.addEventListener('click', r, {once: true}); + if(!preloader) { + preloader = new ProgressivePreloader(null, false); + } + + preloader.attach(downloadDiv); + this.append(downloadDiv); + + new Promise((resolve) => { + if(this.audio.readyState >= 2) resolve(); + else this.addAudioListener('canplay', resolve); + }).then(() => { + downloadDiv.remove(); + + //setTimeout(() => { + // release loaded audio + if(appAudio.willBePlayedAudio == this.audio) { + this.audio.play(); + appAudio.willBePlayedAudio = null; + } + //}, 10e3); + }); + }; + + this.addEventListener('click', r, {once: true}); + } } } else { this.preloader.attach(downloadDiv, false); diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 18b61659..5ef32988 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -10,14 +10,18 @@ export default class ProgressivePreloader { private promise: CancellablePromise = null; - constructor(elem?: Element, private cancelable = true) { + constructor(elem?: Element, private cancelable = true, streamable = false) { this.preloader = document.createElement('div'); this.preloader.classList.add('preloader-container'); + + if(streamable) { + this.preloader.classList.add('preloader-streamable'); + } this.preloader.innerHTML = `
- +
`; @@ -97,6 +101,8 @@ export default class ProgressivePreloader { public detach() { this.detached = true; + + //return; if(this.preloader.parentElement) { /* setTimeout(() => */window.requestAnimationFrame(() => { diff --git a/src/helpers/context.ts b/src/helpers/context.ts index 01c6ec6a..dbb20194 100644 --- a/src/helpers/context.ts +++ b/src/helpers/context.ts @@ -4,7 +4,7 @@ export const isWorker = isWebWorker || isServiceWorker; // в SW может быть сразу две переменных TRUE, поэтому проверяю по последней -const notifyServiceWorker = (...args: any[]) => { +const notifyServiceWorker = (all: boolean, ...args: any[]) => { (self as any as ServiceWorkerGlobalScope) .clients .matchAll({ includeUncontrolled: false, type: 'window' }) @@ -14,8 +14,10 @@ const notifyServiceWorker = (...args: any[]) => { return; } - // @ts-ignore - listeners[0].postMessage(...args); + listeners.slice(all ? 0 : -1).forEach(listener => { + // @ts-ignore + listener.postMessage(...args); + }); }); }; @@ -26,4 +28,5 @@ const notifyWorker = (...args: any[]) => { const empty = () => {}; -export const notifySomeone = isServiceWorker ? notifyServiceWorker : (isWebWorker ? notifyWorker : empty); \ No newline at end of file +export const notifySomeone = isServiceWorker ? notifyServiceWorker.bind(null, false) : (isWebWorker ? notifyWorker : empty); +export const notifyAll = isServiceWorker ? notifyServiceWorker.bind(null, true) : (isWebWorker ? notifyWorker : empty); \ No newline at end of file diff --git a/src/lib/appManagers/appDownloadManager.ts b/src/lib/appManagers/appDownloadManager.ts index 06983b58..c7e85873 100644 --- a/src/lib/appManagers/appDownloadManager.ts +++ b/src/lib/appManagers/appDownloadManager.ts @@ -58,10 +58,18 @@ export class AppDownloadManager { //console.log('Will download file:', fileName, url); deferred.cancel = () => { + const error = new Error('Download canceled'); + error.name = 'AbortError'; + + apiManager.cancelDownload(fileName); + delete this.downloads[fileName]; + delete this.progress[fileName]; + delete this.progressCallbacks[fileName]; + + deferred.reject(error); deferred.cancel = () => {}; }; - //return this.downloads[fileName] = {promise, controller}; return this.downloads[fileName] = deferred; } diff --git a/src/lib/appManagers/appMediaViewer.ts b/src/lib/appManagers/appMediaViewer.ts index 28e8a0b2..1c639fc3 100644 --- a/src/lib/appManagers/appMediaViewer.ts +++ b/src/lib/appManagers/appMediaViewer.ts @@ -70,8 +70,7 @@ export class AppMediaViewer { this.log = logger('AMV'); this.preloader = new ProgressivePreloader(); - this.preloaderStreamable = new ProgressivePreloader(undefined, false); - this.preloaderStreamable.preloader.classList.add('preloader-streamable'); + this.preloaderStreamable = new ProgressivePreloader(undefined, false, true); this.lazyLoadQueue = new LazyLoadQueue(undefined, true); diff --git a/src/lib/filemanager.ts b/src/lib/filemanager.ts index b56337ab..cb16ecef 100644 --- a/src/lib/filemanager.ts +++ b/src/lib/filemanager.ts @@ -34,7 +34,7 @@ export class FileManager { } } - public getFakeFileWriter(mimeType: string, saveFileCallback: (blob: Blob) => Promise) { + public getFakeFileWriter(mimeType: string, saveFileCallback?: (blob: Blob) => Promise) { const blobParts: Array = []; const fakeFileWriter = { write: async(part: Uint8Array | string) => { @@ -47,9 +47,10 @@ export class FileManager { truncate: () => { blobParts.length = 0; }, - finalize: () => { + finalize: (saveToStorage = true) => { const blob = blobConstruct(blobParts, mimeType); - if(saveFileCallback) { + + if(saveToStorage && saveFileCallback) { saveFileCallback(blob); } diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 1d7e512e..c69a3235 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -8,7 +8,7 @@ import { logger, LogLevels } from "../logger"; import { InputFileLocation, FileLocation, UploadFile } from "../../types"; import { isSafari } from "../../helpers/userAgent"; import cryptoWorker from "../crypto/cryptoworker"; -import { notifySomeone } from "../../helpers/context"; +import { notifySomeone, notifyAll } from "../../helpers/context"; type Delayed = { offset: number, @@ -26,6 +26,8 @@ export type DownloadOptions = { processPart?: (bytes: Uint8Array, offset?: number, queue?: Delayed[]) => Promise }; +const MAX_FILE_SAVE_SIZE = 20e6; + export class ApiFileManager { public cachedDownloadPromises: { [fileName: string]: CancellablePromise @@ -334,14 +336,14 @@ export class ApiFileManager { //done += limit; done += result.bytes.byteLength; - const processedResult = await processDownloaded(result.bytes, offset); - checkCancel(); - //if(!isFinal) { ////this.log('deferred notify 2:', {done: offset + limit, total: size}, deferred); deferred.notify({done, offset, total: size}); //} + const processedResult = await processDownloaded(result.bytes, offset); + checkCancel(); + await writeFilePromise; checkCancel(); @@ -356,7 +358,7 @@ export class ApiFileManager { if(options.processPart) { deferred.resolve(); } else { - deferred.resolve(fileWriter.finalize()); + deferred.resolve(fileWriter.finalize(size < MAX_FILE_SAVE_SIZE)); } } } catch(err) { @@ -384,6 +386,10 @@ export class ApiFileManager { } }; + deferred.notify = (progress: {done: number, total: number, offset: number}) => { + notifyAll({progress: {fileName, ...progress}}); + }; + this.cachedDownloadPromises[fileName] = deferred; return deferred; diff --git a/src/lib/mtproto/mtproto.worker.ts b/src/lib/mtproto/mtproto.worker.ts index 4318384b..2e3439a0 100644 --- a/src/lib/mtproto/mtproto.worker.ts +++ b/src/lib/mtproto/mtproto.worker.ts @@ -1,12 +1,12 @@ // just to include -import {secureRandom} from '../polyfill'; +import {secureRandom, CancellablePromise} from '../polyfill'; secureRandom; import apiManager from "./apiManager"; import AppStorage from '../storage'; import cryptoWorker from "../crypto/cryptoworker"; import networkerFactory from "./networkerFactory"; -import apiFileManager from './apiFileManager'; +import apiFileManager, { ApiFileManager } from './apiFileManager'; import { logger, LogLevels } from '../logger'; import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service'; @@ -112,6 +112,9 @@ ctx.addEventListener('message', async(e) => { let result = apiFileManager[task.task].apply(apiFileManager, task.args); if(result instanceof Promise) { + /* (result as ReturnType).notify = (progress: {done: number, total: number, offset: number}) => { + notify({progress: {fileName, ...progress}}); + }; */ result = await result; } diff --git a/src/scss/partials/_preloader.scss b/src/scss/partials/_preloader.scss index 32516398..addc7153 100644 --- a/src/scss/partials/_preloader.scss +++ b/src/scss/partials/_preloader.scss @@ -96,6 +96,11 @@ cursor: pointer !important; } + circle { + stroke-width: 2.5 !important; + animation: dashNewStreamable 1.5s ease-in-out infinite !important; + } + &:after { content: ""; position: absolute; @@ -144,4 +149,19 @@ stroke-dasharray: 89, 200; stroke-dashoffset: -286%; } +} + +@keyframes dashNewStreamable { + 0% { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -35px; + } + 100% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -237%; + } } \ No newline at end of file