Sending and playing an audio file from Apple Watch Extension to iOS

Watch Image

I recently played around recording audio on my Apple Watch. Doing that was easy enough, and I wanted to send the recorded file from the watch extension to the iOS application – and play it. I started messing around and used sendFile as the communication protocol since it can work in the background (without the iOS app needing to be open). I banged on it a ton and it worked – but I couldn’t figure out a way to actually play it. The file.fileURL.path had some dynamic stuff in the URL which prevented me from successfully creating a valid AVAudioPlayer.

Here is my solution in sending a file (wav) to the iPhone and playing it upon receipt.

//Watch Extension code

override func awake(withContext context: Any?) {
    super.awake(withContext: context)
        
    if WCSession.isSupported() {
        WCSession.default().delegate = self
        WCSession.default().activate()
    }
        
    let fileManager = FileManager.default
    let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.net.ericd.WatchRecord")
    let fileName = "audioFile.wav"
    // class variable
    saveURL = container?.appendingPathComponent(fileName) as NSURL?    
}

@IBAction func sendAudio() {
    let data = NSData(contentsOf: saveURL as! URL)
    sendAudioFile(file: data!) // Quicker.
}

func sendAudioFile(file: NSData) {
    WCSession.default().sendMessageData(file as Data, replyHandler: { (data) -> Void in
        // handle the response from the device
    }) { (error) -> Void in
        print("error: \(error.localizedDescription)")
    }
}

The presentAudioRecorderController, when successfully saving the audio file recorded, enables a button that calls the sendAudio function. That code is here.

@IBAction func recordAudio() {
    let duration = TimeInterval(10)
    let recordingOptions = [WKAudioRecorderControllerOptionsMaximumDurationKey: duration]
    print("record:", saveURL as! URL)
    presentAudioRecorderController(withOutputURL: saveURL as! URL,
                                                    preset: .narrowBandSpeech,
                                                    options: recordingOptions,
                                                    completion: { saved, error in
                                                        
                                                    if let err = error {
                                                        print(err.localizedDescription)
                                                    }
                                                        
                                                    if saved {
                                                        print("saved file.")
                                                        self.playButton.setAlpha(1.0)
                                                        self.sendButton.setAlpha(1.0)
                                                        self.playButton.setEnabled(true)
                                                        self.sendButton.setEnabled(true)
                                                    }
    })
}

Now, in the iOS application, I handle the receipt of the sendMessageData method.

func session(_ session: WCSession, didReceiveMessageData messageData: Data, replyHandler: @escaping (Data) -> Void)
{
    DispatchQueue.main.async
    {
        self.someLabel.text = "We got an audio file: \(messageData)" //Show bytes
        self.versionLabel.textColor = UIColor.blue
            
        do {
            self.player = try AVAudioPlayer(data: messageData)
            guard self.player != nil else { return }
            self.player?.prepareToPlay()
            self.player?.play()
                
        } catch let error as NSError {
            print(error.localizedDescription)
        }
    }
}

And there the audio file is rendered upon receipt. Not using a file transfer (which I would prefer since it’s a background task that doesn’t require the iOS application to be running in the foreground).

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.