wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
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
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
# 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'
|
||||
```
|
||||
Reference in New Issue
Block a user