nkbmusic/lib/offline/encrypted_audio_source.dart
metatroncubeswdev 178d2d00ab Nanga Kathai: Flutter app, self-hosted backend and song tools
- Flutter app (Android/iOS): home, explore, albums, search, player with
  equalizer, mini player, sign up/sign in with email codes, profile,
  encrypted offline downloads, About page
- server/: one-command deploy of self-hosted Supabase behind HestiaCP/nginx
  (or Caddy), migrations, backups, email templates
- supabase/migrations: schema, row-level security, profiles
- tools/: song scanner and importer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:39:26 -04:00

56 lines
1.6 KiB
Dart

// StreamAudioSource is marked experimental in just_audio but is its supported way to play custom byte streams.
// ignore_for_file: experimental_member_use
import 'dart:io';
import 'dart:typed_data';
import 'package:just_audio/just_audio.dart';
import 'song_cipher.dart';
/// Plays a downloaded song straight from its encrypted file, decrypting only the
/// bytes the player asks for. The plain audio never touches the disk.
class EncryptedFileAudioSource extends StreamAudioSource {
EncryptedFileAudioSource(this.file, this.cipher, {this.contentType = 'audio/mpeg', super.tag});
final File file;
final SongCipher cipher;
final String contentType;
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
final raf = await file.open();
final total = await raf.length() - SongCipher.headerLength;
final nonce = Uint8List(SongCipher.headerLength);
await raf.readInto(nonce);
start ??= 0;
end = (end == null || end > total) ? total : end;
Stream<List<int>> body() async* {
try {
const chunk = 64 * 1024;
var pos = start!;
await raf.setPosition(SongCipher.headerLength + pos);
while (pos < end!) {
final len = end - pos < chunk ? end - pos : chunk;
final data = await raf.read(len);
if (data.isEmpty) break;
cipher.apply(data, pos, nonce);
yield data;
pos += data.length;
}
} finally {
await raf.close();
}
}
return StreamAudioResponse(
sourceLength: total,
contentLength: end - start,
offset: start,
stream: body(),
contentType: contentType,
);
}
}