Browse Source

Preloader fixes

Audio status fix
master
morethanwords 4 years ago
parent
commit
2b498a5b2c
  1. 6
      src/components/appAudio.ts
  2. 48
      src/components/audio.ts
  3. 10
      src/components/preloader.ts
  4. 11
      src/helpers/context.ts
  5. 10
      src/lib/appManagers/appDownloadManager.ts
  6. 3
      src/lib/appManagers/appMediaViewer.ts
  7. 7
      src/lib/filemanager.ts
  8. 16
      src/lib/mtproto/apiFileManager.ts
  9. 7
      src/lib/mtproto/mtproto.worker.ts
  10. 20
      src/scss/partials/_preloader.scss

6
src/components/appAudio.ts

@ -4,6 +4,8 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appDocsManager from "../lib/appManagers/appDocsManager"; import appDocsManager from "../lib/appManagers/appDocsManager";
import opusDecodeController from "../lib/opusDecodeController"; import opusDecodeController from "../lib/opusDecodeController";
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда
class AppAudio { class AppAudio {
private container: HTMLElement; private container: HTMLElement;
private audios: {[mid: string]: HTMLAudioElement} = {}; private audios: {[mid: string]: HTMLAudioElement} = {};
@ -133,6 +135,10 @@ class AppAudio {
public willBePlayed(audio: HTMLAudioElement) { public willBePlayed(audio: HTMLAudioElement) {
this.willBePlayedAudio = audio; this.willBePlayedAudio = audio;
} }
public audioExists(mid: number) {
return !!this.audios[mid];
}
} }
const appAudio = new AppAudio(); const appAudio = new AppAudio();

48
src/components/audio.ts

@ -390,35 +390,39 @@ export default class AudioElement extends HTMLElement {
this.addEventListener('click', onClick); this.addEventListener('click', onClick);
this.click(); this.click();
} else { } else {
const r = () => { if(appAudio.audioExists(mid)) { // чтобы показать прогресс, если аудио уже было скачано
onLoad(); onLoad();
} else {
const r = () => {
onLoad();
appAudio.willBePlayed(this.audio); // prepare for loading audio appAudio.willBePlayed(this.audio); // prepare for loading audio
if(!preloader) { if(!preloader) {
preloader = new ProgressivePreloader(null, false); preloader = new ProgressivePreloader(null, false);
} }
preloader.attach(downloadDiv); preloader.attach(downloadDiv);
this.append(downloadDiv); this.append(downloadDiv);
new Promise((resolve) => { new Promise((resolve) => {
if(this.audio.readyState >= 2) resolve(); if(this.audio.readyState >= 2) resolve();
else this.addAudioListener('canplay', resolve); else this.addAudioListener('canplay', resolve);
}).then(() => { }).then(() => {
downloadDiv.remove(); downloadDiv.remove();
//setTimeout(() => { //setTimeout(() => {
// release loaded audio // release loaded audio
if(appAudio.willBePlayedAudio == this.audio) { if(appAudio.willBePlayedAudio == this.audio) {
this.audio.play(); this.audio.play();
appAudio.willBePlayedAudio = null; appAudio.willBePlayedAudio = null;
} }
//}, 10e3); //}, 10e3);
}); });
}; };
this.addEventListener('click', r, {once: true}); this.addEventListener('click', r, {once: true});
}
} }
} else { } else {
this.preloader.attach(downloadDiv, false); this.preloader.attach(downloadDiv, false);

10
src/components/preloader.ts

@ -10,14 +10,18 @@ export default class ProgressivePreloader {
private promise: CancellablePromise<any> = null; private promise: CancellablePromise<any> = null;
constructor(elem?: Element, private cancelable = true) { constructor(elem?: Element, private cancelable = true, streamable = false) {
this.preloader = document.createElement('div'); this.preloader = document.createElement('div');
this.preloader.classList.add('preloader-container'); this.preloader.classList.add('preloader-container');
if(streamable) {
this.preloader.classList.add('preloader-streamable');
}
this.preloader.innerHTML = ` this.preloader.innerHTML = `
<div class="you-spin-me-round"> <div class="you-spin-me-round">
<svg xmlns="http://www.w3.org/2000/svg" class="preloader-circular" viewBox="25 25 50 50"> <svg xmlns="http://www.w3.org/2000/svg" class="preloader-circular" viewBox="25 25 50 50">
<circle class="preloader-path-new" cx="50" cy="50" r="23" fill="none" stroke-miterlimit="10"/> <circle class="preloader-path-new" cx="50" cy="50" r="${streamable ? 19 : 23}" fill="none" stroke-miterlimit="10"/>
</svg> </svg>
</div>`; </div>`;
@ -98,6 +102,8 @@ export default class ProgressivePreloader {
public detach() { public detach() {
this.detached = true; this.detached = true;
//return;
if(this.preloader.parentElement) { if(this.preloader.parentElement) {
/* setTimeout(() => */window.requestAnimationFrame(() => { /* setTimeout(() => */window.requestAnimationFrame(() => {
if(!this.detached) return; if(!this.detached) return;

11
src/helpers/context.ts

@ -4,7 +4,7 @@ export const isWorker = isWebWorker || isServiceWorker;
// в SW может быть сразу две переменных TRUE, поэтому проверяю по последней // в SW может быть сразу две переменных TRUE, поэтому проверяю по последней
const notifyServiceWorker = (...args: any[]) => { const notifyServiceWorker = (all: boolean, ...args: any[]) => {
(self as any as ServiceWorkerGlobalScope) (self as any as ServiceWorkerGlobalScope)
.clients .clients
.matchAll({ includeUncontrolled: false, type: 'window' }) .matchAll({ includeUncontrolled: false, type: 'window' })
@ -14,8 +14,10 @@ const notifyServiceWorker = (...args: any[]) => {
return; return;
} }
// @ts-ignore listeners.slice(all ? 0 : -1).forEach(listener => {
listeners[0].postMessage(...args); // @ts-ignore
listener.postMessage(...args);
});
}); });
}; };
@ -26,4 +28,5 @@ const notifyWorker = (...args: any[]) => {
const empty = () => {}; const empty = () => {};
export const notifySomeone = isServiceWorker ? notifyServiceWorker : (isWebWorker ? notifyWorker : empty); export const notifySomeone = isServiceWorker ? notifyServiceWorker.bind(null, false) : (isWebWorker ? notifyWorker : empty);
export const notifyAll = isServiceWorker ? notifyServiceWorker.bind(null, true) : (isWebWorker ? notifyWorker : empty);

10
src/lib/appManagers/appDownloadManager.ts

@ -58,10 +58,18 @@ export class AppDownloadManager {
//console.log('Will download file:', fileName, url); //console.log('Will download file:', fileName, url);
deferred.cancel = () => { 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 = () => {}; deferred.cancel = () => {};
}; };
//return this.downloads[fileName] = {promise, controller};
return this.downloads[fileName] = deferred; return this.downloads[fileName] = deferred;
} }

3
src/lib/appManagers/appMediaViewer.ts

@ -70,8 +70,7 @@ export class AppMediaViewer {
this.log = logger('AMV'); this.log = logger('AMV');
this.preloader = new ProgressivePreloader(); this.preloader = new ProgressivePreloader();
this.preloaderStreamable = new ProgressivePreloader(undefined, false); this.preloaderStreamable = new ProgressivePreloader(undefined, false, true);
this.preloaderStreamable.preloader.classList.add('preloader-streamable');
this.lazyLoadQueue = new LazyLoadQueue(undefined, true); this.lazyLoadQueue = new LazyLoadQueue(undefined, true);

7
src/lib/filemanager.ts

@ -34,7 +34,7 @@ export class FileManager {
} }
} }
public getFakeFileWriter(mimeType: string, saveFileCallback: (blob: Blob) => Promise<Blob>) { public getFakeFileWriter(mimeType: string, saveFileCallback?: (blob: Blob) => Promise<Blob>) {
const blobParts: Array<Uint8Array | string> = []; const blobParts: Array<Uint8Array | string> = [];
const fakeFileWriter = { const fakeFileWriter = {
write: async(part: Uint8Array | string) => { write: async(part: Uint8Array | string) => {
@ -47,9 +47,10 @@ export class FileManager {
truncate: () => { truncate: () => {
blobParts.length = 0; blobParts.length = 0;
}, },
finalize: () => { finalize: (saveToStorage = true) => {
const blob = blobConstruct(blobParts, mimeType); const blob = blobConstruct(blobParts, mimeType);
if(saveFileCallback) {
if(saveToStorage && saveFileCallback) {
saveFileCallback(blob); saveFileCallback(blob);
} }

16
src/lib/mtproto/apiFileManager.ts

@ -8,7 +8,7 @@ import { logger, LogLevels } from "../logger";
import { InputFileLocation, FileLocation, UploadFile } from "../../types"; import { InputFileLocation, FileLocation, UploadFile } from "../../types";
import { isSafari } from "../../helpers/userAgent"; import { isSafari } from "../../helpers/userAgent";
import cryptoWorker from "../crypto/cryptoworker"; import cryptoWorker from "../crypto/cryptoworker";
import { notifySomeone } from "../../helpers/context"; import { notifySomeone, notifyAll } from "../../helpers/context";
type Delayed = { type Delayed = {
offset: number, offset: number,
@ -26,6 +26,8 @@ export type DownloadOptions = {
processPart?: (bytes: Uint8Array, offset?: number, queue?: Delayed[]) => Promise<any> processPart?: (bytes: Uint8Array, offset?: number, queue?: Delayed[]) => Promise<any>
}; };
const MAX_FILE_SAVE_SIZE = 20e6;
export class ApiFileManager { export class ApiFileManager {
public cachedDownloadPromises: { public cachedDownloadPromises: {
[fileName: string]: CancellablePromise<Blob> [fileName: string]: CancellablePromise<Blob>
@ -334,14 +336,14 @@ export class ApiFileManager {
//done += limit; //done += limit;
done += result.bytes.byteLength; done += result.bytes.byteLength;
const processedResult = await processDownloaded(result.bytes, offset);
checkCancel();
//if(!isFinal) { //if(!isFinal) {
////this.log('deferred notify 2:', {done: offset + limit, total: size}, deferred); ////this.log('deferred notify 2:', {done: offset + limit, total: size}, deferred);
deferred.notify({done, offset, total: size}); deferred.notify({done, offset, total: size});
//} //}
const processedResult = await processDownloaded(result.bytes, offset);
checkCancel();
await writeFilePromise; await writeFilePromise;
checkCancel(); checkCancel();
@ -356,7 +358,7 @@ export class ApiFileManager {
if(options.processPart) { if(options.processPart) {
deferred.resolve(); deferred.resolve();
} else { } else {
deferred.resolve(fileWriter.finalize()); deferred.resolve(fileWriter.finalize(size < MAX_FILE_SAVE_SIZE));
} }
} }
} catch(err) { } 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; this.cachedDownloadPromises[fileName] = deferred;
return deferred; return deferred;

7
src/lib/mtproto/mtproto.worker.ts

@ -1,12 +1,12 @@
// just to include // just to include
import {secureRandom} from '../polyfill'; import {secureRandom, CancellablePromise} from '../polyfill';
secureRandom; secureRandom;
import apiManager from "./apiManager"; import apiManager from "./apiManager";
import AppStorage from '../storage'; import AppStorage from '../storage';
import cryptoWorker from "../crypto/cryptoworker"; import cryptoWorker from "../crypto/cryptoworker";
import networkerFactory from "./networkerFactory"; import networkerFactory from "./networkerFactory";
import apiFileManager from './apiFileManager'; import apiFileManager, { ApiFileManager } from './apiFileManager';
import { logger, LogLevels } from '../logger'; import { logger, LogLevels } from '../logger';
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service'; 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); let result = apiFileManager[task.task].apply(apiFileManager, task.args);
if(result instanceof Promise) { if(result instanceof Promise) {
/* (result as ReturnType<ApiFileManager['downloadFile']>).notify = (progress: {done: number, total: number, offset: number}) => {
notify({progress: {fileName, ...progress}});
}; */
result = await result; result = await result;
} }

20
src/scss/partials/_preloader.scss

@ -96,6 +96,11 @@
cursor: pointer !important; cursor: pointer !important;
} }
circle {
stroke-width: 2.5 !important;
animation: dashNewStreamable 1.5s ease-in-out infinite !important;
}
&:after { &:after {
content: ""; content: "";
position: absolute; position: absolute;
@ -145,3 +150,18 @@
stroke-dashoffset: -286%; 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%;
}
}
Loading…
Cancel
Save