// 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 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> 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, ); } }