Composer: Support all audio formats with cover images (#1793)

This commit is contained in:
Alexander Zinchuk 2022-04-01 20:43:24 +02:00
parent fb89133431
commit 5921e9770a
13 changed files with 348 additions and 60 deletions

257
package-lock.json generated
View File

@ -16,10 +16,12 @@
"emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#54443d1938ec1c157e74d2a95e9103dcb3f5c6dd",
"events": "^3.3.0",
"idb-keyval": "^6.1.0",
"music-metadata-browser": "^2.5.5",
"opus-recorder": "github:Ajaxy/opus-recorder",
"os-browserify": "^0.3.0",
"pako": "^2.0.4",
"path-browserify": "^1.0.1",
"process": "^0.11.10",
"qr-code-styling": "github:zubiden/qr-code-styling#10f7cf3",
"websocket": "^1.0.34"
},
@ -90,7 +92,7 @@
},
"engines": {
"node": "^16.13",
"npm": "^8.1"
"npm": "^8.5.2"
},
"optionalDependencies": {
"fsevents": "2.1.2"
@ -2816,6 +2818,11 @@
"node": ">=8"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
},
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"dev": true,
@ -4233,7 +4240,6 @@
},
"node_modules/base64-js": {
"version": "1.5.1",
"dev": true,
"funding": [
{
"type": "github",
@ -4406,7 +4412,6 @@
},
"node_modules/buffer": {
"version": "6.0.3",
"dev": true,
"funding": [
{
"type": "github",
@ -4938,7 +4943,6 @@
},
"node_modules/content-type": {
"version": "1.0.4",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@ -5431,7 +5435,6 @@
},
"node_modules/debug": {
"version": "4.3.3",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
@ -6963,6 +6966,22 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-type": {
"version": "16.5.3",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz",
"integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==",
"dependencies": {
"readable-web-to-node-stream": "^3.0.0",
"strtok3": "^6.2.4",
"token-types": "^4.1.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"dev": true,
@ -7752,7 +7771,6 @@
},
"node_modules/ieee754": {
"version": "1.2.1",
"dev": true,
"funding": [
{
"type": "github",
@ -7850,7 +7868,6 @@
},
"node_modules/inherits": {
"version": "2.0.4",
"dev": true,
"license": "ISC"
},
"node_modules/ini": {
@ -10974,7 +10991,6 @@
},
"node_modules/ms": {
"version": "2.1.2",
"dev": true,
"license": "MIT"
},
"node_modules/multicast-dns": {
@ -10994,6 +11010,51 @@
"dev": true,
"license": "MIT"
},
"node_modules/music-metadata": {
"version": "7.12.2",
"resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-7.12.2.tgz",
"integrity": "sha512-KO1L6q30b6HfGlDQk1VAdrZqCKi4Gy7pN7eZOZ0YZQkhF/KCLHxKCjKKli9ao9kIBC/9s+uXHvjW3bDIBWuGew==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"content-type": "^1.0.4",
"debug": "^4.3.3",
"file-type": "16.5.3",
"media-typer": "^1.1.0",
"strtok3": "^6.2.4",
"token-types": "^4.2.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/music-metadata-browser": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/music-metadata-browser/-/music-metadata-browser-2.5.5.tgz",
"integrity": "sha512-38A/q1fz7LOIDxpi2fAzPGMNZQ0YyQUfErizK/rbWRIKC7E4N2BQpqCHq38nHlb7+Iv/wEHgwVoIwbUAXtphEA==",
"dependencies": {
"buffer": "^6.0.3",
"debug": "^4.3.3",
"music-metadata": "^7.12.0",
"readable-stream": "^3.6.0",
"readable-web-to-node-stream": "^3.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/music-metadata/node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/nanoid": {
"version": "3.3.1",
"dev": true,
@ -11545,6 +11606,18 @@
"node": ">=8"
}
},
"node_modules/peek-readable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==",
"engines": {
"node": ">=8"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"dev": true,
@ -12431,6 +12504,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"dev": true,
@ -12753,7 +12834,6 @@
},
"node_modules/readable-stream": {
"version": "3.6.0",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
@ -12764,6 +12844,21 @@
"node": ">= 6"
}
},
"node_modules/readable-web-to-node-stream": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
"dependencies": {
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"dev": true,
@ -13735,7 +13830,6 @@
},
"node_modules/string_decoder": {
"version": "1.3.0",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
@ -13743,7 +13837,6 @@
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.2.1",
"dev": true,
"funding": [
{
"type": "github",
@ -13922,6 +14015,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strtok3": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
"integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^4.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/style-loader": {
"version": "3.3.1",
"dev": true,
@ -14877,6 +14986,22 @@
"node": ">=0.6"
}
},
"node_modules/token-types": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.0.tgz",
"integrity": "sha512-P0rrp4wUpefLncNamWIef62J0v0kQR/GfDVji9WKY7GDCWy5YbVSrKUTam07iWPZQGy0zWNOfstYTykMmPNR7w==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/totalist": {
"version": "1.1.0",
"dev": true,
@ -15211,7 +15336,6 @@
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"dev": true,
"license": "MIT"
},
"node_modules/utila": {
@ -17892,6 +18016,11 @@
}
}
},
"@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
},
"@tootallnate/once": {
"version": "1.1.2",
"dev": true
@ -18858,8 +18987,7 @@
"dev": true
},
"base64-js": {
"version": "1.5.1",
"dev": true
"version": "1.5.1"
},
"batch": {
"version": "0.6.1",
@ -18972,7 +19100,6 @@
},
"buffer": {
"version": "6.0.3",
"dev": true,
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
@ -19304,8 +19431,7 @@
}
},
"content-type": {
"version": "1.0.4",
"dev": true
"version": "1.0.4"
},
"convert-source-map": {
"version": "1.8.0",
@ -19612,7 +19738,6 @@
},
"debug": {
"version": "4.3.3",
"dev": true,
"requires": {
"ms": "2.1.2"
}
@ -20620,6 +20745,16 @@
"flat-cache": "^3.0.4"
}
},
"file-type": {
"version": "16.5.3",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.3.tgz",
"integrity": "sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==",
"requires": {
"readable-web-to-node-stream": "^3.0.0",
"strtok3": "^6.2.4",
"token-types": "^4.1.1"
}
},
"fill-range": {
"version": "7.0.1",
"dev": true,
@ -21102,8 +21237,7 @@
}
},
"ieee754": {
"version": "1.2.1",
"dev": true
"version": "1.2.1"
},
"ignore": {
"version": "5.2.0",
@ -21150,8 +21284,7 @@
}
},
"inherits": {
"version": "2.0.4",
"dev": true
"version": "2.0.4"
},
"ini": {
"version": "1.3.8",
@ -23109,8 +23242,7 @@
"dev": true
},
"ms": {
"version": "2.1.2",
"dev": true
"version": "2.1.2"
},
"multicast-dns": {
"version": "6.2.3",
@ -23124,6 +23256,39 @@
"version": "1.1.0",
"dev": true
},
"music-metadata": {
"version": "7.12.2",
"resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-7.12.2.tgz",
"integrity": "sha512-KO1L6q30b6HfGlDQk1VAdrZqCKi4Gy7pN7eZOZ0YZQkhF/KCLHxKCjKKli9ao9kIBC/9s+uXHvjW3bDIBWuGew==",
"requires": {
"@tokenizer/token": "^0.3.0",
"content-type": "^1.0.4",
"debug": "^4.3.3",
"file-type": "16.5.3",
"media-typer": "^1.1.0",
"strtok3": "^6.2.4",
"token-types": "^4.2.0"
},
"dependencies": {
"media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="
}
}
},
"music-metadata-browser": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/music-metadata-browser/-/music-metadata-browser-2.5.5.tgz",
"integrity": "sha512-38A/q1fz7LOIDxpi2fAzPGMNZQ0YyQUfErizK/rbWRIKC7E4N2BQpqCHq38nHlb7+Iv/wEHgwVoIwbUAXtphEA==",
"requires": {
"buffer": "^6.0.3",
"debug": "^4.3.3",
"music-metadata": "^7.12.0",
"readable-stream": "^3.6.0",
"readable-web-to-node-stream": "^3.0.2"
}
},
"nanoid": {
"version": "3.3.1",
"dev": true
@ -23465,6 +23630,11 @@
"version": "4.0.0",
"dev": true
},
"peek-readable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="
},
"picocolors": {
"version": "1.0.0",
"dev": true
@ -23981,6 +24151,11 @@
}
}
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
},
"process-nextick-args": {
"version": "2.0.1",
"dev": true
@ -24190,13 +24365,20 @@
},
"readable-stream": {
"version": "3.6.0",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"readable-web-to-node-stream": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
"requires": {
"readable-stream": "^3.6.0"
}
},
"readdirp": {
"version": "3.6.0",
"dev": true,
@ -24830,14 +25012,12 @@
},
"string_decoder": {
"version": "1.3.0",
"dev": true,
"requires": {
"safe-buffer": "~5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"dev": true
"version": "5.2.1"
}
}
},
@ -24939,6 +25119,15 @@
"version": "3.1.1",
"dev": true
},
"strtok3": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
"integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==",
"requires": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^4.1.0"
}
},
"style-loader": {
"version": "3.3.1",
"dev": true,
@ -25564,6 +25753,15 @@
"version": "1.0.1",
"dev": true
},
"token-types": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.0.tgz",
"integrity": "sha512-P0rrp4wUpefLncNamWIef62J0v0kQR/GfDVji9WKY7GDCWy5YbVSrKUTam07iWPZQGy0zWNOfstYTykMmPNR7w==",
"requires": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
}
},
"totalist": {
"version": "1.1.0",
"dev": true
@ -25759,8 +25957,7 @@
}
},
"util-deprecate": {
"version": "1.0.2",
"dev": true
"version": "1.0.2"
},
"utila": {
"version": "0.4.0",

View File

@ -106,10 +106,12 @@
"emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#54443d1938ec1c157e74d2a95e9103dcb3f5c6dd",
"events": "^3.3.0",
"idb-keyval": "^6.1.0",
"music-metadata-browser": "^2.5.5",
"opus-recorder": "github:Ajaxy/opus-recorder",
"os-browserify": "^0.3.0",
"pako": "^2.0.4",
"path-browserify": "^1.0.1",
"process": "^0.11.10",
"qr-code-styling": "github:zubiden/qr-code-styling#10f7cf3",
"websocket": "^1.0.34"
},

