Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Play audio from local file in web app #187

Closed
Mr-Pepe opened this issue Sep 21, 2020 · 38 comments
Closed

Play audio from local file in web app #187

Mr-Pepe opened this issue Sep 21, 2020 · 38 comments
Assignees
Labels
3 testing enhancement New feature or request

Comments

@Mr-Pepe
Copy link

Mr-Pepe commented Sep 21, 2020

Is your feature request related to a problem? Please describe.
Playing audio from a local file doesn't work in a web app.

Describe the solution you'd like
Support out of the box for playing audio from local files in a web app.

Is there a roadmap for this feature?

@Mr-Pepe Mr-Pepe added 1 backlog enhancement New feature or request labels Sep 21, 2020
@ryanheise
Copy link
Owner

I assume you are aware of the file system restrictions in web apps.

If it is not working, would you be interested in helping by creating a pull request? Some relevant information is below:

https://w3c.github.io/FileAPI/

I would have expected a file selected via a picker should work in just_audio, although I haven't tested it.

@ryanheise
Copy link
Owner

Closing due to no response, but comment below if you'd like me to re-open it.

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Nov 7, 2020

Thanks for the reply and sorry for the late response.

I have never done any web development before, so I had to play around a bit first. I use the file_picker package to select a file, but as you pointed out, the file is not available via the path. I can get the raw byte data of the file, but now I am stuck when it comes to actually using it. From what I understand just_audio can not play directly from memory (yet), so I have to provide it some file. I tried writing to an in-memory file, but get error 4: "Failed to load URL."

File audioFile = MemoryFileSystem().file('test.dart');
await audioFile.writeAsBytes(bytes, flush: true);

player = AudioPlayer();
await player.load(AudioSource.uri(audioFile.uri));

Do you have an idea why this wouldn't work?

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Nov 7, 2020

Is it because just_audio looks for the URI in the actual file system and not the in-memory one? Can I somehow pass the file directly to the player?

@ryanheise ryanheise reopened this Jan 8, 2021
@ryanheise
Copy link
Owner

Hi and sorry for the delay. I have just created a new branch called proxy_improvements which includes a new class StreamAudioSource. You can use it to feed bytes into the audio player, and this should make your use case possible.

Let me know how it goes for you, and I might consider adding more convenience methods such as:

setStream(Stream<List<int>> stream)
setBytes(List<int> bytes)

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 10, 2021

Looks promising, but I don't fully understand it yet.

I write my on StreamAudioSource:

class MyAudioSource extends StreamAudioSource {
  Uint8List _buffer;

  MyAudioSource(this._buffer) : super("Bla");

  @override
  Stream<List<int>> read([int start, int end]) {
    return Stream.value(_buffer.skip(start).take(end - start));
  }

  @override
  int get lengthInBytes {
    return _buffer.length;
  }
}

And then I try to set that audio source:

await player.setAudioSource(MyAudioSource(buffer));

However, I get a NoSuchMethodError: The getter 'uri' was called on null. in addStreamAudioSource. It looks to me like the proxy is not running.

Is there some setup required?

@ryanheise
Copy link
Owner

Hmm, are you able to get a stack trace?

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 11, 2021

The error is:
JSNoSuchMethodError (NoSuchMethodError: invalid member on null: 'addStreamAudioSource')

And this is the call stack. I used VS Code, activated breaking on exceptions and copied the call stack. Please let me know if you need more info and I'll try my best to help.

_setup (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:1889)
<asynchronous gap> (Unknown Source:0)
<closure> (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:937)
<asynchronous gap> (Unknown Source:0)
_setPlatformActive (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:937)
load (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:594)
load (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:584)
setAudioSource (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:567)
setAudioSource (/home/felipe/Projects/just_audio/just_audio/lib/just_audio.dart:550)
setAudio (/home/felipe/Projects/tingting/lib/viewModels/tingtingViewModel.dart:59)
setAudio (/home/felipe/Projects/tingting/lib/viewModels/tingtingViewModel.dart:47)
getAudioFromFile (/home/felipe/Projects/tingting/lib/utils/getAudio.dart:80)
<asynchronous gap> (Unknown Source:0)
<closure> (/home/felipe/.pub-cache/hosted/pub.dartlang.org/file_picker-2.0.0/lib/src/file_picker_web.dart:59)
<asynchronous gap> (Unknown Source:0)
<closure> (/home/felipe/.pub-cache/hosted/pub.dartlang.org/file_picker-2.0.0/lib/src/file_picker_web.dart:64)
<closure> (/home/felipe/.pub-cache/hosted/pub.dartlang.org/file_picker-2.0.0/lib/src/file_picker_web.dart:63)

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 11, 2021

