Search code examples
swiftreact-nativeaudioavplayeravmutablecomposition

AVMutableComposition goes silent every once in a while


I'm trying to create a an audio loop in Swift and it works very well 9/10 times. But then suddenly, the 10th time (or so) the insertTimeRange function seems to fail in some way.

I can see that the player has the correct length, but instead of taking the full 60 seconds of audio from every loop, it just seems to take a very, very short part of it and loop every minute. Illustration of the problem with five loops:

.----------.----------.----------.----------.----------

(short but hearable audio = ., complete silence = -)

Unfortunately, it doesn't throw any error. Here's how I create the composition:

  private func createComposition(audioURL: URL, minutesToLoop: UInt) -> AVMutableComposition? {

// Initiate new composition
let composition = AVMutableComposition()

// Add one audio track (channel) to the composition
let compositionAudioTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: 0)

// Create AVAsset from audio file (mp3 -> React Native bundle string -> URL -> AVAsset)
let asset = AVURLAsset(url: audioURL)

// Extract (now compatible) audio track from AVAsset
let track = asset.tracks(withMediaType: AVMediaType.audio)[0]

// Create a time range from 0 - 60 seconds that we can use to cut that out from the track
let timeRange = CMTimeRangeMake(start: .zero, duration: CMTimeMakeWithSeconds(60, preferredTimescale: 600))

if (compositionAudioTrack != nil) {
  // Repeat for as many minutes as user specified in the app
  
  for _ in 0...(minutesToLoop - 1) {
    do {
      // Take first 60 seconds from the audio file (timeRange, of: track) and paste over and over again exactly at the end of the track (at: composition.duration)
      try compositionAudioTrack!.insertTimeRange(timeRange, of: track, at: composition.duration)
      
    } catch {
      print("FAILED TO MERGE AUDIO")
      return nil
    }
  }
}
return composition

}


Solution

  • For future AVMutableComposition lovers, I found the solution. You needed to add a boolean to your asset options, like so:

    let asset = AVURLAsset(url: audioURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])