View File

@ -36,6 +36,7 @@ import {
LOCAL_MESSAGE_ID_BASE,
SERVICE_NOTIFICATIONS_USER_ID,
SPONSORED_MESSAGE_CACHE_MS,
SUPPORTED_AUDIO_CONTENT_TYPES,
SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES,
VIDEO_MOV_TYPE,
@ -1079,9 +1080,8 @@ function buildUploadingMedia(
} = attachment;
if (attachment.quick) {
const { width, height, duration } = attachment.quick;
if (mimeType.startsWith('image/')) {
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
const { width, height } = attachment.quick;
return {
photo: {
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
@ -1090,7 +1090,9 @@ function buildUploadingMedia(
blobUrl,
},
};
} else {
}
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
const { width, height, duration } = attachment.quick;
return {
video: {
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
@ -1105,7 +1107,8 @@ function buildUploadingMedia(
},
};
}
} else if (attachment.voice) {
}
if (attachment.voice) {
const { duration, waveform } = attachment.voice;
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
return {
@ -1115,26 +1118,29 @@ function buildUploadingMedia(
waveform: inputWaveform,
},
};
} else if (mimeType.startsWith('audio/')) {
}
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {
const { duration, performer, title } = attachment.audio || {};
return {
audio: {
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
mimeType,
fileName,
size,
duration: 200, // Arbitrary
},
};
} else {
return {
document: {
mimeType,
fileName,
size,
...(previewBlobUrl && { previewBlobUrl }),
duration: duration || 0,
title,
performer,
},
};
}
return {
document: {
mimeType,
fileName,
size,
...(previewBlobUrl && { previewBlobUrl }),
},
};
}
function buildNewPoll(poll: ApiNewPoll, localId: number) {

View File

@ -534,10 +534,9 @@ export async function rescheduleMessage({
async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) {
const {
filename, blobUrl, mimeType, quick, voice,
filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl,
} = attachment;
const file = await fetchFile(blobUrl, filename);
const patchedOnProgress: ApiOnProgress = (progress) => {
if (onProgress.isCanceled) {
patchedOnProgress.isCanceled = true;
@ -545,8 +544,13 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
onProgress(progress, localMessage.id);
}
};
const file = await fetchFile(blobUrl, filename);
const inputFile = await uploadFile(file, patchedOnProgress);
const thumbFile = previewBlobUrl && await fetchFile(previewBlobUrl, filename);
const thumb = thumbFile ? await uploadFile(thumbFile) : undefined;
const attributes: GramJs.TypeDocumentAttribute[] = [new GramJs.DocumentAttributeFilename({ fileName: filename })];
if (quick) {
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
@ -566,6 +570,15 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
}
}
if (audio) {
const { duration, title, performer } = audio;
attributes.push(new GramJs.DocumentAttributeAudio({
duration,
title,
performer,
}));
}
if (voice) {
const { duration, waveform } = voice;
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
@ -580,6 +593,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
file: inputFile,
mimeType,
attributes,
thumb,
});
}

View File

@ -33,6 +33,11 @@ export interface ApiAttachment {
duration: number;
waveform: number[];
};
audio?: {
duration: number;
title?: string;
performer?: string;
};
previewBlobUrl?: string;
}

