Audio Recording in watchOS Tutorial

Soheil Azarpour

This is an abridged chapter from our book watchOS by Tutorials, which has been completely updated for Swift 4 and watchOS 4. This tutorial is presented as part of our iOS 11 Launch Party — enjoy!

In watchOS 2, Apple introduced a new API to play and record multimedia files on the Apple Watch. In watchOS 4, Apple greatly improved the multimedia API and created great opportunities to build innovative apps and enhance the user experience.

In this tutorial, you’ll learn about watchOS 4’s audio recording and playback APIs and how to use them in your apps. You’ll add audio recording to a memo app so that users can record and review their thoughts and experiences right from their wrists. Let’s get started!

Getting Started

Download the starter project for the tutorial here.

The starter project you’ll use in this tutorial is called TurboMemo. Open TurboMemo.xcodeproj in Xcode and make sure the TurboMemo scheme for iPhone is selected. Build and run in the iPhone simulator, and you’ll see the following screen:

Users can record audio diaries by simply tapping the plus (+) button. The app sorts the entries by date, and users can play back an entry by tapping it.

Try adding some entries to create some initial data.

Now, stop the app and change the scheme to TurboMemoWatch. Build and run in the Watch Simulator, and you’ll see the following screen:

The Watch app syncs with the iPhone app to display the same entries, but it doesn’t do anything else yet. You’re about to change that.

Note: TurboMemo uses Watch Connectivity, which is covered in depth in Chapter 16 and Chapter 19.

Audio Playback

There are two ways you can play an audio file in watchOS. You can either use the built-in media player, or build your own. You’ll start with the built-in media player as it’s simpler and more straightforward. In the next section, you’ll build your own media player.

The easiest way to play a media file is to present the built-in media player controller using the presentMediaPlayerController(with:options:completion:) method of WKInterfaceController. All you have to do is to pass in a file URL that corresponds to the index of the row selected by the user in WKInterfaceTable.

Open TurboMemoWatchExtension/InterfaceController.swift, find the implementation of table(_:, didSelectRowAt:) and update it as follows:

// 1
let memo = memos[rowIndex]
// 2
presentMediaPlayerController(
  with: memo.url,
  options: nil,
  completion: {_,_,_ in })

Going through this step-by-step:

  1. You get the selected memo by passing the selected row index to the array of memos.
  2. You present a media player controller by calling presentMediaPlayerController(with:options:completion:) and passing in the URL of the selected memo. You can optionally pass in a dictionary of playback options. Since you don’t want any particular customization at this point, you pass nil. In the completion block, you can check playback results based on your specific needs. Because the API requires a non-nil completion block, you simply provide an empty block.

That’s it! Build and run the app. Tap on a row in the table and you can now listen to the memos!

Note: To learn more about playback options and playing video files, check out Chapter 21: Handoff Video Playback.

Building an Audio Player

The media player controller in watchOS is great for playing short media files but it comes with limitations: As soon as the user dismisses the player, playback stops. This can be a problem if the user is listening to a long audio memo, and you want to continue playing the file even when the user closes the media player.

The built-in media interface can’t be customized either. So if you want more control over the playback and appearance of the media player, you need to build your own.

You’ll use WKAudioFilePlayer to play long audio files. WKAudioFilePlayer gives you more control over playback and the rate of playback. However, you’re responsible for providing an interface and building your own UI.

Note: Apps can play audio content using WKAudioFilePlayer only through a connected Bluetooth headphone or speaker on a real device. You won’t be able to hear the audio using WKAudioFilePlayer either in watchOS simulator or via Apple Watch speaker. Therefore, to follow along with this section, you’ll need an Apple Watch that’s paired with Bluetooth headphones.

The starter project includes AudioPlayerInterfaceController. You’ll use AudioPlayerInterfaceController as a basis for your custom audio player. But before you go there, while you’re still in InterfaceController, you can rewire the code to call the AudioPlayerInterfaceController instead.

Once again, find the implementation of table(_:didSelectRowAtIndex:) in InterfaceController.swift, and update it as follows:

override func table(
  _ table: WKInterfaceTable,
  didSelectRowAt rowIndex: Int) {

    let memo = memos[rowIndex]
    presentController(
      withName: "AudioPlayerInterfaceController",
      context: memo)
}

Make sure you place the existing code entirely. Here, instead of using the built-in media player, you call your soon-to-be-made custom media player. If you build and run at this point, and select a memo entry from the table, you’ll see the new media player that does … nothing! Time to fix that.

Open AudioPlayerInterfaceController scene in TurboMemoWatch/Interface.storyboard. AudioPlayerInterfaceController provides a basic UI for audio playback.

