florent37 / Flutter-AssetsAudioPlayer

Play simultaneously music/audio from assets/network/file directly from Flutter, compatible with android / ios / web / macos, displays notifications
https://pub.dartlang.org/packages/assets_audio_player
Apache License 2.0
757 stars 370 forks source link

Custom Audio Stream #754

Open mt633 opened 1 year ago

mt633 commented 1 year ago

I have an audio file that is stored in a password-protected ZIP and I would prefer not having to store the file locally before playing it. Would it be possible to add an option to provide the bytes that are to be played manually?

just_audio has the option to extend StreamAudioSource that allows you to do just that.

Apart from security reasons, if I need to extract the ZIP to provide the local path, that would also mean that I need to extract all of the files I want to play before I add them to a playlist with the current setup. Ideally I would like to extract the file once the playback should start.

Here's an idea of what I have in mind, based on the just_audio setup.

import 'dart:io';
import 'dart:typed_data';

import 'package:archive/archive_io.dart';
import 'package:assets_audio_player/assets_audio_player.dart';

class CustomAudioSource extends StreamAudio {
  final String path;
  Uint8List? _buffer;
  CustomAudioSource({required this.path});

  Future<Uint8List?> buildBuffer() async {
    if (_buffer != null) return _buffer!;
    File archiveFile = File(path);
    final decodedArchive = ZipDecoder().decodeBytes(
        archiveFile.readAsBytesSync(),
        verify: true,
        password: MY_PASSWORD);
    var file = decodedArchive.files.first;
    return file.content;
  }

  @override
  Future<StreamAudioResponse?> request([int? start, int? end]) async {
    Uint8List buffer = await buildBuffer() ?? Uint8List(0);
    if (buffer.isEmpty) return null;

    start = start ?? 0;
    end = end ?? buffer.length;

    return Future.value(
      StreamAudioResponse(
        sourceLength: buffer.length,
        contentLength: end - start,
        offset: start,
        contentType: 'audio/mp4',
        stream:
            Stream.value(List<int>.from(buffer.skip(start).take(end - start))),
      ),
    );
  }
}
mt633 commented 1 year ago

As I mentioned in this comment https://github.com/florent37/Flutter-AssetsAudioPlayer/issues/260#issuecomment-1436967890, I've found a way to do this for Android and iOS. Let me know if anyone needs the code or if you want me to create a PR.

avenpace commented 1 year ago

Is there a way that I can just intercept the music buffer from the network and manipulate it at runtime?

mt633 commented 1 year ago

Building on this method https://github.com/florent37/Flutter-AssetsAudioPlayer/issues/260#issuecomment-1438175992 you should be able to manipulate the bytes before returning them to the native player if that's what you need. However, this is not implemented in this library yet, nor have I finished a PR for it.

mt633 commented 1 year ago

Now I've finally published my changes. Both base64 and custom stream for Android/iOS is there. I've also added a few other changes, such as a callback for seeking state, support for custom errors (especially for custom streams) and various bug fixes. Didn't have the time to split them into different branches.

If anyone is interested in trying it out, clone/fork this repository and check out the examples, or add this to your pubspec.yaml:

assets_audio_player:
    git:
      url: https://github.com/mt633/Flutter-AssetsAudioPlayer

Check out the README for example usages.

Let me know if you stumble upon any bugs using it.

avenpace commented 1 year ago

awesome, thanks for sharing @mt633

Faiyyaz commented 1 year ago

@mt633 CustomAudioSource should allow direct passing of Uint8List similar to just_audio package