View File

@ -407,8 +407,12 @@ function renderAudio(
{!showSeekline && !showProgress && (
<div className="meta" dir={isRtl ? 'rtl' : undefined}>
<span className="duration" dir="auto">{formatMediaDuration(duration)}</span>
<span className="bullet">&bull;</span>
<span className="performer" dir="auto" title={performer}>{renderText(performer || 'Unknown')}</span>
{performer && (
<>
<span className="bullet">&bull;</span>
<span className="performer" dir="auto" title={performer}>{renderText(performer)}</span>
</>
)}
{date && (
<>
<span className="bullet">&bull;</span>

View File

@ -7,6 +7,7 @@ import { ApiAttachment, ApiChatMember } from '../../../api/types';
import {
CONTENT_TYPES_WITH_PREVIEW,
EDITABLE_INPUT_MODAL_ID,
SUPPORTED_AUDIO_CONTENT_TYPES,
SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../config';
@ -185,7 +186,7 @@ const AttachmentModal: FC<OwnProps> = ({
const areAllPhotos = renderingAttachments.every((a) => SUPPORTED_IMAGE_CONTENT_TYPES.has(a.mimeType));
const areAllVideos = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType));
const areAllAudios = renderingAttachments.every((a) => a.mimeType.startsWith('audio/'));
const areAllAudios = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType));
let title = '';
if (areAllPhotos) {

View File

@ -1,5 +1,10 @@
import { ApiAttachment } from '../../../../api/types';
import { SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES } from '../../../../config';
import {
SUPPORTED_AUDIO_CONTENT_TYPES,
SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../../config';
import { parseAudioMetadata } from '../../../../util/audio';
import {
preloadImage,
preloadVideo,
@ -17,6 +22,7 @@ export default async function buildAttachment(
const blobUrl = URL.createObjectURL(blob);
const { type: mimeType, size } = blob;
let quick;
let audio;
let previewBlobUrl;
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
@ -44,6 +50,16 @@ export default async function buildAttachment(
quick = { width, height, duration };
previewBlobUrl = await createPosterForVideo(blobUrl);
} else if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {
const {
duration, title, performer, coverUrl,
} = await parseAudioMetadata(blobUrl);
audio = {
duration: duration || 0,
title,
performer,
};
previewBlobUrl = coverUrl;
}
return {
@ -52,6 +68,7 @@ export default async function buildAttachment(
mimeType,
size,
quick,
audio,
previewBlobUrl,
...options,
};

View File

@ -148,6 +148,18 @@ export const SUPPORTED_VIDEO_CONTENT_TYPES = new Set([
'video/mp4', // video/quicktime added dynamically in environment.ts
]);
export const SUPPORTED_AUDIO_CONTENT_TYPES = new Set([
'audio/mp3',
'audio/ogg',
'audio/wav',
'audio/mpeg',
'audio/flac',
'audio/aac',
'audio/m4a',
'audio/mp4',
'audio/x-m4a',
]);
export const CONTENT_TYPES_WITH_PREVIEW = new Set([
...SUPPORTED_IMAGE_CONTENT_TYPES,
...SUPPORTED_VIDEO_CONTENT_TYPES,

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from '../lib/teact/teact';
export const useAsync = <T>(fn: () => Promise<T>, deps: any[], defaultValue?: T) => {
const useAsync = <T>(fn: () => Promise<T>, deps: any[], defaultValue?: T) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<Error | undefined>();
const [result, setResult] = useState<T | undefined>(defaultValue);
@ -23,3 +23,5 @@ export const useAsync = <T>(fn: () => Promise<T>, deps: any[], defaultValue?: T)
}, deps);
return { isLoading, error, result };
};
export default useAsync;

View File

@ -4,16 +4,17 @@ import {
ApiAudio, ApiChat, ApiMessage, ApiUser, ApiVoice,
} from '../api/types';
import useMedia from './useMedia';
import { useAsync } from './useAsync';
import {
getAudioHasCover, getChatAvatarHash, getChatTitle, getMessageContent, getMessageMediaHash, getSenderTitle,
} from '../global/helpers';
import { getTranslation } from '../util/langProvider';
import { buildMediaMetadata } from '../util/mediaSession';
import { scaleImage, resizeImage } from '../util/imageResize';
import { AVATAR_FULL_DIMENSIONS } from '../components/common/helpers/mediaDimensions';
import useLang from './useLang';
import useMedia from './useMedia';
import useAsync from './useAsync';
import telegramLogoPath from '../assets/telegram-logo-filled.svg';
const LOGO_DIMENSIONS = { width: 200, height: 200 };
@ -23,10 +24,12 @@ const MINIMAL_SIZE = 115; // spec says 100, but on Chrome 93 it's not showing
const useMessageMediaMetadata = (
message: ApiMessage, sender?: ApiUser | ApiChat, chat?: ApiChat,
): MediaMetadata | undefined => {
const lang = useLang();
const { audio, voice } = getMessageContent(message);
const title = audio ? (audio.title || audio.fileName) : voice ? 'Voice message' : '';
const artist = (audio && audio.performer) || (sender && getSenderTitle(getTranslation, sender));
const album = (chat && getChatTitle(getTranslation, chat)) || 'Telegram';
const artist = audio?.performer || (sender && getSenderTitle(lang, sender));
const album = (chat && getChatTitle(lang, chat)) || 'Telegram';
const audioCoverHash = (audio && getAudioHasCover(audio) && getMessageMediaHash(message, 'pictogram'));
const avatarHash = sender && getChatAvatarHash(sender, 'big');
@ -36,7 +39,9 @@ const useMessageMediaMetadata = (
const size = useMemo(() => {
return getCoverSize(audio, voice, media);
}, [audio, media, voice]);
const { result: url } = useAsync(() => makeGoodArtwork(media, size), [media, size], telegramLogoPath);
const { result: url } = useAsync(() => (
makeGoodArtwork(media, size)
), [media, size], telegramLogoPath);
return useMemo(() => {
return buildMediaMetadata({
title,
@ -61,7 +66,7 @@ function makeGoodArtwork(url?: string, size?: { width: number; height: number })
function getCoverSize(audio?: ApiAudio, voice?: ApiVoice, url?: string) {
if (!url) return LOGO_DIMENSIONS;
if (audio) {
if (!audio.thumbnailSizes || audio.thumbnailSizes.length === 0) return undefined;
if (!audio.thumbnailSizes?.length) return undefined;
const preferred = audio.thumbnailSizes.find((size) => size.type === 'm');
return preferred || audio.thumbnailSizes[0]; // Sometimes `m` is not present
}

22
src/util/audio.ts Normal file
View File

@ -0,0 +1,22 @@
type AudioMetadata = {
title?: string;
performer?: string;
duration?: number;
coverUrl?: string;
};
export async function parseAudioMetadata(url: string): Promise<AudioMetadata> {
const { fetchFromUrl, selectCover } = await import('music-metadata-browser');
const metadata = await fetchFromUrl(url);
const { common: { title, artist, picture }, format: { duration } } = metadata;
const cover = selectCover(picture);
const coverUrl = cover ? `data:${cover.format};base64,${cover.data.toString('base64')}` : undefined;
return {
title,
performer: artist,
duration,
coverUrl,
};
}

View File

@ -133,6 +133,7 @@ module.exports = (env = {}, argv = {}) => {
}),
new ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser',
}),
...(argv.mode === 'production' ? [
new BundleAnalyzerPlugin({