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

Proxy Handler triggered two times in StreamAudioSource #1195

Open
JakeSeo opened this issue Feb 22, 2024 · 8 comments
Open

Proxy Handler triggered two times in StreamAudioSource #1195

JakeSeo opened this issue Feb 22, 2024 · 8 comments
Assignees
Labels
1 backlog bug Something isn't working

Comments

@JakeSeo
Copy link

JakeSeo commented Feb 22, 2024

Which API doesn't behave as documented, and how does it misbehave?
I tried to create my own StreamAudioSource, and injected it as the audio source for my streaming audio.

It works but it seems that last few chunks of the stream is not being played. I tried to check the library code and the part where local audio server is created and listens to incoming requests, the corresponding proxy handler for the audio streaming is triggered two times, when the stream starts and ends.

I don't understand why the handler is triggered when the stream ends. When the handler is triggered, the stream is already finished, so it closes the listeners right away. At this point, audio is not finished so the player cuts the audio before the last few chunks are played. Is this suppose to happen? or am I missing something?

here's the code that I think causes the problem:

// from just_audio.dart
...
  /// Starts the server.
  Future<dynamic> start() async {
    _running = true;
    _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
    _server.listen((request) async {
      if (request.method == 'GET') {                        // this listener gets triggered when the stream ends.
        final uriPath = _requestKey(request.uri);
        final handler = _handlerMap[uriPath]!;
        handler(this, request); 
      }
    }, onDone: () {
      _running = false;
    }, onError: (Object e, StackTrace st) {
      _running = false;
    });
  }

...

/// The type of functions that can handle HTTP requests sent to the proxy.
typedef _ProxyHandler = void Function(
    _ProxyHttpServer server, HttpRequest request);

/// A proxy handler for serving audio from a [StreamAudioSource].
_ProxyHandler _proxyHandlerForSource(StreamAudioSource source) {
  Future<void> handler(_ProxyHttpServer server, HttpRequest request) async {
    final rangeRequest =
        _HttpRangeRequest.parse(request.headers[HttpHeaders.rangeHeader]);

    request.response.headers.clear();

    StreamAudioResponse sourceResponse;
    Stream<List<int>> stream;
    try {
      sourceResponse =
          await source.request(rangeRequest?.start, rangeRequest?.endEx);
      stream = sourceResponse.stream;
    } catch (e, st) {
      // ignore: avoid_print
      print("Proxy request failed: $e\n$st");

      request.response.headers.clear();
      request.response.statusCode = HttpStatus.internalServerError;
      await request.response.close();
      return;
    }

    request.response.headers
        .set(HttpHeaders.contentTypeHeader, sourceResponse.contentType);

    if (sourceResponse.rangeRequestsSupported) {
      request.response.headers.set(HttpHeaders.acceptRangesHeader, 'bytes');
    }

    if (rangeRequest != null && sourceResponse.offset != null) {
      final range = _HttpRangeResponse(
          sourceResponse.offset!,
          sourceResponse.offset! + sourceResponse.contentLength! - 1,
          sourceResponse.sourceLength);
      request.response.contentLength = range.length ?? -1;
      request.response.headers
          .set(HttpHeaders.contentRangeHeader, range.header);
      request.response.statusCode = 206;
    } else {
      request.response.contentLength = sourceResponse.contentLength ?? -1;
      request.response.statusCode = 200;
    }

    final completer = Completer<void>();
    final subscription = stream.listen((event) {
      request.response.add(event);
    }, onError: (Object e, StackTrace st) {
      source._player?._playbackEventSubject.addError(e, st);
    }, onDone: () {
      completer.complete();
    });

    request.response.done.then((dynamic value) {
      subscription.cancel();
    });

    await completer.future;

    await request.response.close();        // it closes the response while audio is still playing since
  }                                                              //  it is called when the stream ends??? not sure..

  return handler;
}
...

you can see my full code below:

Minimal reproduction project
https://github.com/JakeSeo/just_audio

To Reproduce (i.e. user steps, not code)
Steps to reproduce the behavior:
run the example code of just_audio inside the repository above.

Error messages

  • if the stream is not a broadcast stream, it throws bad state error since the listener is registered two times.

Expected behavior
The audio is played until the end without being cut down.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: -
  • Browser -

Smartphone (please complete the following information):

  • Device: Galaxy S23 FE
  • OS: Android 14

Flutter SDK version

 % flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.16.9, on macOS 13.6.2 22G320 darwin-arm64, locale en-KR)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.3.1)
[✓] Android Studio (version 2021.2)
[✓] VS Code (version 1.86.2)
[✓] Connected device (1 available)
[✓] Network resources

Additional context

@JakeSeo JakeSeo added 1 backlog bug Something isn't working labels Feb 22, 2024
@ryanheise
Copy link
Owner

See the instructions below:

Minimal reproduction project
Provide a link here using one of two options:

  1. Fork this repository and modify the example to reproduce the bug, then provide a link here.
  2. If the unmodified official example already reproduces the bug, just write "The example".

@JakeSeo
Copy link
Author

JakeSeo commented Feb 23, 2024

I updated the issue. What I'm actually trying to do is request tts from Azure Open AI and the last part of the result is always cut. I gave you my full code, I will be disposing the api-key once this issue will be resolved. Help me please. thank you..

@ryanheise
Copy link
Owner

Before looking into your hypothesis, I am just looking at your implementation of StreamSource:

class StreamSource extends StreamAudioSource {
  StreamSource(this.stream);

  final Stream<List<int>> stream;

  @override
  Future<StreamAudioResponse> request([int? start, int? end]) async {
    return StreamAudioResponse(
      sourceLength: null,
      contentLength: null,
      offset: null,
      stream: stream,
      contentType: 'audio/mpeg',
      rangeRequestsSupported: false,
    );
  }
}

An implementation should normally handle the case where multiple requests are made.

I see in your bug report that you are mystified about why multiple requests would be made, but those requests are outside my control, they originate from the native player module (in this case, ExoPlayer). I also can't say I know why ExoPlayer is making multiple requests (particularly since you disabled range requests), but debug printing out the request headers might answer those questions.

I am considering between whether that's just part of the mysterious behaviour of ExoPlayer which you need to adapt to, or whether somewhere in my code I am ignoring your request to disable range requests.

@JakeSeo
Copy link
Author

JakeSeo commented Feb 23, 2024

I just feel that multiple requests is the problem but I'm not sure if it actually is.

The actual problem is that the audio is not being played until the end. Few of the last chunks of the stream are not being played. If I may refer to my example code, I request Text to Speech request with the text that ends with "..... fine-tune custom models.", the audio is only played until "..... fine-tune custom mod"(this part is always different, sometimes it plays until the end, sometimes it even ends before this). I feel that this certainly is a bug/issue that needs to be fixed if my implementation is not wrong.

I would be really grateful if you could look into this. I will also be playing around the library and comment if I find anything. Thank you so much!

@ryanheise
Copy link
Owner

I will be disposing the api-key once this issue will be resolved.

Can you reproduce this by downloading the response beforehand, saving it to a file, putting it in the assets directory and changing your stream audio source to read from that file stream?

@JakeSeo
Copy link
Author

JakeSeo commented Feb 23, 2024

I haven't tried, but i think it will work fine if i download the file. I tested this in Postman and it was ok.

@JakeSeo
Copy link
Author

JakeSeo commented Feb 27, 2024

In, iOS, the first few parts are lost.

@yadaniyil
Copy link

Same for me. On iOS audio playback skips the first 3-4 words. @JakeSeo did you find the solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1 backlog bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants