[ Music ]
[ Applause ]
My name is Neil Desai, and I'm a watchOS Frameworks engineer, and I'm really excited to be here to talk about how to create some great audio apps for watchOS.
And honestly, we've been waiting a little over a year to give this talk, so I'm really happy to be here, so let's just jump right in.
In watchOS 4, we unveiled a revamped music and radio app.
And in watchOS 5, we unveiled a brand-new podcast app.
And we're finding that users absolutely love being able to play audio on the go or in the middle of a workout.
And we're also finding that users really like to be able to remote control some playback that's occurring on another device.
For example, your iPhone or HomePod.
And I especially really like this feature.
I love just being able to play some music out of my iPhone and then being able to control the volume, for instance, directly with my watch.
That's a great feature.
And today, with watchOS 5, we're going to show you how to build a great experience like you saw on the keynote like Audible was able to make and just like Pandora was able to make.
And so they were able to use some of the new tools available in watchOS 5.
And today, we're going to talk about how we can do the same sort of thing.
And to do that, we're going to talk about four major themes.
We're going to talk about our native controls and how we can now embed our system controls directly within our application.
We're going to talk about, how do we get content over to the watch?
And then, once we have that content, how do we actually play something back for our user?
And lastly, we're going to go over the whole audio experience and how to build a really rich experience for our users that integrates well throughout the entire system.
So let's talk about our native controls.
In watchOS 5, we're unveiling two new controls that you can embed directly within your application.
And the first is a Now Playing view which you can embed within your watchOS app.
And this is really great for content that may not be yours, for instance.
And you can also embed a volume control so you can build your own custom UI to allow your user to control the volume.
And you might be wondering for that first view, that Now Playing view, when exactly would I ever want to allow my user to control some audio, but the content's not exactly mine?
Well, let me give you an example.
We do this ourselves in the workout app.
So I'm in the middle of a HIIT workout.
I swipe to the left.
I get my power song, "Eye of the Tiger," and I'm a little bit of a dork, so I actually do listen to this song sometimes when I work out.
I love it.
And this is a great experience.
You just swipe over, and you can allow your user to control the playback.
And so if you're building a workout app, and you can do this, build the same sort of experience, just embedding the Now Playing view directly within your application.
Let's talk about the characteristics of that Now Playing view.
And the first thing I want to note is that the Digital Crown controls the volume.
And because of that, you're going to want to place this view in a non-scrolling controller.
So for instance, it's, in my app here, I've actually just pushed directly to a controller that has the Now Playing view in it.
Or you could reload directly to a page that has this controller or place it in a page interface similar to the workout app.
And this view is going to automatically switch the sources on behalf.
So no matter if it's the iPhone or the watch that's providing the source of audio playback, this view's going to take care of it for you.
You don't have to worry about that.
And lastly, the Now Playing view, it's going to grab your application's tint color so it fits within the look and feel of your application.
Now, to use this, let's see how it looks in Interface Builder.
Here we are in Xcode, and I've selected my Object Library, and then I can just drag and drop my Now Playing view into a controller.
Once my user navigates to that view, everything will be done for you.
The system will take care of it all.
And it really is just that simple.
So that's our Now Playing view.
Now, let's talk about our volume control.
So the volume control can be set up to either control the iPhone's volume or the local watch's volume.
And just like our Now Playing view, we're going to grab your application's tint color and apply it appropriately to this particular control.
And in the case of when the user is actually scrolling with the Digital Crown, then, in that case, the volume control will be tinted to the system green color to match the rest of the system.
So for the volume control, to actually decide when or not to control the local or iPhone's volume, there, in the Attributes Inspector is a check box for Controls Local Volume.
So if you leave it unchecked, then the volume control will control the iPhone's content.
And if you check it off, then the volume control will control the watch's content.
And so if your application might have multiple scenarios where it could either remote control some content or play some content locally, then, in that case, you might have different controls with different volume controls within them, and you make those decisions at design time.
All right, so those are really our system controls, so now let's talk about, how do we actually get content over to the device itself?
So I've been building a museum guided tour type application.
I want to allow my user to just tap in, listen to a guided tour while they're in the museum, and it's a great experience on the watch because I can just put my phone away.
I can just have the entire experience on my wrist and enjoy the beautiful art around me.
And let's say I had a, like a buffer that allowed my user to download the content on, when they're on their way to the museum.
In that case, I would want to use URLSession and grab that directly from my server, all my content, and download it to the watch itself.
So let's jump into some code and see how we could do that.
So here we are.
I have an IBAction which is tied to my WK Interface button.
And the first thing you'll notice is I set my frontmost timeout extended property to be true.
And we're going to talk a little bit more about frontmost later, but the thing I wanted to mention is that the user has made a user interaction, so it's fair to say that the user, next time they raise their wrist, would want to see our application.
And because we're extending the frontmost timeout, then we're getting some additional priority by the system for our URLSessions.
And again, I'll talk a little bit more about that later.
And the user can, of course, put their wrist down at any point, so I want to make sure to use a backgroundSession.
So that way, when my application goes into the background, my download is still occurring.
And then, I can just download that task and appropriately resume it.
And when you're building an experience like this, you want to indicate download progress and, of course, handle any errors.
So let's look at how to do that.
So here we are.
I have my class, my DownloadManager, which is my SessionTaskDelegate and my URLSessionDownload Delegate.
And in the first function, I can actually grab the total bytes written and divide it by the total bytes expected to write.
So that way, I can get the progress of my particular download whenever URLSession calls me and gives me that information.
And when I do this, I can just update progress for my user in my UI.
And then, when the download completes, I can, of course, manage the file, and I might want to post a notification alerting my user that there's a playback opportunity.
And, of course, I'm also going to be called into a didCompleteWithError, so I just want to clean up appropriately and handle any errors at that point, if there are any.
And that's wonderful, URLSession, for when we can download directly to the watch, but sometimes the content might already exist on our iPhone.
In that case, we can just use watch connectivity and transfer the file directly over to our watch.
And to do so, we'll just use the transfer file API.
And new in watchOS 5, you can get the progress of that transfer.
So let's take a look.
Here we are in code, and I see my WCSession FileTransfer.
And new, there's a progress property that's available in watchOS 5 and iOS 12.
And then, I can just get my fileTransfer, query for the progress, and update my view on iOS.
And if you want to know some more about watch connectivity.
There's some great sample code that's available online.
Highly recommend you check it out, the simple watch connectivity sample code.
And you might be wondering there's URLSession, there's watch connectivity which one do we use, exactly?
And when does it make sense?
Well, for URLSession, we want to use that when it's user initiated on the watch itself.
And then, when we do that, we want a query the waits for connectivity property to know that we have a network connection at that point.
And I just wanted to mention that if the iPhone is nearby, then requests are proxied through the iPhone when it's in range.
And if you want to know some of the nuances of URLSession, there's a great talk from last year that's available online, Advances in Networking, Part 2.
I definitely recommend checking it out.
And then, on the flip side, there's watch connectivity, and that's really important when your user flow might be when it's initiated on the iPhone itself or in the case where you've already downloaded some content and there's no need to request it from your server again.
And for both and especially for watch connectivity, you're going to want to set expectations for your user.
Make sure to instruct them of when the download, or the transfer's most likely to complete.
And that's going to be when the watch is on the magnetic charger.
And for example, we do this ourselves.
In the Apple Watch app in the Music pane, at the top, we actually show our user how many songs have actually been transferred over to the watch itself, and we even instruct our user, hey, music's going to sync when the Apple Watch is on its charger, so keep that in mind.
All right, so we talked about getting content, and now that we have it, let's talk about actually playing something back.
And on watchOS, we have a bunch of different tools we can use.
And some of those audio tools are in WatchKit itself.
So there's a presentMedia PlayerController with options, completion that you can use.
And there's also WKAudioFile QueuePlayer.
And so you can give it a list of items, and then the queue player will appropriately play back that content.
So the presentMedia PlayerController option will present the system UI.
So the controls are taken care of for you.
There's not really much you have to do.
And this is perfect for when it's short-form content and you don't really want to have to think too much about it.
And WKAudioFIleQueuePlayer is great for when you have more long-form content that you want to play in the background.
And this is more of a conveyer belt type approach where you have some items, you just want to hand it over to the system, and the system will play it on your behalf.
And that's exactly what WKAudioFileQueuePlayer is for.
And there's also AVFoundation at our disposal.
So there's AVAudioPlayer and AVAudioEngine.
And prior to watchOS 5, it was tied to the workout background mode, or when your application was foreground and screen on.
For example, Nike Run Club app actually did this exact thing, where you're in the middle of a run, you want some mindfulness, you can play some guided meditation with Headspace.
And prior to watchOS 5, background playback was, with AVFoundation, was really only possible for these types of workout apps.
But new in watchOS 5, there's a brand-new audio playback background mode, which I'm really thrilled for.
So let's talk about what we have now at our disposal.
Kind of broadened our tool kit.
And so we have AVFoundation, and it doesn't have to be tied to any other background mode, and we can now use just AVFoundation directly in the background.
We also are exposing the ability to allow you to, allow your user to pick a different route.
So maybe some different types of Bluetooth headphones.
And new in watchOS 5, we're also exposing the MediaPlayer.
framework, so you can provide some Now Playing information and also handle any media remote commands.
And just like our own Music, Podcasts, and Radio app, the long-form content is restricted to Bluetooth routes, which really is just a fancy way of saying you can't play it through the speaker.
So let's take it step by step and talk about how we actually get this background long-form content to play.
So the first thing, you add audio to your info.plist background modes.
And then, you just appropriately set up your session, and you're going to want to tell the system that you want to play this long-form content in the background.
You want to allow your user to select a different route.
And then, you're just going to want to call play and appropriately handle the playback when the user's in the middle of that playback session.
So let's break that more, break that down more a little bit.
And so in Xcode in the Project Settings, in the Capabilities tab, you just flip the switch for Background Mode, you check off Audio, and you're good to go to the next step.
And so to set up your session, you're going to want to set your routeSharingPolicy to longform on your AVAudioSession.
And then, you call the new activate withOptions completion method on AVAudioSession.
And then, once it completes, you're going to call play.
And in between when you first call the activate function and then when the completion comes back, what we call the route picker is going to get displayed on your behalf.
And so this is what the route picker looks like.
And so this is going to be showing up within your application, which is going to allow your user to pick whichever Bluetooth headphones route they want to have and control their playback.
So let's talk a little bit more about that route picker.
Again, all it is, you just call your asynchronous activate function, which is going to call and show your route picker, and then it's going to come back into your completion, and you handle that, and you just call play.
And you're going to want to do this every time you want playback to occur.
So that way, you can ensure that you have appropriate route to cause some playback to occur.
All right, so let's jump into code and set up our session.
And so we have our AVAudioSession, and then we're going to set our category to playback.
And then, we're going to set our mode to default.
And then, most importantly, we're going to set our route sharing policy to longform.
And then, you want to activate your session, which is going to display the route picker, and then you're going to call play appropriately.
And now, for that route picker, I wanted to call out that we really think of routes in two different ways.
And the first way is our Apple wireless chip-based routes, which are commonly referred to as our W1 routes.
So these are our AirPods or Beats Studio Wireless.
And then, we just have our normal Bluetooth headphone routes.
And it's important to make that distinction when we talk about the route picker itself.
And so if the user has an active connected route, then the route picker's going to automatically select that route on your behalf.
What's great is that the route picker might not even display to your user.
You're just going to get called back in your completion immediately.
And in the case where the user has an active W1 route but it's being used by the iPhone, then the system's going to grab that route from iPhone and bring it directly to your watch.
And there's some cases where that won't happen.
It's really just when your iPhone has more priority.
So for example, say I'm in the middle of a phone call on my phone.
Then, we're not going to take that route and put it on your watch because phone call's probably pretty important.
And if there are no active routes, then the route picker will just appear on your behalf.
All right, so that's our route picker, and now let's talk about, how do we actually get some playback to occur?
Well, you just use the same AVAudioPlayer and AVAudioEngine.
And there's a variety of different formats that are supported on watchOS.
And what's great about this is if you have iOS code that uses these classes, then you could have a shared framework between your iOS and watchOS playback operations.
And in the case of AVAudioEngine, you can actually play some DRM content in conjunction with AVAudioPlayer node.
And so you can now play your own DRM content, decrypt it yourself, and play it back for your user.
An especially important thing to note for watchOS is the power of the device itself, so only play audio when absolutely necessary, when the user actually wants the audio to occur.
And for AVAudioEngine, the autoShutdownEnabled property is enabled by default, so if there are no active nodes on your AVAudioEngine, then we're going to automatically power down your AVAudioEngine when necessary.
And so that's our playback.
And then, we, let's talk about our MediaPlayer.
So we want to, of course, provide our Now Playing information to the system.
And then, the Now Playing UI will update, and that's the system-wide notion of what's playing, and it's going to be updated based on the information you provide.
So this is going to show up in the Now Playing app, and even inside apps such as Workout, or in the cases of any third-party apps that embed that Now Playing view.
This will also show up there.
And then, with the MediaPlayer.
framework, we just want to handle the events, any type of media remote commands for example, a Next Track on AirPods, for instance.
All right, let's jump into some code and see how we can provide that Now Playing information.
So here we are.
We have our MPNowPlaying InfoCenter.
We just want to grab that.
And then, we set up our nowPlayingInfo.
And then, we have, we want to set our artwork, our MPMedia ItemArtwork.
And it's not currently available in seed 1, but it's coming in seed 2.
And in this case, the artwork that you provide will display on the Siri watch face.
And then, we just set up, in this case, our title, album, artwork, and then we set it to our nowPlaying InfoCenter.
And then, when we do something such as this, then our data will appear in the Now Playing app.
And this is the actual Now Playing app.
And I provided some, a song that I was currently listening to in my application.
And when we do this, then, also, the Now Playing complication will also get updated.
So in this case, the bottom shows the "Lights in the Sky" song that I was listening to.
And then, for media remote commands, again, just use the MediaPlayer.
And now, in watchOS, you can handle those commands however you wish.
So let's look at some code.
So we just grab our MPRemoteCommandCenter.
And then, instead of doing a Next Track, let's talk about how we can actually skip forward for our user.
So I have this function.
It's a enableSkipForwardCommand and, just by default, takes in 15 seconds.
And I set my preferred intervals on my skip forward command.
And then, I just add a target and selector, and then I just enable the command within my remote command center.
If you want to know more about the media player framework and especially these media remote commands, I highly recommend checking out the MP remote command sample that's available online.
And in this case, when I actually set my preferred intervals, then the system's Now Playing app will appropriately get adjusted.
That Now Playing UI, wherever it is, will show the minus 15 seconds or plus 15 seconds, and that's all done on your behalf.
All right, so that's the audio experience on watch, or that's our playback, so let's talk about our audio experience.
So there's 4 major things I want to talk about first for the audio experience.
There's our auto-launching feature where your app will automatically get launched.
There's the frontmost app state and how we can take advantage of that in our application.
There's our new interactive notifications.
And how to integrate with Siri Shortcuts.
And so we've been talking a lot about playback occurring on the watch itself, but I want to take a step back, and I just wanted to talk through that remote control case.
So let's say I was playing some Apple Music on my iPhone, and automatically the Now Playing app will get displayed on my watch.
And I love this feature.
It feels like magic when I just look at my wrist and I can easily control my playback.
I, it's the coolest thing.
And in the case where it's our application that's actually causing the playback to occur on iPhone, then our app automatically gets launched on the watch.
And we call this the auto-launch audio apps feature.
And so whenever the Now Playing session is occurring on iPhone, then our Apple Watch app will be brought frontmost at that time.
And we're going to stay frontmost for the duration of that session.
So as long as the user is listening to content on iPhone, then our app will stay frontmost, unless the user decides to navigate away.
And new in watchOS 5 is we're exposing a Now Playing session API where you're going to know when you're launched for this Now Playing session on iPhone, and so you can take your user directly to the appropriate view.
And to do this, you're going to use the handleRemoteNow PlayingActivity on your WKExtension Delegate.
So let's take a look at some code and see how we could use this.
So here we are.
I have my extension delegate, and then I can implement my handleRemoteNow PlayingActivity.
And the first thing I want to check is, where is my user at that time?
So I can just grab the visible interface controller, I can check where my user is, and if the user already is at my Now Playing UI, that's great.
Let's leave them right there.
But in the case they're not, we can just reload directly to our view.
So that way, we have that same magical experience where I raise my wrists and I see the remote controls that I want to use.
And in some cases, you might want to opt out of this feature if it doesn't make sense for your user.
So just do the right thing.
Make that decision.
And let's say our app has nothing to do with our phone app, and that's an appropriate use of actually opting out of this capability.
And so to do so, use the auto, the opt out of auto-launch feature on your WK extension's info.plist.
And in the case you do opt out, then the Now Playing app will show instead.
And now, let's talk about that frontmost app state, and let's give a little bit of a refresher of that.
So here I have my app.
It's foreground running.
I'm looking at it.
The screen is on, and this app is considered active and frontmost.
But then, let's say I put my wrist down.
In that case, the application is still considered frontmost because if I raise my wrist again, I'll see that application.
And in that case, the application is just in the background.
So with the frontmost app state, we get some enhanced capabilities from the system.
Our background transfers from iPhones so say we were transferring our content from iPhone to watch itself, those background transfers, when they complete, the resumes will directly just wake up our app if we're in the frontmost app state.
And the same thing in, with our URL session resumes.
So if the URL session, our background session before was downloading and it completes, then our app will automatically get waked up and deliver the payload.
And if we're frontmost, we're going to get called into, so we can decide whenever, if a notification appears, how we properly want to handle that notification.
Maybe we want to keep our user in the app, or maybe we want to show the notification to our user.
And in the frontmost app state, we can make that decision.
And when you're in the frontmost app state, you can play some haptics.
And if you want to know a little bit more about frontmost app state, there's the talk from last year, The Life of a watchOS App.
It's a great talk.
You might recognize the speaker.
And so you're going to stay frontmost for 2 minutes, but, if you recall, I extended my frontmost earlier.
And so in that case, it's just going to be extended by default to 8 minutes.
And so, again, this is great for when we were downloading that content from before, and so I can just extend my time out.
And when we're actually playing back some audio, our app will be kept frontmost for the duration of that session, just like when we were actually remote controlling the audio when it was occurring on iPhone.
And again, if the user ever navigates away, then you're no longer frontmost, but your background playback will still occur appropriately.
And in that case, you can, of course, properly handle all of your background events.
So now, we're and when we are in that frontmost state, then we also can integrate properly with notification.
So we could post, for example, when the download completes, a content available local notification.
And we might want to have play as our primary action.
And with our new interaction notifications on watchOS, we can allow our user to actually configure some playback right from within the notification and then be able to call play as the primary action, then get into your app and immediately cause some playback to occur.
So for example, say I had a Salvador Dali exhibit.
It's ready to go.
I can then adjust the playback speed, immediately call play, that'll launch my app, and then I can play that great content at the appropriate rate for my user.
If you want to know more about some interactive notifications, check out the What's New in watchOS talk given earlier, which is available online.
It was a great talk, and you'll know a lot more about interactive notifications after watching it.
All right, so do you remember this code from before where I actually posted that notification alerting our user of this type of playback opportunity?
Well, let's say our app was frontmost at that time.
In that case, when I got delivered the notification from frontmost in user notification, I'm going to get called into and will present notification with completion handler.
And in that case, I can actually decide whether or not I want to still post that notification.
Or maybe because my app's frontmost, I could just play a haptic and let my user know that, hey, raise your wrist, and there's some new content.
I can update my view.
And again, if you want to know some more about the frontmost notifications, check out the Life of a watchOS App.
Great, so that's how we used our interactive notifications and the frontmost app state, but we also want to show up on the Siri watch face.
And with audio apps, you can provide suggestions of audio to play on the Siri watch face using relevant shortcuts.
So for example, that could be the next episode of your user's favorite podcast or the new music that you just synced over to the watch.
For example, Audible was able to build the same experience where they can just get their user directly within their application from the Siri watch face and resume the playback of an audiobook.
And whenever a user interaction is occurring, just wanted to highlight, think through your donations.
Make sure to donate your INMedia PlaybackIntent to the system so that way the system knows what your user is commonly doing on the watch.
And that way, you can become more relevant on the Siri watch face.
And also, just think through your shortcut phrases like design appropriate phrases that your user could use within Siri to get your user directly within your application to allow for some playback to occur.
And all in all, on the Siri watch face, design a great experience.
You might have some glanceable information like, hey, there's an exhibit coming soon.
Or you could have some tappable actions like Audible was able to make where, hey, we just want to play that audio tour and get our user directly in the app.
And so for shortcuts, donate your INMediaPlaybackIntent.
Use the relevant shortcut API to appear on the Siri watch face.
And think through your shortcut phrases.
And there's been some great talks this week about shortcuts, and I definitely wanted to highlight the Shortcuts on the Siri Watch Face talk.
I saw that myself.
It was a great talk, and I learned a lot.
And so in summary, for the first time, you can embed your native controls directly from within your application.
You can allow background playback to occur however you want and handle media remote commands however you want.
And there are better ways that transfer audio assets to your watch and convey that type of progress to your users.
So if you want some more information, feel free to go online or join us in the lab this afternoon.
I'm really excited to see what type of audio apps are built on watchOS, and thanks, everyone.
Have a great WWDC.
[ Applause ]