Started implementing video support.

This commit is contained in:
2026-04-10 09:15:37 +02:00
parent 7d9737e9c2
commit 2bcb6adbb3
13 changed files with 429 additions and 55 deletions

View File

@@ -17,7 +17,7 @@
<masonry [maxColSize]="2" style="overflow-y: scroll; height: 500px">
@for (file of viewModel().files(); track file) {
@if (file.type == "image") {
<img [src]="file.preview" style="width: 100%; height: 100%; object-fit: fill;"/>
<img [src]="file.blob" style="width: 100%; height: 100%; object-fit: fill;"/>
}
}
</masonry>

View File

@@ -107,22 +107,24 @@ export class MessageBox {
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
const type = file.type.split("/").shift() ?? ""
let preview = ""
let blob = ""
let thumbnailBlob = undefined
let height = 0
let width = 0
console.log(type)
blob = URL.createObjectURL(file)
if (type == "image") {
try {
const imgData = await makePicturePreview(file)
console.log(imgData)
preview = imgData.preview
height = imgData.height
width = imgData.width
} catch (error) {
console.error(error)
}
const imgData = await fetchImageDimensions(file)
console.log(imgData)
height = imgData.height
width = imgData.width
} else if (type == "video") {
const videoData = await processVideo(file)
thumbnailBlob = videoData.thumbnailBlob
height = videoData.height
width = videoData.width
}
console.log("push")
@@ -132,14 +134,15 @@ export class MessageBox {
name: file.name,
type: type,
extension: file.name.split(".").pop() ?? "",
preview: preview,
blob: blob,
height: height,
width: width,
videoThumbnail: thumbnailBlob,
})
}
this.viewModel().dialogOpen.set(true)
function makePicturePreview(file: File): Promise<{ preview: string, height: number, width: number }> {
function fetchImageDimensions(file: File): Promise<{ height: number, width: number }> {
return new Promise((resolve, reject) => {
const img = new Image();
const objectUrl = URL.createObjectURL(file);
@@ -147,7 +150,6 @@ export class MessageBox {
img.onload = () => {
console.log("Loaded image")
resolve({
preview: objectUrl,
width: img.naturalWidth,
height: img.naturalHeight,
});
@@ -162,6 +164,37 @@ export class MessageBox {
img.src = objectUrl;
});
}
async function processVideo(file: File): Promise<{ thumbnailBlob: string, height: number, width: number }> {
const objectUrl = URL.createObjectURL(file);
const video = document.createElement('video');
video.src = objectUrl;
video.crossOrigin = 'anonymous';
video.muted = true;
return new Promise((resolve) => {
video.addEventListener('loadeddata', async () => {
video.currentTime = 0;
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx!.drawImage(video, 0, 0, canvas.width, canvas.height);
const thumbnail = canvas.toDataURL('image/jpeg')
const thumbnailResp = await fetch(thumbnail);
const thumbnailBlob = await thumbnailResp.blob();
const thumbnailBlobUrl = URL.createObjectURL(thumbnailBlob);
resolve({
thumbnailBlob: thumbnailBlobUrl,
height: video.videoHeight,
width: video.videoWidth,
});
});
});
}
}
protected readonly console = console;
@@ -171,7 +204,8 @@ export class MessageBox {
* All the extra data for client-side rendering. (With finished messages these are generated on the server)
*/
export interface FileDataWithPreview extends FileData {
preview: string;
blob: string;
videoThumbnail?: string;
height: number;
width: number;
}