This has:

  • titleLabel which is blank by default
  • playButton that’s hooked up to playButtonTapped().
  • a static label that says Time lapsed:.
  • interfaceTimer that is set to 0 by default.

Now, open AudioPlayerInterfaceController.swift and add the following properties at the beginning of AudioPlayerInterfaceController:

// 1
private var player: WKAudioFilePlayer!
// 2
private var asset: WKAudioFileAsset!
// 3
private var statusObserver: NSKeyValueObservation?
// 4
private var timer: Timer!

Taking this line-by-line:

  1. player is an instance of WKAudioFilePlayer. You’ll use it to play back an audio file.
  2. asset is a representation of the voice memo. You’ll use this to create a new WKAudioFilePlayerItem to play the audio file.
  3. statusObserver is your key-value observer for the player’s status. You’ll need to observer the status of the player and start playing only if the audio file is ready to play.
  4. timer that you use to update the UI. You kick off the timer at the same time you start playing. You do this because currently there’s no other way to know when you’re finished playing the audio file. You’ll have to maintain your own timer with the same duration as your audio file.

You’ll see all these in action in a moment.

Now, add the implementation of awakeWithContext(_:) to AudioPlayerInterfaceController as follows:

override func awake(withContext context: Any?) {
  super.awake(withContext: context)
  // 1
  let memo = context as! VoiceMemo
  // 2
  asset = WKAudioFileAsset(url: memo.url)
  // 3
  titleLabel.setText(memo.filename)
  // 4
  playButton.setEnabled(false)
}

Again, taking this line-by-line:

  1. After calling super as ordinary, you know for sure the context that’s being passed to the controller is a VoiceMemo. This is Design by Contract!
  2. Create a WKAudioFileAsset object with the voice memo and store it in asset. You’ll reuse the asset to replay the same memo when user taps on the play button.
  3. Set the titleLabel with the filename of the memo.
  4. Disable the playButton until the file is ready to be played.

You prepared the interface to playback an audio file, but you haven’t done anything to actually play it. You’ll kick off the playback in didAppear() so that playback starts when the interface is fully presented to the user.

Speaking of didAppear(), add the following to AudioPlayerInterfaceController:

override func didAppear() {
  super.didAppear()
  prepareToPlay()
}

Here, you simply call a convenient method, prepareToPlay(). So let’s add that next:

private func prepareToPlay() {
  // 1
  let playerItem = WKAudioFilePlayerItem(asset: asset)
  // 2
  player = WKAudioFilePlayer(playerItem: playerItem)
  // 3
  statusObserver = player.observe(
    \.status,
    changeHandler: { [weak self] (player, change) in
      // 4
      guard
        player.status == .readyToPlay,
        let duration = self?.asset.duration
        else { return }
      // 5
      let date = Date(timeIntervalSinceNow: duration)
      self?.interfaceTimer.setDate(date)
      // 6
      self?.playButton.setEnabled(false)
      // 7
      player.play()
      self?.interfaceTimer.start()
      // 8
      self?.timer = Timer.scheduledTimer(
        withTimeInterval: duration, 
        repeats: false, block: { _ in
        
        self?.playButton.setEnabled(true)
      })
  })
}

There’s a lot going on here:

  1. Create a WKAudioFilePlayerItem object from the asset you set earlier in awake(withContext:). You have to do this each time you want to play a media file, since WKAudioFilePlayerItem can’t be reused.
  2. Initialize the player with the WKAudioFilePlayerItem you just created. You’ll have to do this even if you’re playing the same file again.
  3. The player may not be ready to play the audio file immediately. You need to observe the status of the WKAudioFilePlayer object, and whenever it’s set to .readyToPlay, you can start the playback. You use the new Swift 4 key-value observation (KVO) API to listen to changes in player.status.
  4. In the observer block, you check for the player’s status and if it’s .readyToPlay, you safely unwrap duration of the asset and continue. Otherwise, you simply ignore the change notification.
  5. Once the item is ready to play, you create a Date object with the duration of the memo, and update interfaceTimer to show the lapsed time.
  6. Disable the playButton while you’re playing the file.
  7. Start playing by calling player.play(), and at the same time, start the countdown in the interface.
  8. Kick off an internal timer to re-enable the playButton after the playback is finished so the user can start it again if they wish.

That was a big chunk of code, but as you see, it’s mostly about maintaining the state of the WKAudioFilePlayer and keeping the interface in sync.

Note: Unfortunately, at the time of writing this tutorial, currentTime of WKAudioFilePlayerItem is not KVO-complaint so you can’t add an observer. Ideally, you would want to observe currentTime instead of maintaining a separate timer on your own.

Before you build and run, there’s one more thing to add!

When the timer is up and playButton is enabled, the user should be able to tap on Play to restart playing the same file. To implement this, find the implementation of playButtonTapped() in AudioPlayerInterfaceController.swift and update it as follows:

@IBAction func playButtonTapped() {
  prepareToPlay()
}

It’s that simple! Merely call the convenient method, prepareToPlay(), to restart the playback.

Next, build and run, and select a voice memo from the list. The app will present your custom interface. The interface will automatically start playing the audio file, and once it’s stopped, the Play button will be re-enabled and you can play it again.

If you have more than one item to play, such as in a playlist, you’ll want to use WKAudioFileQueuePlayer instead of WKAudioFilePlayer and queue your items. The system will play queued items back-to-back and provide a seamless transition between files.

Background Audio Playback

In watchOS, very much like in iOS, you can specify that your app should use background audio. This lets the system prepare to take over and continue playing the audio file if a user dismisses your media player.

To declare support for background audio, you’ll update the Info.plist for the Watch app. Open TurboMemoWatch\Info.plist, select the Information Property List entry and tap the + button:

Change the value of the new key to UIBackgroundModes. Make sure its type is Array and then expand the key and add a new value named audio. Xcode will most likely change the values to more readable versions:

Adding this key lets the Watch app continue running for the purpose of playing audio. If the key is not present, playback ends when the user stops interacting with your app.

Recording Audio

One of the most exciting features of watchOS is its access to the microphone. Being able to add a voice memo to Turbo Memo on the Apple Watch is definitely something users will appreciate — so let’s do it!

When you start recording, it’s the Watch app that does the recording and has access to the microphone. Prior to watchOS 4, the WatchKit extension had to provide a shared container using App Groups to which both could read and write, allowing the Watch app to write the audio and the WatchKit extension to grab it.

Even though the WatchKit extension code was bundled and copied to the Apple Watch along with the Watch app itself, from the system’s standpoint, they were still two separate processes that were sandboxed within their own containers. In other words, the Watch app and the WatchKit extension didn’t share the same sandbox!

New in watchOS 4, thanks to Unified Process Runtime, both the Watch app and the WatchKit extension run in the same process so they both have access to the same sandbox.

Note: The good news is that if you’re dropping support for watchOS versions prior to watchOS 4, you can simplify your code by removing the code related to communication between App Groups and your app’s container. The bad news is that if you want to have a backward-compatible watchOS app, you need to enable App Groups. To learn more about App Groups, check out the “Sharing Data with Your Containing App” section of Apple’s App Extension Programming Guide: apple.co/1I5YBtZ

The starter project includes a menu item called + Voice that’s accessible in the app’s main interface by force-touching the screen:

In code, it’s hooked up to addVoiceMemoMenuItemTapped() in InterfaceController.swift, and currently does … (surprise) nothing.

It’s time to tune up this code and do some recording.

Open InterfaceController.swift, find the empty implementation of addVoiceMemoMenuItemTapped() and update it as follows:

// 1
let outputURL = MemoFileNameHelper.newOutputURL()
// 2
let preset = WKAudioRecorderPreset.narrowBandSpeech
// 3
let options: [String : Any] =
  [WKAudioRecorderControllerOptionsMaximumDurationKey: 30]
// 4
presentAudioRecorderController(
  withOutputURL: outputURL,
  preset: preset,
  options: options) { 
    [weak self] (didSave: Bool, error: Error?) in
    
    // 5
    guard didSave else { return }
    self?.processRecordedAudio(at: outputURL)
}

This is the action method you’ll call when a user wants to add a new voice memo. Here’s what you’re doing:

  1. Create a new URL by calling MemoFileNameHelper.newOutputURL() which is a convenient helper module. All it does is that it generates a unique file name based on the current date and time, appends .m4a as the file extension to it, and creates a URL based on user’s documentDirectory on the current device — it’s a shared code between the iPhone and the Watch app. This is basically where you’ll save the audio file.
  2. Configure presets for the recorder. See below for more information on the presets you can use.
  3. Create an options dictionary to specify the maximum duration of the recording session. Here, it’s 30 seconds.
  4. Present the system-provided audio recording controller.
  5. In the completion block, if the audio file is successfully saved, you pass it on to a helper method, processRecordedAudio(at:) which will then broadcast it to the iPhone app and update your data source for the interface table.

When you present an audio recording controller, there are a number of things you can specify. First, the preset you select determines the sample and bit rates at which the audio will record:

  • NarrowBandSpeech: As its name implies, this is a good preset for voice memos and voice messages. It has a sample rate of 8 kHz, and it records at a bit rate of 24 kbps with an AAC codec and 128 kbps with an LPCM codec.
  • WideBandSpeech: This preset has a higher sample rate of 16 kHz, and it records at a bit rate of 32 kbps with an AAC codec and 256 kbps with an LPCM codec.
  • HighQualityAudio: This preset has the highest sample rate at 44.1 kHz, and it records at a bit rate of 96 kbps with an AAC codec and 705.6 kbps with an LPCM codec.

You can also specify various recording options:

  • WKAudioRecorderControllerOptionsMaximumDurationKey: You can set the maximum duration of recorded audio clips by passing in a TimeInterval value in seconds. There’s no maximum recording time if you don’t set a value for this key.
  • WKAudioRecorderControllerOptionsAlwaysShowActionTitleKey: You can use this key to pass either true or false to modify the behavior for showing the action button. If you specify false, the audio recorder controller shows the button only after the user has recorded some audio. By default, the action button is always visible.
  • WKAudioRecorderControllerOptionsActionTitleKey: You can use this key to pass in a String to customize the display title of the button that the user taps to accept a recording. By default, the button’s title is Save.
  • WKAudioRecorderControllerOptionsAutorecordKey: By passing a Boolean value for this key, you can change the automatic recording behavior of the audio recorder controller. If you set it to true, once the controller is presented, it automatically starts recording; otherwise, the user has to tap on the record button to start recording. The default value is true.

That’s it! Build and run. Bring up the contextual menu using the force touch gesture and tap on the + Voice button. The app will present you with an audio recording controller. Tap the Save button, and you’ll have recorded your first voice memo on an Apple Watch, using your own code!

If you try recording on a real device, the very first time you present the system-provided audio recording controller, watchOS will ask for the user’s permission.

Very much like in iOS, the user should grant access to the microphone on the Watch. However, unlike iOS, you don’t explicitly ask for user’s permission as there’s no API for that. Instead, the watchOS uses the NSMicrophoneUsageDescription key in the iPhone’s app to present the appropriate UI and ask the user for their permission. If user doesn’t grant access, the audio recorder will still work, but it will only record silence!

Note: At the time of writing this tutorial, the watchOS simulator doesn’t present the dialog asking for the user’s permission. The iPhone simulator, on the other hand, does present the permission dialog.

Where to Go From Here?

You can download the completed project for the tutorial here.

The audio recording and playback API of watchOS 4 makes it possible to deliver a smooth multimedia experience on the Apple Watch even when the paired iPhone isn’t in proximity. This is a technology with endless possibilities.

If you enjoyed what you learned in this tutorial, why not check out the complete watchOS by Tutorials book, available in our store?

Here’s a taste of what’s in the book:

Chapter 1, Hello, Apple Watch!: Dive straight in and build your first watchOS 4 app — a very modern twist on the age-old “Hello, world!” app.

Chapter 2, Designing Great Watch Apps: Talks about the best practices based on Apple recommendations in WWDC this year, and how to design a Watch app that meets these criteria.

Chapter 3, Architecture: watchOS 4 might support native apps, but they still have an unusual architecture. This chapter will teach you everything you need to know about this unique aspect of watch apps.

Chapter 4, UI Controls: There’s not a `UIView` to be found! In this chapter you’ll dig into the suite of interface objects that ship with WatchKit–watchOS’ user interface framework.

Chapter 5, Pickers: `WKInterfacePicker` is one of the programmatic ways to work with the Digital Crown. You’ll learn how to set one up, what the different visual modes are, and how to respond to the user interacting with the Digital Crown via the picker.

Chapter 6, Layout: Auto Layout? Nope. Springs and Struts then? Nope. Guess again. Get an overview of the layout system you’ll use to build the interfaces for your watchOS apps.

Chapter 7, Tables: Tables are the staple ingredient of almost any watchOS app. Learn how to set them up, how to populate them with data, and just how much they differ from `UITableView`.

Chapter 8, Navigation: You’ll learn about the different modes of navigation available on watchOS, as well as how to combine them.

Chapter 9, Digital Crown and Gesture Recognizers: You’ll learn about accessing Digital Crown raw data, and adding various gesture recognizers to your watchOS app interface.

Chapter 10, Snapshot API: Glances are out, and the Dock is in! You’ll learn about the Snapshot API to make sure that the content displayed is always up-to-date.

Chapter 11, Networking: `NSURLSession`, meet Apple Watch. That’s right, you can now make network calls directly from the watch, and this chapter will show you the ins and outs of doing just that.

Chapter 12, Animation: The way you animate your interfaces has changed with watchOS 3, with the introduction of a single, `UIView`-like animation method. You’ll learn everything you need to know about both animated image sequences and the new API in this chapter.

Chapter 13, CloudKit: You’ll learn how to keep the watch and phone data in sync even when the phone isn’t around, as long as user is on a known WiFi network.

Chapter 14, Notifications: watchOS offers support for several different types of notifications, and allows you to customize them to the individual needs of your watch app. In this chapter, you’ll get the complete overview.

Chapter 15, Complications: Complications are small elements that appear on the user’s selected watch face and provide quick access to frequently used data from within your app. This chapter will walk you through the process of setting up your first complication, along with introducing each of the complication families and their corresponding layout templates.

Chapter 16, Watch Connectivity: With the introduction of native apps, the way the watch app and companion iOS app share data has fundamentally changed. Out are App Groups, and in is the Watch Connectivity framework. In this chapter you’ll learn the basics of setting up device-to-device communication between the Apple Watch and the paired iPhone.

Chapter 17, Audio Recording: As a developer, you can now record audio directly on the Apple Watch inline in your apps, without relying on the old-style system form sheets. In this chapter, you’ll gain a solid understanding of how to implement this, as well as learn about some of the idiosyncrasies of the APIs, which are related to the unique architecture of a watch app.

Chapter 18, Interactive Animations with SpriteKit and SceneKit: You’ll learn how to apply SpriteKit and SceneKit in your Watch apps, and how to create interactive animations of your own.

Chapter 19, Advanced Watch Connectivity: In Chapter 15, you learned how to set up a Watch Connectivity session and update the application context. In this chapter, you’ll take a look at some of the other features of the framework, such as background transfers and real-time messaging.

Chapter 20, Advanced Complications: Now that you know how to create a basic complication, this chapter will walk you through adding Time Travel support, as well giving you the lowdown on how to efficiently update the data presented by your complication.

Chapter 21, Handoff Video Playback: Want to allow your watch app users to begin a task on their watch and then continue it on their iPhone? Sure you do, and this chapter will show exactly how to do that through the use of Handoff.

Chapter 22, Core Motion: The Apple Watch doesn’t have every sensor the iPhone does, but you can access what is available via the Core Motion framework. In this chapter, you’ll learn how to set up Core Motion, how to request authorization, and how to use the framework to track the user’s steps.

Chapter 23, HealthKit: The HealthKit framework allows you to access much of the data stored in user’s health store, including their heart rate! This chapter will walk you through incorporating HealthKit into your watch app, from managing authorization to recording a workout session.

Chapter 24, Core Location: A lot of apps are now location aware, but in order to provide this functionality you need access to the user’s location. Developers now have exactly that via the Core Location framework. Learn everything you need to know about using the framework on the watch in this chapter.

Chapter 25, Core Bluetooth: In watchOS 4, you can pair and interact with BLE devices directly. Learn how to send control instructions and other data directly over Bluetooth.

Chapter 26, Localization: Learn how to expand your reach and grow a truly international audience by localizing your watch app using the tools and APIs provided by Apple.

Chapter 27, Accessibility: You want as many people as possible to enjoy your watch app, right? Learn all about the assistive technologies available in watchOS, such as VoiceOver and Dynamic Type, so you can make your app just as enjoyable for those with disabilities as it is for those without.

One thing you can count on: after reading this book you’ll have all the experience necessary to build rich and engaging apps for the Apple Watch platform.

And to help sweeten the deal, the digital edition of the book is on sale for $49.99! But don’t wait — this sale price is only available for a limited time.

Speaking of sweet deals, be sure to check out the great prizes we’re giving away this year with the iOS 11 Launch Party, including over $9,000 in giveaways!

To enter, simply retweet this post using the #ios11launchparty hashtag by using the button below:


We hope you enjoy this update, and stay tuned for more book releases and updates!

Soheil Azarpour

My name is Soheil Azarpour and I'm an engineer, developer, author, creator, husband and father. I enjoy bicycling, boating and playing piano. I live in Manchester, NH. I create iOS apps professionally and independently. You can find me on Twitter, GitHub, Stack Overflow and LinkedIn.

Other Items of Interest

Black Friday Sale

50% OFF All Swift & iOS Books

Ends in…

0
:
0
:
0

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

iOS Team

... 78 total!

Android Team

... 27 total!

Unity Team

... 12 total!

Articles Team

... 15 total!

Resident Authors Team

... 20 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!