Skip to content

[Bug] Streaming Audio takes too long to load and stream #575

Closed as not planned
Closed as not planned

Description

@dsilva-adastra

Describe the bug

Hello everyone,

I have an issue with streaming audio with Readium. I have an audio file that takes 30 seconds to load the Publication object and another 20 seconds to start streaming after calling the play method.

I don't know if I am doing something wrong.
I attached a sample project that reproduces the issue
I am using Swift 6

Any help is appreciated. Thank you

How to reproduce?

  1. Open the Sample project
  2. Run it on a simulator or device of your choice (running in the preview also works)
  3. You have to wait for 30 seconds until you can play the file
  4. You have to wait at least 20 seconds until the playback starts

Readium version

3.2.0

OS version

iOS 17.6+

Testing device

Any device of emulator

Environment

macOS: 15.4
platform: arm64
Xcode 16.3
Build version 16E140

Additional context

ReadiumStreaming.zip

Activity

  1. mickael-menu commented on Apr 23, 2025

    @mickael-menu
    Member

    The URL in the project you shared does not seem to be available anymore (the server timed out)

    https://davidjares.eu/wp-content/uploads/2025/04/01-Prolog-Cesta-z-ni%CC%81z%CC%8C-neni%CC%81-na%CC%81vratu.mp3

    It might be a server issue if your server is too slow to return the bytes. Otherwise I think it might be a duplicate of this other issue: #579

  2. dsilva-adastra commented on Apr 23, 2025

    @dsilva-adastra
    Author

    @mickael-menu thank you for your reply. I don't know, maybe the server was down at that time, the link works for me, and it's part of a website. It's an audio of more than one hour of playback, which takes a lot of time to start.

    I implement my AVPlayer logic, and the playback begins immediately. Strangely, Readium takes so long to load the file and then play it. Even for smaller audio files like this Audio File, it still takes a few seconds to load, whereas when I use the AVPlayer directly, it's instantaneously.

    I did a little debugging and the open publication code is stuck for 30 seconds (with the original audio) in this method private func makeReadingOrder(for container: Container) async -> Result<[(AnyURL, Format)], PublicationParseError> after that it's considered loaded, but it still takes several seconds to play.

    Thank you again for your reply and I will check the other issue.

  3. mickael-menu commented on Apr 25, 2025

    @mickael-menu
    Member

    Thank you for investigating, that's really helpful. I will try to take a look into the makeReadingOrder code next week.

  4. added theissue type on Apr 25, 2025
  5. dsilva-adastra commented on Apr 28, 2025

    @dsilva-adastra
    Author

    @mickael-menu I think I figured out the issue.
    When retrieving the metadata from a remote resource using this line let asset = AVURLAsset(url: url, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true]) takes a long time to load since we are requesting the precise audio duration.

    Based on the Apple documentation, the default value should be enough, but I am unsure since I don't know the entire implementation.

    Setting a value of true indicates longer loading times are acceptable in cases where you require precise timing. Container formats like QuickTime and MPEG-4 provide sufficient timing information and don’t require additional parsing to retrieve it. Other formats don’t provide sufficient summary information, and the system can’t accurately calculate the resource’s duration and timing without examining the media content.

    If you only intend to play the asset, the default value of false is sufficient because AVPlayer supports approximate random access by time when full precision isn’t available.

    Removing that option makes the metadata load much faster. I believe that when a remote asset is loaded, the code shouldn't block the execution for the metadata to be fully loaded before the audio is ready to play. The audio can be played immediately, and the metadata can be loaded in the background. I am not sure how to achieve that at the moment since there are many parts of the code relying on the metadata.

  6. mickael-menu commented on Apr 30, 2025

    @mickael-menu
    Member

    Thank you for your on-going investigation on this issue. I took a look today and have some observations:

    • There is definitely something wrong with the URL https://davidjares.eu/wp-content/uploads/2025/04/01-Prolog-Cesta-z-ni%CC%81z%CC%8C-neni%CC%81-na%CC%81vratu.mp3. Now it works, but it takes ~30 secondes to answer to a HEAD request from Swift (interestingly a GET request is much faster). From my benchmarking this is the reason it takes a while to open a publication with this URL, it gets stuck in the DefaultHTTPClient in the receiveChallenge step. You can test it with this script to be run with swift script.swift:

      import Foundation
      
      let url = URL(string: "https://davidjares.eu/wp-content/uploads/2025/04/01-Prolog-Cesta-z-ni%CC%81z%CC%8C-neni%CC%81-na%CC%81vratu.mp3")!
      
      let group = DispatchGroup()
      group.enter()
      
      var request = URLRequest(url: url)
      request.httpMethod = "HEAD"
      
      let task = URLSession.shared.dataTask(with: request) { data, response, error in
          defer { group.leave() }
      
          if let error = error {
              print(error)
              return
          }
      
          let httpResponse = response as! HTTPURLResponse
          print("Status Code: \(httpResponse.statusCode)")
          print("Headers: \(httpResponse.allHeaderFields)")
      }
      
      task.resume()
      
      group.wait()
    • The second URL you shared opens quickly for me, under a second (https://github.com/dimitris-c/sample-audio/raw/main/bensound-jazzyfrenchy-optimized.m4a). It might be slower than using AVPlayer because Readium has to sniff the format of the URL you provide to the AssetRetriever to know what type of publication it is. If you already know the format, you can pass it to the AssetRetriever to skip a bunch of HTTP requests.

      let format = Format(specifications: .mp4, mediaType: .mp4, fileExtension: "mp4")
      let asset = try await assetRetriever.retrieve(url: url, format: format).get()
    • For your last suggestion regarding AVURLAssetPreferPreciseDurationAndTimingKey I'm really confused. It should not be called when streaming a remote HTTP URL, as you can see here (the fileURL):

      This is what I observed when running the test using the URLs you provided. We do need the precise duration because this code fills in the metadata in the Link objects. If you don't need it, you can provide a different implementation of the AudioPublicationManifestAugmentor when creating an AudioParser.

    • I took a look at your original code and I'm confusing with why you are creating a Container yourself from a single URL:

      let container = CompositeContainer(
          HTTPContainer(
              client: readium.httpClient,
              entries: Set(urls.map { $0 })
          )
      )
      let asset = try await readium.assetRetriever.retrieve(
          container: container
      ).get()

      If you have a single resource (e.g. audio file), you don't need to create a Container, you can open the URL directly with the AssetRetriever. It knows how to load HTTP URLs if you provided an HTTPClient when creating the AssetRetriever.

    Let me know if I'm missing something and you still think there is something wrong with Readium, I'll reopen the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions