Share ringtone to Garageband via UIActivityViewController

I am implementing an ringtone application and I would like to share the audio file to GarageBand to let users continue the process,

Here is the code on how I share the media

 fileprivate func shareMedia(url: URL) {
       let activityViewController:UIActivityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
       activityViewController.popoverPresentationController?.sourceView = saveAsRingtone
       activityViewController.popoverPresentationController?.sourceRect = saveAsRingtone.frame
       present(activityViewController, animated: true, completion: nil)
   }

To Answer my own question here is the full answer

class AudioUtils {
class func trimAudio(audioToTrim: URL, startTime: Double, setExportSession: (AVAssetExportSession) -> Void, completion: @escaping (URL?, String?) -> Void) {
    let audioFileInput = audioToTrim
    let mixedAudio: String = "ringtone.aif"
    let exportPathDirectory = NSTemporaryDirectory()
    //        let audioFileDirectory = URL(fileURLWithPath: exportPathDirectory).appendingPathComponent("Project").appendingPathExtension("band")
    
    let projectBandDirectory = exportPathDirectory + "Project.band"
    if (FileManager.default.fileExists(atPath: projectBandDirectory)) {
        try! FileManager.default.removeItem(at: URL(fileURLWithPath: projectBandDirectory))
    }
    
    try! FileManager.default.createDirectory(atPath: projectBandDirectory, withIntermediateDirectories: true, attributes: nil)
    let exportPath: String = projectBandDirectory + "/"
    
    let bundlePath = Bundle.main.path(forResource: "projectData", ofType: "")
    let fullDestPath = NSURL(fileURLWithPath: exportPath).appendingPathComponent("projectData")
    let fullDestPathString = (fullDestPath?.path)!
    
    
    
    try! FileManager.default.createDirectory(atPath: exportPath + "Media", withIntermediateDirectories: true, attributes: nil)
    try! FileManager.default.createDirectory(atPath: exportPath + "Output", withIntermediateDirectories: true, attributes: nil)
    
    let audioFileOutput = URL(fileURLWithPath: exportPathDirectory + mixedAudio)//.appendingPathExtension("band")
    print("Will export to \(audioFileOutput.absoluteString)")
    
    try? FileManager.default.removeItem(at: audioFileOutput)
    
    let asset = AVAsset(url: audioFileInput)
    
    let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough)
    
    if (exportSession == nil) {
        completion(nil, "ExportSession is nil")
        return
    }
    setExportSession(exportSession!)
    let startCMTime = CMTimeMakeWithSeconds(startTime, preferredTimescale: 1)
    let stopCMtime = CMTimeMakeWithSeconds(startTime + 30, preferredTimescale: 1)
    let exportTimeRange = CMTimeRangeFromTimeToTime(start: startCMTime, end: stopCMtime)
    
    exportSession?.outputURL = audioFileOutput
    exportSession?.outputFileType = AVFileType.caf
    exportSession?.timeRange = exportTimeRange
    
    exportSession?.exportAsynchronously {
        switch (exportSession?.status) {
        case .completed:
            var options = AKConverter.Options()
            // any options left nil will assume the value of the input file
            options.format = "aif"
            options.sampleRate = 48000
            options.bitDepth = 24
            
            
            let sourceUrl = audioFileOutput
            let destUrl = URL(fileURLWithPath: exportPath + "Media/ringtone.aiff")
            convertAudio(sourceUrl, outputURL: destUrl)
            
            let fileData = try! Data.init(contentsOf: destUrl)
            var fileStream:String = fileData.base64EncodedString(options: NSData.Base64EncodingOptions.init(rawValue: 0))
            fileStream = String(fileStream.dropLast())
            
            let playersDictionary = NSMutableDictionary(contentsOfFile: bundlePath!)
            
            let playersNamesArray = (playersDictionary?.object(forKey: "$objects"))! as! NSMutableArray
            
            let nsDataParentDictionary = (playersNamesArray.object(at: 4)) as! NSMutableDictionary
            
            nsDataParentDictionary.setValue(fileStream, forKey: "NS.data")
            playersNamesArray.removeObject(at: 4)
            playersNamesArray.insert(nsDataParentDictionary, at: 4)
            playersDictionary?.setValue(playersNamesArray, forKey: "$objects")
            playersDictionary?.write(toFile: fullDestPathString, atomically: true)
            do {
                try FileManager.default.copyItem(atPath: bundlePath!, toPath: fullDestPathString)
                completion(URL(fileURLWithPath: exportPath), nil)
            } catch let exception {
                completion(nil, exception.localizedDescription)
            }
            break
        default:
            completion(nil, exportSession?.error?.localizedDescription)
            break
        }
    }
}

class func convertAudio(_ url: URL, outputURL: URL) {
    var error : OSStatus = noErr
    var destinationFile : ExtAudioFileRef? = nil
    var sourceFile : ExtAudioFileRef? = nil
    
    var srcFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
    var dstFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
    
    ExtAudioFileOpenURL(url as CFURL, &sourceFile)
    
    var thePropertySize: UInt32 = UInt32(MemoryLayout.stride(ofValue: srcFormat))
    
    ExtAudioFileGetProperty(sourceFile!,
                            kExtAudioFileProperty_FileDataFormat,
                            &thePropertySize, &srcFormat)
    
    dstFormat.mSampleRate = 44100  //Set sample rate
    dstFormat.mFormatID = kAudioFormatLinearPCM
    dstFormat.mChannelsPerFrame = 1
    dstFormat.mBitsPerChannel = 16
    dstFormat.mBytesPerPacket = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mFramesPerPacket = 1
    dstFormat.mFormatFlags = kAudioFormatFlagIsBigEndian |
    kAudioFormatFlagIsSignedInteger
    
    // Create destination file
    error = ExtAudioFileCreateWithURL(
        outputURL as CFURL,
        kAudioFileAIFFType,
        &dstFormat,
        nil,
        AudioFileFlags.eraseFile.rawValue,
        &destinationFile)
    print("Error = \(error)")
    
    error = ExtAudioFileSetProperty(sourceFile!,
                                    kExtAudioFileProperty_ClientDataFormat,
                                    thePropertySize,
                                    &dstFormat)
    print("Error = \(error)")
    
    error = ExtAudioFileSetProperty(destinationFile!,
                                    kExtAudioFileProperty_ClientDataFormat,
                                    thePropertySize,
                                    &dstFormat)
    print("Error = \(error)")
    
    let bufferByteSize : UInt32 = 32768
    var srcBuffer = [UInt8](repeating: 0, count: 32768)
    var sourceFrameOffset : ULONG = 0
    
    while(true){
        var fillBufList = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: 2,
                mDataByteSize: UInt32(srcBuffer.count),
                mData: &srcBuffer
            )
        )
        var numFrames : UInt32 = 0
        
        if(dstFormat.mBytesPerFrame > 0){
            numFrames = bufferByteSize / dstFormat.mBytesPerFrame
        }
        
        error = ExtAudioFileRead(sourceFile!, &numFrames, &fillBufList)
        print("Error = \(error)")
        
        if(numFrames == 0){
            error = noErr;
            break;
        }
        
        sourceFrameOffset += numFrames
        error = ExtAudioFileWrite(destinationFile!, numFrames, &fillBufList)
        print("Error = \(error)")
    }
    
    error = ExtAudioFileDispose(destinationFile!)
    print("Error = \(error)")
    error = ExtAudioFileDispose(sourceFile!)
    print("Error = \(error)")
}
}

What happens here?... First of all here is a function to trim the audio for 30 seconds

I have checked an existing .band project file and I got its components, and tried to create these files programmatically. Please feel free to ask me anything about the code

There is projectData file that you should use, this is a file found in every .garageBand folder https://www.dropbox.com/s/7r7uh2ekzigyy3u/projectData?dl=0


Solution 1:

There's no problem with this method for sharing a track on Garageband

Just Make sure that your shared file Extension is "band"

.appendingPathExtension("band") not anything else