WebP new converter
This commit is contained in:
parent
617acdbb13
commit
c33748eb5c
23
package-lock.json
generated
23
package-lock.json
generated
@ -3171,6 +3171,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.4.tgz",
|
||||
"integrity": "sha512-k3NqigXWRzQZVBDS5D1U70A5E8Qk4Kh+Ha/x4M8Bt9pF0X05eggfnC9+63Usc9Q928hRUIpIhTQaXsZwZBl4Ew=="
|
||||
},
|
||||
"@types/pako": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.1.tgz",
|
||||
"integrity": "sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/puppeteer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz",
|
||||
@ -6444,6 +6450,17 @@
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-png": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.2.tgz",
|
||||
"integrity": "sha512-75j60BAZP5R3eTHslW5o8apfuAtxRONfY3gGt3yQlsm46Xz+uRCjguBFFr5SMNGPJ7MjYFBsE1dlwPLnTA4t6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/pako": "^1.0.1",
|
||||
"iobuffer": "^5.0.2",
|
||||
"pako": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"fastdom": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.9.tgz",
|
||||
@ -8082,6 +8099,12 @@
|
||||
"integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
|
||||
"dev": true
|
||||
},
|
||||
"iobuffer": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.0.2.tgz",
|
||||
"integrity": "sha512-FwwzAUZkapfHq76amLGSaEigB2nRSZR+ADc+zRcIRm2MsofDJAiIEPGbw+is/lI+kW32fM5jezmJ8dVQnd1UbA==",
|
||||
"dev": true
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
|
@ -38,6 +38,7 @@
|
||||
"compression-webpack-plugin": "^3.1.0",
|
||||
"css-loader": "^3.5.3",
|
||||
"express": "^4.17.1",
|
||||
"fast-png": "^5.0.2",
|
||||
"fastdom": "^1.0.9",
|
||||
"file-loader": "^4.3.0",
|
||||
"handlebars": "^4.7.6",
|
||||
|
@ -15,6 +15,6 @@ app.get('/', (req, res) => {
|
||||
https.createServer({
|
||||
key: fs.readFileSync(__dirname + '/certs/server-key.pem'),
|
||||
cert: fs.readFileSync(__dirname + '/certs/server-cert.pem')
|
||||
}, app).listen(9001, () => {
|
||||
}, app).listen(443/* 9001 */, () => {
|
||||
console.log('Listening...');
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import { logger, LogLevels } from "../lib/logger";
|
||||
import smoothscroll from '../lib/smoothscroll';
|
||||
import smoothscroll from '../vendor/smoothscroll';
|
||||
import { touchSupport, isSafari } from "../lib/config";
|
||||
//import { isInDOM } from "../lib/utils";
|
||||
(window as any).__forceSmoothScrollPolyfill__ = true;
|
||||
|
@ -9,17 +9,16 @@ import ProgressivePreloaderNew from './preloader_new';
|
||||
import LazyLoadQueue from './lazyLoadQueue';
|
||||
import VideoPlayer from '../lib/mediaPlayer';
|
||||
import { RichTextProcessor } from '../lib/richtextprocessor';
|
||||
import { CancellablePromise } from '../lib/polyfill';
|
||||
import { renderImageFromUrl } from './misc';
|
||||
import appMessagesManager from '../lib/appManagers/appMessagesManager';
|
||||
import { Layouter, RectPart } from './groupedLayout';
|
||||
import PollElement from './poll';
|
||||
import appWebpManager from '../lib/appManagers/appWebpManager';
|
||||
import { mediaSizes } from '../lib/config';
|
||||
import { mediaSizes, isSafari } from '../lib/config';
|
||||
import { MTDocument, MTPhotoSize } from '../types';
|
||||
import animationIntersector from './animationIntersector';
|
||||
import AudioElement from './audio';
|
||||
import { Download } from '../lib/appManagers/appDownloadManager';
|
||||
import { webpWorkerController } from '../lib/webp/webpWorkerController';
|
||||
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: {
|
||||
doc: MTDocument,
|
||||
@ -420,12 +419,12 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
if(thumb.bytes) {
|
||||
let img = new Image();
|
||||
|
||||
if((appWebpManager.isSupported() || doc.stickerThumbConverted)/* && false */) {
|
||||
if((!isSafari || doc.stickerThumbConverted)/* && false */) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true));
|
||||
|
||||
div.append(img);
|
||||
} else {
|
||||
appWebpManager.convertToPng(thumb.bytes).then(bytes => {
|
||||
webpWorkerController.convert(doc.id, thumb.bytes).then(bytes => {
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
thumb.bytes = bytes;
|
||||
|
@ -1,28 +0,0 @@
|
||||
MTProto.apiManager.invokeApi('messages.getPeerDialogs', {
|
||||
peers: [
|
||||
{
|
||||
_: 'inputDialogPeer',
|
||||
peer: {_: 'inputPeerUser', user_id: 296814355, access_hash: '7461657386624868366'}
|
||||
}
|
||||
]
|
||||
}).then(dialogs => console.log(dialogs));
|
||||
|
||||
MTProto.apiManager.invokeApi('messages.getPinnedDialogs', {
|
||||
folder_id: 0
|
||||
}).then(dialogs => console.log(dialogs));
|
||||
|
||||
// read_outbox_max_id && read_inbox_max_id are 0!
|
||||
MTProto.apiManager.invokeApi('messages.getDialogs', {
|
||||
flags: 0 | 1,
|
||||
exclude_pinned: true,
|
||||
folder_id: 0,
|
||||
offset_date: 0,
|
||||
offset_id: 0,
|
||||
offset_peer: {_: 'inputPeerEmpty'},
|
||||
limit: 6,
|
||||
hash: Date.now() * 5
|
||||
}).then(dialogs => console.log(dialogs));
|
||||
|
||||
// [109, 188, 177, 157, 19, 7, 177, 17, 49, 155, 9, 0, 44, 155, 9, 0, 237, 154, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 157, 80, 175] - works
|
||||
// [109, 188, 177, 157, 19, 7, 177, 17, 49, 155, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 157, 80, 175] - pinned
|
||||
|
7
src/global.d.ts
vendored
Normal file
7
src/global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
declare module 'worker-loader!*' {
|
||||
class WebpackWorker extends Worker {
|
||||
constructor();
|
||||
}
|
||||
|
||||
export default WebpackWorker;
|
||||
}
|
15
src/helpers/userAgent.ts
Normal file
15
src/helpers/userAgent.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export const userAgent = navigator ? navigator.userAgent : null;
|
||||
export const isApple = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) != -1;
|
||||
export const isAndroid = navigator.userAgent.toLowerCase().indexOf('android') != -1;
|
||||
|
||||
/**
|
||||
* Returns true when run in WebKit derived browsers.
|
||||
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
|
||||
* transfer data between WebWorkers and the main thread.
|
||||
* https://github.com/mapbox/mapbox-gl-js/issues/8771
|
||||
*
|
||||
* This should be removed once the underlying Safari issue is fixed.
|
||||
*/
|
||||
const ctx = typeof(window) !== 'undefined' ? window : self;
|
||||
|
||||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
|
@ -1,103 +0,0 @@
|
||||
class AppWebpManager {
|
||||
private webpMachine: any = null;
|
||||
private loaded: Promise<void>;
|
||||
private busyPromise: Promise<Uint8Array | void>;
|
||||
private queue: {bytes: Uint8Array, callback: (res: Uint8Array) => void}[] = [];
|
||||
|
||||
private testPromise: Promise<boolean> = null;
|
||||
public webpSupport = false;
|
||||
|
||||
constructor() {
|
||||
this.testWebpSupport();
|
||||
}
|
||||
|
||||
private loadWebpHero() {
|
||||
if(this.loaded) return this.loaded;
|
||||
|
||||
this.loaded = new Promise(async(resolve, reject) => {
|
||||
let res = await this.testWebpSupport();
|
||||
|
||||
if(!res) {
|
||||
(window as any).webpLoaded = () => {
|
||||
//console.log('webpHero loaded');
|
||||
this.webpMachine = new (window as any).WebpMachine();
|
||||
resolve();
|
||||
};
|
||||
|
||||
let sc = document.createElement('script');
|
||||
sc.src = 'npm.webp-hero.chunk.js';
|
||||
sc.async = true;
|
||||
sc.onload = (window as any).webpLoaded;
|
||||
|
||||
document.body.appendChild(sc);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private convert(bytes: Uint8Array): AppWebpManager['busyPromise'] {
|
||||
return this.webpMachine.decode(bytes);
|
||||
}
|
||||
|
||||
private async processQueue() {
|
||||
if(this.busyPromise) return;
|
||||
|
||||
this.busyPromise = Promise.resolve();
|
||||
|
||||
let {bytes, callback} = this.queue.pop();
|
||||
|
||||
if(!this.loaded) {
|
||||
this.loadWebpHero();
|
||||
}
|
||||
|
||||
await this.loaded;
|
||||
|
||||
this.busyPromise = this.convert(bytes);
|
||||
let res = await this.busyPromise;
|
||||
|
||||
//console.log('converted webp', res);
|
||||
|
||||
callback(res as Uint8Array);
|
||||
|
||||
this.busyPromise = null;
|
||||
|
||||
if(this.queue.length) {
|
||||
this.processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
public testWebpSupport() {
|
||||
if(this.testPromise) return this.testPromise;
|
||||
|
||||
return this.testPromise = new Promise((resolve, reject) => {
|
||||
return resolve(this.webpSupport = true);
|
||||
let webP = new Image();
|
||||
webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' +
|
||||
'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';
|
||||
webP.onload = webP.onerror = () => {
|
||||
resolve(this.webpSupport = webP.height === 2/* && false */);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public isSupported() {
|
||||
return this.webpSupport;
|
||||
}
|
||||
|
||||
public convertToPng(bytes: Uint8Array) {
|
||||
//console.warn('convertToPng!');
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
// @ts-ignore
|
||||
this.queue.push({bytes, callback: resolve});
|
||||
this.processQueue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const appWebpManager = new AppWebpManager();
|
||||
/* // @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).appWebpManager = appWebpManager;
|
||||
} */
|
||||
export default appWebpManager;
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
import {blobConstruct} from './bin_utils';
|
||||
|
||||
class FileManager {
|
||||
export class FileManager {
|
||||
public blobSupported = true;
|
||||
|
||||
constructor() {
|
||||
@ -15,22 +15,6 @@ class FileManager {
|
||||
return this.blobSupported;
|
||||
}
|
||||
|
||||
public copy(fromFileEntry: any, toFileEntry: any) {
|
||||
return this.write(toFileEntry, fromFileEntry).then(() => {
|
||||
console.log('copy success');
|
||||
return toFileEntry;
|
||||
}, (error: any) => {
|
||||
console.error('copy error 1:', error);
|
||||
try {
|
||||
toFileEntry.truncate(0);
|
||||
} catch(e) {
|
||||
console.error('copy error', e);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
public write(fileWriter: ReturnType<FileManager['getFakeFileWriter']>, bytes: Uint8Array | Blob | {file: any}): Promise<void> {
|
||||
if('file' in bytes) {
|
||||
return bytes.file((file: any) => {
|
||||
|
@ -4,9 +4,9 @@ import cacheStorage from "../cacheStorage";
|
||||
import FileManager from "../filemanager";
|
||||
import apiManager from "./apiManager";
|
||||
import { deferredPromise, CancellablePromise } from "../polyfill";
|
||||
import appWebpManager from "../appManagers/appWebpManager";
|
||||
import { logger } from "../logger";
|
||||
import { InputFileLocation, FileLocation, UploadFile } from "../../types";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
|
||||
type Delayed = {
|
||||
offset: number,
|
||||
@ -21,7 +21,6 @@ export type DownloadOptions = {
|
||||
fileName?: string,
|
||||
mimeType?: string,
|
||||
limitPart?: number,
|
||||
stickerType?: number,
|
||||
processPart?: (bytes: Uint8Array, offset?: number, queue?: Delayed[]) => Promise<any>
|
||||
};
|
||||
|
||||
@ -42,6 +41,8 @@ export class ApiFileManager {
|
||||
} = {};
|
||||
public downloadActives: {[dcID: string]: number} = {};
|
||||
|
||||
public webpConvertPromises: {[fileName: string]: CancellablePromise<Uint8Array>} = {};
|
||||
|
||||
private log: ReturnType<typeof logger> = logger('AFM');
|
||||
|
||||
public downloadRequest(dcID: 'upload', cb: () => Promise<void>, activeDelta: number): Promise<void>;
|
||||
@ -151,15 +152,11 @@ export class ApiFileManager {
|
||||
let size = options.size ?? 0;
|
||||
let {dcID, location} = options;
|
||||
|
||||
let processSticker = false;
|
||||
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
if(size > 524288) {
|
||||
delete options.stickerType;
|
||||
} else {
|
||||
processSticker = true;
|
||||
let processWebp = false;
|
||||
if(options.mimeType == 'image/webp' && isSafari) {
|
||||
processWebp = true;
|
||||
options.mimeType = 'image/png';
|
||||
}
|
||||
}
|
||||
|
||||
// this.log('Dload file', dcID, location, size)
|
||||
const fileName = getFileNameByLocation(location);
|
||||
@ -172,7 +169,7 @@ export class ApiFileManager {
|
||||
if(options.processPart) {
|
||||
return cachedPromise.then((blob) => {
|
||||
return this.convertBlobToBytes(blob).then(bytes => {
|
||||
options.processPart(bytes)
|
||||
options.processPart(bytes);
|
||||
return blob;
|
||||
});
|
||||
});
|
||||
@ -204,13 +201,13 @@ export class ApiFileManager {
|
||||
|
||||
let canceled = false;
|
||||
let resolved = false;
|
||||
let cacheFileWriter: any;
|
||||
let cacheFileWriter: ReturnType<typeof FileManager['getFakeFileWriter']>;
|
||||
let errorHandler = (error: any) => {
|
||||
deferred.reject(error);
|
||||
errorHandler = () => {};
|
||||
|
||||
if(cacheFileWriter && (!error || error.type != 'DOWNLOAD_CANCELED')) {
|
||||
cacheFileWriter.truncate(0);
|
||||
cacheFileWriter.truncate();
|
||||
}
|
||||
};
|
||||
|
||||
@ -269,8 +266,22 @@ export class ApiFileManager {
|
||||
await options.processPart(bytes, offset, delayed);
|
||||
}
|
||||
|
||||
if(processSticker) {
|
||||
return appWebpManager.convertToPng(bytes);
|
||||
if(processWebp) {
|
||||
const convertPromise = deferredPromise<Uint8Array>();
|
||||
|
||||
(self as any as ServiceWorkerGlobalScope)
|
||||
.clients
|
||||
.matchAll({includeUncontrolled: false, type: 'window'})
|
||||
.then((listeners) => {
|
||||
if(!listeners.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners[0].postMessage({type: 'convertWebp', payload: {fileName, bytes}});
|
||||
});
|
||||
|
||||
return await (this.webpConvertPromises[fileName] = convertPromise);
|
||||
//return appWebpManager.convertToPng(bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
|
@ -9,6 +9,7 @@ import networkerFactory from "./networkerFactory";
|
||||
import apiFileManager, { DownloadOptions } from './apiFileManager';
|
||||
import { getFileNameByLocation } from '../bin_utils';
|
||||
import { logger, LogLevels } from '../logger';
|
||||
import { isSafari } from '../../helpers/userAgent';
|
||||
|
||||
const log = logger('SW'/* , LogLevels.error */);
|
||||
|
||||
@ -16,34 +17,11 @@ const ctx = self as any as ServiceWorkerGlobalScope;
|
||||
|
||||
//console.error('INCLUDE !!!', new Error().stack);
|
||||
|
||||
/**
|
||||
* Returns true when run in WebKit derived browsers.
|
||||
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
|
||||
* transfer data between WebWorkers and the main thread.
|
||||
* https://github.com/mapbox/mapbox-gl-js/issues/8771
|
||||
*
|
||||
* This should be removed once the underlying Safari issue is fixed.
|
||||
*
|
||||
* @private
|
||||
* @param scope {WindowOrWorkerGlobalScope} Since this function is used both on the main thread and WebWorker context,
|
||||
* let the calling scope pass in the global scope object.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
var _isSafari: boolean = null;
|
||||
function isSafari(scope: any) {
|
||||
if(_isSafari == null) {
|
||||
var userAgent = scope.navigator ? scope.navigator.userAgent : null;
|
||||
_isSafari = !!scope.safari ||
|
||||
!!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
|
||||
}
|
||||
return _isSafari;
|
||||
}
|
||||
|
||||
function isObject(object: any) {
|
||||
return typeof(object) === 'object' && object !== null;
|
||||
}
|
||||
|
||||
function fillTransfer(transfer: any, obj: any) {
|
||||
/* function fillTransfer(transfer: any, obj: any) {
|
||||
if(!obj) return;
|
||||
|
||||
if(obj instanceof ArrayBuffer) {
|
||||
@ -59,7 +37,7 @@ function fillTransfer(transfer: any, obj: any) {
|
||||
fillTransfer(transfer, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Respond to request
|
||||
@ -83,7 +61,7 @@ function respond(client: Client | ServiceWorker | MessagePort, ...args: any[]) {
|
||||
* Broadcast Notification
|
||||
*/
|
||||
function notify(...args: any[]) {
|
||||
ctx.clients.matchAll({ includeUncontrolled: false, type: 'window' }).then((listeners) => {
|
||||
ctx.clients.matchAll({includeUncontrolled: false, type: 'window'}).then((listeners) => {
|
||||
if(!listeners.length) {
|
||||
//console.trace('no listeners?', self, listeners);
|
||||
return;
|
||||
@ -108,6 +86,13 @@ ctx.addEventListener('message', async(e) => {
|
||||
if(e.data.useLs) {
|
||||
AppStorage.finishTask(e.data.taskID, e.data.args);
|
||||
return;
|
||||
} else if(e.data.type == 'convertWebp') {
|
||||
const {fileName, bytes} = e.data.payload;
|
||||
const deferred = apiFileManager.webpConvertPromises[fileName];
|
||||
if(deferred) {
|
||||
deferred.resolve(bytes);
|
||||
delete apiFileManager.webpConvertPromises[fileName];
|
||||
}
|
||||
}
|
||||
|
||||
switch(e.data.task) {
|
||||
@ -288,7 +273,7 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => {
|
||||
|
||||
if(info.mimeType) headers['Content-Type'] = info.mimeType;
|
||||
|
||||
if(isSafari(ctx)) {
|
||||
if(isSafari) {
|
||||
ab = ab.slice(offset - alignedOffset, end - alignedOffset + 1);
|
||||
headers['Content-Range'] = `bytes ${offset}-${offset + ab.byteLength - 1}/${info.size || '*'}`;
|
||||
headers['Content-Length'] = `${ab.byteLength}`;
|
||||
|
@ -2,8 +2,8 @@ import {isObject, $rootScope} from '../utils';
|
||||
import AppStorage from '../storage';
|
||||
import CryptoWorkerMethods from '../crypto/crypto_methods';
|
||||
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
|
||||
import { InputFileLocation, FileLocation } from '../../types';
|
||||
import { logger } from '../logger';
|
||||
import { webpWorkerController } from '../webp/webpWorkerController';
|
||||
|
||||
type Task = {
|
||||
taskID: number,
|
||||
@ -74,6 +74,8 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
}
|
||||
} else if(e.data.progress) {
|
||||
$rootScope.$broadcast('download_progress', e.data.progress);
|
||||
} else if(e.data.type == 'convertWebp') {
|
||||
webpWorkerController.postMessage(e.data);
|
||||
} else {
|
||||
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
|
||||
}
|
||||
@ -162,15 +164,6 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
return this.performTaskWorker('logOut');
|
||||
}
|
||||
|
||||
public downloadFile(dcID: number, location: InputFileLocation | FileLocation, size: number = 0, options: Partial<{
|
||||
mimeType: string,
|
||||
toFileEntry: any,
|
||||
limitPart: number,
|
||||
stickerType: number
|
||||
}> = {}): Promise<Blob> {
|
||||
return this.performTaskWorker('downloadFile', dcID, location, size, options);
|
||||
}
|
||||
|
||||
public cancelDownload(fileName: string) {
|
||||
return this.performTaskWorker('cancelDownload', fileName);
|
||||
}
|
||||
|
@ -136,15 +136,15 @@ class ConfigStorage {
|
||||
}
|
||||
}
|
||||
|
||||
const configStorage = new ConfigStorage();
|
||||
|
||||
class AppStorage {
|
||||
private isWebWorker: boolean;
|
||||
private isWorker: boolean;
|
||||
private taskID = 0;
|
||||
private tasks: {[taskID: number]: (result: any) => void} = {};
|
||||
//private log = (...args: any[]) => console.log('[SW LS]', ...args);
|
||||
private log = (...args: any[]) => {};
|
||||
|
||||
private configStorage: ConfigStorage;
|
||||
|
||||
constructor() {
|
||||
if(Modes.test) {
|
||||
this.setPrefix('t_');
|
||||
@ -152,15 +152,23 @@ class AppStorage {
|
||||
|
||||
// @ts-ignore
|
||||
//this.isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
||||
this.isWebWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
|
||||
this.isWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
|
||||
|
||||
if(!this.isWorker) {
|
||||
this.configStorage = new ConfigStorage();
|
||||
}
|
||||
}
|
||||
|
||||
public setPrefix(newPrefix: string) {
|
||||
configStorage.keyPrefix = newPrefix;
|
||||
if(this.configStorage) {
|
||||
this.configStorage.keyPrefix = newPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
public noPrefix() {
|
||||
configStorage.noPrefix = true;
|
||||
if(this.configStorage) {
|
||||
this.configStorage.noPrefix = true;
|
||||
}
|
||||
}
|
||||
|
||||
public finishTask(taskID: number, result: any) {
|
||||
@ -175,9 +183,9 @@ class AppStorage {
|
||||
delete this.tasks[taskID];
|
||||
}
|
||||
|
||||
private proxy<T>(methodName: string, ..._args: any[]) {
|
||||
private proxy<T>(methodName: 'set' | 'get' | 'remove' | 'clear', ..._args: any[]) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
if(this.isWebWorker) {
|
||||
if(this.isWorker) {
|
||||
const taskID = this.taskID++;
|
||||
|
||||
this.tasks[taskID] = resolve;
|
||||
@ -203,8 +211,7 @@ class AppStorage {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
configStorage[methodName].apply(configStorage, args);
|
||||
this.configStorage[methodName].apply(this.configStorage, args as any);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,55 +0,0 @@
|
||||
import {Webp} from "webp-hero/libwebp/dist/webp.js"
|
||||
|
||||
const relax = () => new Promise(resolve => requestAnimationFrame(resolve));
|
||||
|
||||
export class WebpMachineError extends Error {}
|
||||
|
||||
/**
|
||||
* Webp Machine
|
||||
* - decode and polyfill webp images
|
||||
* - can only decode images one-at-a-time (otherwise will throw busy error)
|
||||
*/
|
||||
export class WebpMachine {
|
||||
private readonly webp: Webp;
|
||||
private busy = false;
|
||||
|
||||
constructor() {
|
||||
this.webp = new Webp();
|
||||
this.webp.Module.doNotCaptureKeyboard = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode raw webp data into a png data url
|
||||
*/
|
||||
decode(webpData: Uint8Array): Promise<Uint8Array> {
|
||||
if(this.busy) throw new WebpMachineError("cannot decode when already busy");
|
||||
this.busy = true;
|
||||
|
||||
try {
|
||||
return relax().then(() => {
|
||||
const canvas = document.createElement("canvas");
|
||||
this.webp.setCanvas(canvas);
|
||||
this.webp.webpToSdl(webpData, webpData.length);
|
||||
this.busy = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob(blob => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
resolve(new Uint8Array(event.target.result as ArrayBuffer));
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}, 'image/png', 1);
|
||||
});
|
||||
});
|
||||
} catch(error) {
|
||||
this.busy = false;
|
||||
error.name = WebpMachineError.name;
|
||||
error.message = `failed to decode webp image: ${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).WebpMachine = WebpMachine;
|
42
src/lib/webp/webp.ts
Normal file
42
src/lib/webp/webp.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { WebPDecoder } from '../../vendor/libwebp-0.2.0';
|
||||
import { encode } from 'fast-png';
|
||||
|
||||
export function webp2png(data: Uint8Array) {
|
||||
const decoder = new WebPDecoder();
|
||||
const config: any = decoder.WebPDecoderConfig;
|
||||
const buffer = config.j || config.output;
|
||||
const bitstream = config.input;
|
||||
|
||||
decoder.WebPInitDecoderConfig(config);
|
||||
decoder.WebPGetFeatures(data, data.length, bitstream);
|
||||
|
||||
/** MODE_RGBA = 1 MODE_ARGB = 4, */
|
||||
buffer.J = 1;
|
||||
|
||||
let status;
|
||||
try {
|
||||
status = decoder.WebPDecode(data, data.length, config);
|
||||
} catch(e) {
|
||||
status = e;
|
||||
}
|
||||
|
||||
if(status === 0) {
|
||||
const rgbaData = buffer.Jb;
|
||||
const pngData = encode({
|
||||
data: rgbaData,
|
||||
width: buffer.width,
|
||||
height: buffer.height,
|
||||
channels: 4,
|
||||
depth: 8,
|
||||
});
|
||||
|
||||
return {status, bytes: pngData};
|
||||
}
|
||||
|
||||
return {status, bytes: data};
|
||||
}
|
||||
|
||||
export function webp2pngAsBlob(data: Uint8Array) {
|
||||
const {status, bytes} = webp2png(data);
|
||||
return new Blob([bytes], {type: status === 0 ? 'image/png' : 'image/webp'});
|
||||
}
|
50
src/lib/webp/webp.worker.ts
Normal file
50
src/lib/webp/webp.worker.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { webp2png } from './webp';
|
||||
import type { WebpConvertTask } from './webpWorkerController';
|
||||
|
||||
const ctx = self as any as DedicatedWorkerGlobalScope;
|
||||
const tasks: WebpConvertTask[] = [];
|
||||
let isProcessing = false;
|
||||
|
||||
function finishTask() {
|
||||
isProcessing = false;
|
||||
processTasks();
|
||||
}
|
||||
|
||||
function processTasks() {
|
||||
if(isProcessing) return;
|
||||
|
||||
const task = tasks.shift();
|
||||
if(!task) return;
|
||||
|
||||
isProcessing = true;
|
||||
|
||||
switch(task.type) {
|
||||
case 'convertWebp': {
|
||||
const {fileName, bytes} = task.payload;
|
||||
|
||||
ctx.postMessage({
|
||||
type: 'convertWebp',
|
||||
payload: {
|
||||
fileName,
|
||||
bytes: webp2png(bytes).bytes
|
||||
}
|
||||
});
|
||||
|
||||
finishTask();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
finishTask();
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleTask(task: WebpConvertTask) {
|
||||
tasks.push(task);
|
||||
processTasks();
|
||||
}
|
||||
|
||||
ctx.addEventListener('message', (event) => {
|
||||
scheduleTask(event.data);
|
||||
});
|
53
src/lib/webp/webpWorkerController.ts
Normal file
53
src/lib/webp/webpWorkerController.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import WebpWorker from 'worker-loader!./webp.worker';
|
||||
import { CancellablePromise, deferredPromise } from '../polyfill';
|
||||
|
||||
export type WebpConvertTask = {
|
||||
type: 'convertWebp',
|
||||
payload: {
|
||||
fileName: string,
|
||||
bytes: Uint8Array
|
||||
}
|
||||
};
|
||||
|
||||
export class WebpWorkerController {
|
||||
private worker: Worker;
|
||||
private convertPromises: {[fileName: string]: CancellablePromise<Uint8Array>} = {};
|
||||
|
||||
init() {
|
||||
this.worker = new WebpWorker();
|
||||
this.worker.addEventListener('message', (e) => {
|
||||
const payload = (e.data as WebpConvertTask).payload;
|
||||
|
||||
if(payload.fileName.indexOf('main-') === 0) {
|
||||
const promise = this.convertPromises[payload.fileName];
|
||||
if(promise) {
|
||||
promise.resolve(payload.bytes);
|
||||
delete this.convertPromises[payload.fileName];
|
||||
}
|
||||
} else {
|
||||
navigator.serviceWorker.controller.postMessage(e.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
postMessage(data: WebpConvertTask) {
|
||||
if(this.init) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
this.worker.postMessage(data);
|
||||
}
|
||||
|
||||
convert(fileName: string, bytes: Uint8Array) {
|
||||
const convertPromise = deferredPromise<Uint8Array>();
|
||||
|
||||
fileName = 'main-' + fileName;
|
||||
|
||||
this.postMessage({type: 'convertWebp', payload: {fileName, bytes}});
|
||||
|
||||
return this.convertPromises[fileName] = convertPromise;
|
||||
}
|
||||
}
|
||||
|
||||
export const webpWorkerController = new WebpWorkerController();
|
4082
src/vendor/libwebp-0.2.0.js
vendored
Normal file
4082
src/vendor/libwebp-0.2.0.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -65,10 +65,8 @@
|
||||
"node_modules",
|
||||
"public",
|
||||
"coverage",
|
||||
"./src/lib/ckin.js",
|
||||
"./src/lib/crypto/crypto.worker.js",
|
||||
"src/lib/config.ts",
|
||||
"./src/lib/StackBlur.js",
|
||||
"src/vendor/StackBlur.js",
|
||||
"./src/lib/*.js",
|
||||
"./src/*.js",
|
||||
"*.js",
|
||||
|
@ -51,29 +51,6 @@ module.exports = {
|
||||
}
|
||||
].filter(l => !!l)
|
||||
},
|
||||
/* {
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
'style-loader',
|
||||
// Translates CSS into CommonJS
|
||||
'css-loader',
|
||||
// Compiles Sass to CSS
|
||||
{
|
||||
loader: 'resolve-url-loader'
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
}, */
|
||||
{
|
||||
test: /\.ts?$/,
|
||||
use: [
|
||||
@ -82,21 +59,6 @@ module.exports = {
|
||||
],
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
/* {
|
||||
test: /\.(woff2?|ttf|otf|eot|svg|jpg)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
outputPath: 'assets/',
|
||||
publicPath: 'assets/',
|
||||
name: '[folder]/[name].[ext]'
|
||||
}
|
||||
}, */
|
||||
{
|
||||
test: /\.worker\.(js|ts)$/,
|
||||
use: { loader: 'worker-loader' }
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.hbs$/,
|
||||
use: [
|
||||
@ -105,28 +67,23 @@ module.exports = {
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: [ '.ts', '.js' ],
|
||||
},
|
||||
//entry: './src/index.ts',
|
||||
entry: {
|
||||
index: './src/index.ts',
|
||||
//mp4: './src/lib/MP4Source.ts',
|
||||
webp: './src/lib/webp.ts'/* ,
|
||||
lottie: './src/lib/lottie.ts' */
|
||||
extensions: ['.ts', '.js'],
|
||||
},
|
||||
|
||||
entry: './src/index.ts',
|
||||
/* entry: {
|
||||
index: './src/index.ts',
|
||||
'lottie-web': ['lottie-web']
|
||||
//lottieLoader: './src/lib/lottieLoader.ts'
|
||||
index: './src/index.ts'
|
||||
}, */
|
||||
//devtool: 'inline-source-map',
|
||||
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'public'),
|
||||
//filename: 'bundle.js',
|
||||
filename: "[name].bundle.js",
|
||||
chunkFilename: "[name].chunk.js"
|
||||
},
|
||||
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'public'),
|
||||
watchContentBase: true,
|
||||
@ -165,19 +122,19 @@ module.exports = {
|
||||
},
|
||||
sockHost: useLocal ? undefined : 'tweb.enko.club',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new ServiceWorkerWebpackPlugin({
|
||||
entry: path.join(__dirname, 'src/lib/mtproto/mtproto.service.ts'),
|
||||
filename: 'sw.js',
|
||||
excludes: ['**/*'],
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: `index.html`,
|
||||
//template: 'public/index_template.html',
|
||||
template: 'src/index.hbs',
|
||||
//inject: true,
|
||||
inject: false,
|
||||
//inject: 'head',
|
||||
inject: false, // true, 'head'
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
@ -191,7 +148,7 @@ module.exports = {
|
||||
minifyURLs: true
|
||||
},
|
||||
chunks: "all",
|
||||
excludeChunks: ['npm.webp-hero'/* , 'mp4' *//* , 'npm.lottie-web' */]
|
||||
excludeChunks: []
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
|
Loading…
x
Reference in New Issue
Block a user