feat(backgrounds): accept webm custom call backgrounds (#2377)

This commit is contained in:
Hampus
2026-09-02 17:30:50 +02:00
committed by GitHub
parent f57dc77c6d
commit cadab239a2
4 changed files with 33 additions and 20 deletions
@@ -76,7 +76,7 @@ export const ANIMATED_BANNER_FEATURE = 'ANIMATED_BANNER';
export const STATIC_IMAGE_FORMATS = formatAssetUploadExtensions('avatar', {animatedAllowed: false});
export const STATIC_IMAGE_WITH_AVIF_FORMATS = formatAssetUploadExtensions('splash');
export const ANIMATED_IMAGE_FORMATS = formatAssetUploadExtensions('avatar');
export const BACKGROUND_MEDIA_FORMATS = 'JPG, PNG, GIF, WebP, MP4';
export const BACKGROUND_MEDIA_FORMATS = 'JPG, PNG, GIF, WebP, MP4, WebM';
export const ANIMATED_AVATAR_FORMATS = formatKnownAnimatedAssetExtensions('avatar');
export const AVIF_FORMAT_LABEL = 'AVIF';
export const CUSTOM_SOUND_FORMATS = 'MP3, WAV, OGG, M4A, AAC, FLAC, Opus, WebM';
@@ -179,8 +179,8 @@ interface BuiltInBackground {
type BackgroundItemType = BuiltInBackground | BackgroundImage;
const MAX_FILE_SIZE = 10 * 1024 * 1024;
const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4'];
const ALLOWED_FILE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4'];
const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4', 'video/webm'];
const ALLOWED_FILE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm'];
function isAllowedBackgroundFile(file: File): boolean {
if (ALLOWED_MIME_TYPES.includes(file.type)) {
@@ -8,21 +8,29 @@ export interface NativeBackgroundMediaSource {
mediaKind: VoiceBackgroundMediaKind;
}
interface BackgroundImageRead {
cancelled: boolean;
promise: Promise<string | null>;
export interface BackgroundMediaObjectURL {
url: string;
mediaKind: VoiceBackgroundMediaKind;
}
const resolvedUrls = new Map<string, string>();
interface BackgroundImageRead {
cancelled: boolean;
promise: Promise<BackgroundMediaObjectURL | null>;
}
const resolvedMedia = new Map<string, BackgroundMediaObjectURL>();
const pendingReads = new Map<string, BackgroundImageRead>();
async function readBackgroundImageObjectURL(id: string): Promise<string | null> {
async function readBackgroundMediaObjectURL(id: string): Promise<BackgroundMediaObjectURL | null> {
const readVoiceBackgroundMedia = getElectronAPI()?.readVoiceBackgroundMedia;
if (!readVoiceBackgroundMedia) return null;
const media = await readVoiceBackgroundMedia(id);
if (!media?.dataUrl) return null;
const response = await fetch(media.dataUrl);
return URL.createObjectURL(await response.blob());
return {
url: URL.createObjectURL(await response.blob()),
mediaKind: media.mediaKind,
};
}
export async function saveBackgroundImage(id: string, blob: Blob): Promise<NativeBackgroundMediaSource> {
@@ -50,14 +58,14 @@ export async function deleteBackgroundImage(id: string): Promise<void> {
}
export function getCachedBackgroundImageURL(id: string): string | null {
return resolvedUrls.get(id) ?? null;
return resolvedMedia.get(id)?.url ?? null;
}
export function releaseBackgroundImageURL(id: string): void {
const resolved = resolvedUrls.get(id);
const resolved = resolvedMedia.get(id);
if (resolved != null) {
URL.revokeObjectURL(resolved);
resolvedUrls.delete(id);
URL.revokeObjectURL(resolved.url);
resolvedMedia.delete(id);
}
const pending = pendingReads.get(id);
if (pending != null) {
@@ -66,21 +74,21 @@ export function releaseBackgroundImageURL(id: string): void {
}
}
export async function getBackgroundImageURL(id: string): Promise<string | null> {
const resolved = resolvedUrls.get(id);
export async function getBackgroundMediaObjectURL(id: string): Promise<BackgroundMediaObjectURL | null> {
const resolved = resolvedMedia.get(id);
if (resolved != null) return resolved;
const inFlight = pendingReads.get(id);
if (inFlight != null) return inFlight.promise;
const read: BackgroundImageRead = {cancelled: false, promise: Promise.resolve(null)};
read.promise = readBackgroundImageObjectURL(id).then(
(url) => {
read.promise = readBackgroundMediaObjectURL(id).then(
(media) => {
if (read.cancelled) {
if (url != null) URL.revokeObjectURL(url);
if (media != null) URL.revokeObjectURL(media.url);
return null;
}
pendingReads.delete(id);
if (url != null) resolvedUrls.set(id, url);
return url;
if (media != null) resolvedMedia.set(id, media);
return media;
},
(error: unknown) => {
if (!read.cancelled) pendingReads.delete(id);
@@ -90,3 +98,7 @@ export async function getBackgroundImageURL(id: string): Promise<string | null>
pendingReads.set(id, read);
return read.promise;
}
export async function getBackgroundImageURL(id: string): Promise<string | null> {
return (await getBackgroundMediaObjectURL(id))?.url ?? null;
}
@@ -31,6 +31,7 @@ const MEDIA_TYPES = new Map<
['image/gif', {mimeType: 'image/gif', extension: 'gif', mediaKind: 'animated'}],
['image/webp', {mimeType: 'image/webp', extension: 'webp', mediaKind: 'static'}],
['video/mp4', {mimeType: 'video/mp4', extension: 'mp4', mediaKind: 'video'}],
['video/webm', {mimeType: 'video/webm', extension: 'webm', mediaKind: 'video'}],
]);
const MEDIA_TYPES_BY_EXTENSION = new Map(Array.from(MEDIA_TYPES.values()).map((value) => [value.extension, value]));