You can also run the whole thing yourself by checking out the "proxy" branch of this repo. You just need to change the path to your local just_audio version in the pubspec.yml and run the app in the example folder. Click the note symbol in the upper right corner of the app and choose "From file".

@ryanheise
Copy link
Owner

Thanks for the reproduction case! I will take a look.

@ryanheise
Copy link
Owner

Sorry to cause more trouble for you but I just changed the StreamingAudioSource API slightly after trying to implement a subclass myself and realised that in some cases, the length in bytes couldn't be returned synchronously. Please try first updating your code to use this API and if it still produces an error, I would like to see if I can fix it.

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 17, 2021

I have implemented an audio source using the new API, but I still get the same null pointer exception inside the _setup function (line 1909). I have pushed the changes to the branch I mentioned for you to reproduce. Thanks for your effort and let me know if you need help or additional information!

@ryanheise
Copy link
Owner

Ah yes, of course. The proxy is disabled for web because dart:io is not available for web. Unfortunately this means I would need to find another way to support streaming audio data from a buffer to the player on the web platform. (I'm sorry to have gotten your hopes up by the latest commits! It will certainly require more thought.)

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 21, 2021

Thank you for your efforts! It looks like you at least have a solution to directly stream from a buffer on other platforms, which is nice. Unfortunately, that's a show stopper for me for the time being.

@ryanheise
Copy link
Owner

If you can find a Javascript app that works, I may be able to implement it's solution. Does this work for you?

https://simontabor.com/labs/audio/

@ryanheise
Copy link
Owner

I'm currently working on #288 which might lead to a solution for this issue as well.

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 23, 2021

I have tried the HTML player you linked in both Firefox and Chrome, but it doesn't show me anything after loading a file.

I subscribed to #288 and will give it a spin as soon as you think it should work.

@ryanheise
Copy link
Owner

This should all be implemented in the latest commit. Your existing code should theoretically work but please do let me know if it does!

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 23, 2021

Hmm, the request function gets passed null as start and end.

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 23, 2021

This is my request function

@override
  Future<StreamAudioResponse> request([int start, int end]) {
    print(_buffer.length);
    print("Start:" + start.toString());
    print("End:" + end.toString());
    return Future.value(
      StreamAudioResponse(
        sourceLength: _buffer.length,
        contentLength: end - start,
        offset: start,
        contentType: 'audio/mpeg3',
        stream: Stream.value(_buffer.skip(start).take(end - start)),
      ),
    );
  }

which prints

6253769
Start:null
End:null

@ryanheise
Copy link
Owner

Yes that is possible. If those parameters are null, you should return the entire file.

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Jan 23, 2021

Sorry, I overlooked that. I had to cast my buffer from uint8 to int, but now it works!
Thank you so much for your awesome work.

@Mr-Pepe Mr-Pepe closed this as completed Jan 23, 2021
@ryanheise
Copy link
Owner

Great to hear. This is now published in 0.6.9.

@AniketBhadane
Copy link

Sorry, I overlooked that. I had to cast my buffer from uint8 to int, but now it works!
Thank you so much for your awesome work.

Hi @Mr-Pepe , please can you post the updated code which worked for you?

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Feb 28, 2021

Sure, here is my audio source code:

import 'dart:typed_data';

import 'package:just_audio/just_audio.dart';

class BufferAudioSource extends StreamAudioSource {
  Uint8List _buffer;

  BufferAudioSource(this._buffer) : super("Bla");

  @override
  Future<StreamAudioResponse> request([int start, int end]) {
    start = start ?? 0;
    end = end ?? _buffer.length;

    return Future.value(
      StreamAudioResponse(
        sourceLength: _buffer.length,
        contentLength: end - start,
        offset: start,
        contentType: 'audio/mpeg',
        stream:
            Stream.value(List<int>.from(_buffer.skip(start).take(end - start))),
      ),
    );
  }
}

@petherwiklander
Copy link

Sorry, I overlooked that. I had to cast my buffer from uint8 to int, but now it works!

@Mr-Pepe been struggling with this for a while. Could you help regarding how to cast the Uint8List to make your code work?
I keep getting a very generic error when trying - "(0) Source error"

@Mr-Pepe
Copy link
Author

Mr-Pepe commented Mar 30, 2021

It's in the last line of the code I posted:

List<int>.from(_buffer.skip(start).take(end - start))

_buffer is the Uint8List that I take the desired bytes from and List<int>.from(...) converts it to a list of ints.

Where exactly do you get the error? Did you manage to load the file properly?

I use file_picker to load a file

final audioFile = await FilePicker.platform.pickFiles();

and pass the content to the audio source

BufferAudioSource(audioFile.files.first.bytes);

Does that help you?

@petherwiklander
Copy link

Hi! Sorry for the slow response.
I realized two things. One is that this worked fine in web but not in our app after solving a volume bug 🤦🏼‍♂️.
The other is that the error in our app (Android/iOS) is #254.
I will probably end up using streams when its web and files/assets in the app until there is a fix for the error.

@ma-pe
Copy link

ma-pe commented Apr 26, 2021

Hi @Mr-Pepe and @ryanheise, thanks a lot for this issue and your progress on it.
I'd also like to download an audio file (wav) to both app and web and play it locally.

The BufferAudioSource helped me get it to work for Chrome and Firefox. Yet on Safari-based browsers (both desktop and mobile) this does not seem to work out of the box.

When setting the AudioSource I receive an Error(4) failed to load url. This may be about the special handling of media files from apple as stated in the Readme: "The iOS player relies on server headers (e.g. Content-Type, Content-Length and byte range requests)...".

This is how the network traffic tab looks like the Byte range requests are failing:
image
(I can't tell you the exact error since the Web Inspector keeps crashing when clicking the request 😬 )

Any idea how to work around this?

The BufferAudioSource also doesn't work for me for iOS (app, not web: Error: (-11800) The operation could not be completed). This could be the issue mentioned by @petherwiklander. I will check on that next.

@ryanheise
Copy link
Owner

The iOS player relies on server headers (e.g. Content-Type, Content-Length and byte range requests)...

I don't think that would be it, this is implemented internally as a base64 data URL so it doesn't contact any servers. It may mean either that data URLs are not supported in Safari or that maybe the data is too long. Can you confirm whether or not the example works on Safari? The last item in the playlist is an asset so it would be loaded using the same mechanism internally.

@ma-pe
Copy link

ma-pe commented Apr 27, 2021

@ryanheise I checked out the just_audio repository and tried to launch the web version by switching to just_audio_web and running flutter run lib/just_audio_web.dart.

That resulted in a log message:

No supported devices connected.
The following devices were found, but are not supported by this project:
Chrome (web) • chrome • web-javascript • Google Chrome 90.0.4430.85

So I assume I am doing the wrong thing 😬


Meanwhile some more info about the safari issue: When I use a really short wav-file, the web inspector isn't crashing, when opening the details of the request. Yet it seems that Safari is doing byte-range requests on the data url request (which seems a bit odd..?):

image

Even though Safari tries to fetch the Byte Rande 0-1 it receives the full data url. Does that ring any bell? 🙃

By the way: also images are (successfully) transmitted via data urls in Safari (you can see it in the same screenshot). So I think we can assume that this isn't the issue. But maybe you are right on size of the data urls.

@ryanheise
Copy link
Owner

You can test the example by switching to the main example directory and running on the chrome device. e.g. flutter run -d chrome.

@ma-pe
Copy link

ma-pe commented Apr 27, 2021

Hrmpf, obviously I tried the main directory first but forgot to switch to the example directory 🙈 Thanks.

Although I see one failing network request (the image isn't loaded due to cors on safari and chrome) the sound files are playing. The Byte-range headers look exactly the same (also saying they were failing but obviously aren't). The only difference I see is that you use audio/mpeg instead of audio/wav.

I will dig deeper into your example code as well as trying different files.

@ma-pe
Copy link

ma-pe commented Apr 27, 2021

@ryanheise I can confirm that it works with another mp3-file for all browsers and iOS (app).

So it seems to be an issue with the encoding or format of my file. Thanks a lot for your fast help 👍

@ryanheise
Copy link
Owner

Glad to know you got to the bottom of it!

@rguntha
Copy link

rguntha commented Aug 18, 2021

Yes that is possible. If those parameters are null, you should return the entire file.

Is there a way we can return small chunks of the byte array from the StreamAudioSource? Right now I am working with web and android versions, in both of them, the start and end values are coming in as null in the request method and it is taking almost 30 seconds for the song to load in web and around 5 seconds in mobile. This performance is not acceptable. In the web I am able to download the song in few seconds, but the player stays for a long time in loading state. I tried to return only 1kb at a time, by setting the end value to 1024, but the player fails with an error "Unable to load the url"

Please help me to improve the performance. And load fast in small chunks.

@ryanheise
Copy link
Owner

If you would like StreamAudioSource to support range requests on web, please make a separate feature request.

@github-actions
Copy link

github-actions bot commented Nov 2, 2021

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs, or use StackOverflow if you need help with just_audio.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
3 testing enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants