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.
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:
- You get the selected memo by passing the selected row index to the array of memos.
-
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 passnil
. 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!
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.
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 toplayButtonTapped()
. -
a static label that says
Time lapsed:
. -
interfaceTimer
that is set to0
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:
-
player
is an instance ofWKAudioFilePlayer
. You’ll use it to play back an audio file. -
asset
is a representation of the voice memo. You’ll use this to create a newWKAudioFilePlayerItem
to play the audio file. -
statusObserver
is your key-value observer for the player’sstatus
. You’ll need to observer thestatus
of theplayer
and start playing only if the audio file is ready to play. -
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:
-
After calling
super
as ordinary, you know for sure thecontext
that’s being passed to the controller is aVoiceMemo
. This is Design by Contract! -
Create a
WKAudioFileAsset
object with the voice memo and store it inasset
. You’ll reuse the asset to replay the same memo when user taps on the play button. -
Set the
titleLabel
with the filename of the memo. -
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:
-
Create a
WKAudioFilePlayerItem
object from theasset
you set earlier inawake(withContext:)
. You have to do this each time you want to play a media file, sinceWKAudioFilePlayerItem
can’t be reused. -
Initialize the
player
with theWKAudioFilePlayerItem
you just created. You’ll have to do this even if you’re playing the same file again. -
The
player
may not be ready to play the audio file immediately. You need to observe thestatus
of theWKAudioFilePlayer
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 inplayer.status
. -
In the observer block, you check for the player’s
status
and if it’s.readyToPlay
, you safely unwrapduration
of theasset
and continue. Otherwise, you simply ignore the change notification. -
Once the item is ready to play, you create a
Date
object with the duration of the memo, and updateinterfaceTimer
to show the lapsed time. -
Disable the
playButton
while you’re playing the file. -
Start playing by calling
player.play()
, and at the same time, start the countdown in the interface. -
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.
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.
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:
-
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’sdocumentDirectory
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. - Configure presets for the recorder. See below for more information on the presets you can use.
-
Create an
options
dictionary to specify the maximum duration of the recording session. Here, it’s 30 seconds. - Present the system-provided audio recording controller.
-
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 aTimeInterval
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 eithertrue
orfalse
to modify the behavior for showing the action button. If you specifyfalse
, 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 aString
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 totrue
, 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 istrue
.
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!
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!
The post Audio Recording in watchOS Tutorial appeared first on Ray Wenderlich.