25ed765a03
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
356 lines
7.4 KiB
Markdown
356 lines
7.4 KiB
Markdown
# 05 — Medien & Dateien
|
|
|
|
---
|
|
|
|
## Inhaltsverzeichnis
|
|
- [Datei hochladen](#datei-hochladen)
|
|
- [Bild senden](#bild-senden)
|
|
- [Video senden](#video-senden)
|
|
- [Audio / Sprachnachricht senden](#audio--sprachnachricht-senden)
|
|
- [Datei senden](#datei-senden)
|
|
- [Sticker senden](#sticker-senden)
|
|
- [Medien herunterladen & entschlüsseln](#medien-herunterladen--entschlüsseln)
|
|
- [Thumbnails & Vorschaubilder](#thumbnails--vorschaubilder)
|
|
- [MXC-URL zu HTTP-URL](#mxc-url-zu-http-url)
|
|
- [Verschlüsselte Medien](#verschlüsselte-medien)
|
|
- [Avatar hochladen](#avatar-hochladen)
|
|
- [MatrixFile-Objekte](#matrixfile-objekte)
|
|
|
|
---
|
|
|
|
## Datei hochladen
|
|
|
|
```dart
|
|
// Bytes hochladen → mxc:// URI zurück
|
|
final mxcUri = await client.uploadContent(
|
|
fileBytes, // Uint8List
|
|
filename: 'datei.pdf',
|
|
contentType: 'application/pdf', // optional, SDK erkennt es
|
|
);
|
|
print(mxcUri); // mxc://example.org/AbCdEf...
|
|
```
|
|
|
|
---
|
|
|
|
## Bild senden
|
|
|
|
```dart
|
|
import 'package:matrix/matrix.dart';
|
|
|
|
// MatrixImageFile erstellen
|
|
final imageFile = MatrixImageFile(
|
|
bytes: imageBytes, // Uint8List
|
|
name: 'foto.jpg',
|
|
mimeType: 'image/jpeg',
|
|
width: 1920, // optional, für Info
|
|
height: 1080,
|
|
);
|
|
|
|
// Mit Thumbnail
|
|
final thumbnail = MatrixImageFile(
|
|
bytes: thumbnailBytes,
|
|
name: 'foto_thumb.jpg',
|
|
mimeType: 'image/jpeg',
|
|
width: 320,
|
|
height: 180,
|
|
);
|
|
|
|
// Senden (lädt automatisch hoch + sendet m.image Event)
|
|
await room.sendFileEvent(
|
|
imageFile,
|
|
thumbnail: thumbnail, // optional
|
|
);
|
|
|
|
// Alternativ mit sendImageEvent
|
|
await room.sendImageEvent(
|
|
imageFile,
|
|
thumbnail: thumbnail,
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Video senden
|
|
|
|
```dart
|
|
final videoFile = MatrixVideoFile(
|
|
bytes: videoBytes,
|
|
name: 'video.mp4',
|
|
mimeType: 'video/mp4',
|
|
duration: 45000, // Millisekunden
|
|
width: 1920,
|
|
height: 1080,
|
|
);
|
|
|
|
final thumbFile = MatrixImageFile(
|
|
bytes: thumbnailBytes,
|
|
name: 'thumb.jpg',
|
|
mimeType: 'image/jpeg',
|
|
width: 320,
|
|
height: 180,
|
|
);
|
|
|
|
await room.sendVideoEvent(
|
|
videoFile,
|
|
thumbnail: thumbFile,
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Audio / Sprachnachricht senden
|
|
|
|
```dart
|
|
// Audio-Datei
|
|
final audioFile = MatrixAudioFile(
|
|
bytes: audioBytes,
|
|
name: 'aufnahme.ogg',
|
|
mimeType: 'audio/ogg',
|
|
duration: 12000, // Millisekunden
|
|
);
|
|
|
|
await room.sendAudioEvent(audioFile);
|
|
|
|
// Sprachnachricht (Voice Message MSC3245)
|
|
await room.sendFileEvent(
|
|
audioFile,
|
|
extraContent: {
|
|
'org.matrix.msc3245.voice': {}, // markiert als Sprachnachricht
|
|
'org.matrix.msc1767.audio': {
|
|
'duration': 12000,
|
|
'waveform': [0, 50, 100, 75, ...], // Amplituden (0-1024)
|
|
},
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Datei senden
|
|
|
|
```dart
|
|
final file = MatrixFile(
|
|
bytes: fileBytes,
|
|
name: 'dokument.pdf',
|
|
mimeType: 'application/pdf',
|
|
);
|
|
|
|
await room.sendFileEvent(file);
|
|
|
|
// Mit Extra-Metadaten
|
|
await room.sendFileEvent(
|
|
file,
|
|
extraContent: {
|
|
'org.matrix.msc3554.caption': {
|
|
'msgtype': 'm.text',
|
|
'body': 'Hier das Dokument',
|
|
},
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Sticker senden
|
|
|
|
```dart
|
|
// Sticker sind ein eigener Event-Typ (m.sticker)
|
|
await room.sendEvent(
|
|
{
|
|
'body': 'Ein Sticker',
|
|
'url': 'mxc://example.org/stickerId',
|
|
'info': {
|
|
'mimetype': 'image/png',
|
|
'w': 256,
|
|
'h': 256,
|
|
},
|
|
},
|
|
EventTypes.Sticker, // 'm.sticker'
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Medien herunterladen & entschlüsseln
|
|
|
|
```dart
|
|
// Aus Event-Attachment herunterladen (+ automatisch entschlüsseln bei E2EE)
|
|
final data = await event.downloadAndDecryptAttachment(
|
|
getThumbnail: false, // true für Thumbnail statt Original
|
|
);
|
|
// data ist MatrixFile mit .bytes: Uint8List
|
|
|
|
// Direkte MXC-URL herunterladen
|
|
final bytes = await client.database.getFile(mxcUri);
|
|
// oder über HTTP:
|
|
final httpUri = mxcUri.getDownloadUri(client);
|
|
// → https://homeserver/_matrix/media/v3/download/server/mediaId
|
|
```
|
|
|
|
---
|
|
|
|
## Thumbnails & Vorschaubilder
|
|
|
|
```dart
|
|
// Thumbnail-URL für Widget
|
|
final thumbnailUri = event.thumbnailUrl; // mxc:// URI
|
|
|
|
// HTTP-URL für Thumbnail (mit Größe)
|
|
final httpUrl = thumbnailUri?.getThumbnail(
|
|
client,
|
|
width: 800,
|
|
height: 600,
|
|
method: ThumbnailMethod.scale, // oder .crop
|
|
);
|
|
|
|
// Avatar-Thumbnail
|
|
final avatarUrl = user.avatarUrl?.getThumbnail(
|
|
client,
|
|
width: 56,
|
|
height: 56,
|
|
method: ThumbnailMethod.crop,
|
|
);
|
|
|
|
// Im Flutter Widget (CachedNetworkImage o.ä.):
|
|
Image.network(httpUrl.toString());
|
|
```
|
|
|
|
---
|
|
|
|
## MXC-URL zu HTTP-URL
|
|
|
|
```dart
|
|
// Download-URL
|
|
final downloadUrl = Uri.parse(mxcUriString).getDownloadUri(client);
|
|
// → https://homeserver/_matrix/media/v3/download/server/mediaId
|
|
|
|
// Thumbnail-URL
|
|
final thumbUrl = Uri.parse(mxcUriString).getThumbnail(
|
|
client,
|
|
width: 300,
|
|
height: 300,
|
|
method: ThumbnailMethod.crop,
|
|
);
|
|
|
|
// Direkt als String
|
|
final urlString = downloadUrl.toString();
|
|
```
|
|
|
|
---
|
|
|
|
## Verschlüsselte Medien
|
|
|
|
```dart
|
|
// In verschlüsselten Räumen werden Medien automatisch verschlüsselt.
|
|
// Das SDK übernimmt Ver- und Entschlüsselung transparent.
|
|
|
|
// Verschlüsselter Download (automatisch):
|
|
final file = await event.downloadAndDecryptAttachment();
|
|
// file.bytes enthält die entschlüsselten Rohdaten
|
|
|
|
// Manuell ver-/entschlüsseln (selten nötig):
|
|
// Nutze EncryptedFile aus dem matrix-package
|
|
final encryptedFile = EncryptedFile(
|
|
url: 'mxc://...',
|
|
key: {/* JWK */},
|
|
iv: 'base64...',
|
|
hashes: {'sha256': 'base64...'},
|
|
v: 'v2',
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Avatar hochladen
|
|
|
|
```dart
|
|
// Eigener Avatar
|
|
final avatarBytes = await File('avatar.png').readAsBytes();
|
|
final mxcUri = await client.uploadContent(
|
|
avatarBytes,
|
|
filename: 'avatar.png',
|
|
contentType: 'image/png',
|
|
);
|
|
await client.setAvatarUrl(client.userID!, mxcUri);
|
|
|
|
// Raum-Avatar
|
|
final roomAvatarMxc = await client.uploadContent(imageBytes, filename: 'room.png');
|
|
await room.setAvatar(roomAvatarMxc);
|
|
```
|
|
|
|
---
|
|
|
|
## MatrixFile-Objekte
|
|
|
|
```dart
|
|
// Basis-Klasse
|
|
MatrixFile({
|
|
required Uint8List bytes,
|
|
required String name,
|
|
String? mimeType,
|
|
})
|
|
|
|
// Spezialisierte Unterklassen:
|
|
MatrixImageFile({
|
|
required Uint8List bytes,
|
|
required String name,
|
|
String? mimeType, // z.B. 'image/jpeg'
|
|
int? width,
|
|
int? height,
|
|
bool? animated, // für GIFs
|
|
})
|
|
|
|
MatrixVideoFile({
|
|
required Uint8List bytes,
|
|
required String name,
|
|
String? mimeType, // 'video/mp4'
|
|
int? duration, // ms
|
|
int? width,
|
|
int? height,
|
|
MatrixImageFile? thumbnail,
|
|
})
|
|
|
|
MatrixAudioFile({
|
|
required Uint8List bytes,
|
|
required String name,
|
|
String? mimeType, // 'audio/ogg'
|
|
int? duration, // ms
|
|
})
|
|
|
|
// Eigenschaften
|
|
file.bytes // Uint8List
|
|
file.name // Dateiname
|
|
file.mimeType // MIME-Typ
|
|
file.sizeString // '1.4 MB' (formatiert)
|
|
file.size // Bytes als int
|
|
```
|
|
|
|
---
|
|
|
|
## Medien-Events lesen
|
|
|
|
```dart
|
|
// Attachment-Info aus Event
|
|
if (event.messageType == 'm.image') {
|
|
final url = event.attachmentMxcUrl; // mxc:// URI
|
|
final mime = event.attachmentMimetype; // 'image/jpeg'
|
|
final size = event.attachmentSize; // Bytes
|
|
final width = event.infoMap['w'] as int?;
|
|
final height = event.infoMap['h'] as int?;
|
|
}
|
|
|
|
if (event.messageType == 'm.video') {
|
|
final duration = event.infoMap['duration'] as int?; // ms
|
|
}
|
|
|
|
if (event.messageType == 'm.audio') {
|
|
final duration = event.infoMap['duration'] as int?;
|
|
final waveform = event.content
|
|
.tryGetMap('org.matrix.msc1767.audio')
|
|
?.tryGetList<int>('waveform');
|
|
}
|
|
|
|
// Datei-Größe als lesbarer String
|
|
print(event.sizeString); // '2.3 MB'
|
|
```
|