The current version of iOS brings some changes to multimedia playback and functionality, especially the AVFoundation framework. In this chapter, we will have a look at those additions and some of the changes.
Make sure that you have imported the AVFoundation framework in your app before running the code in this chapter.
Let’s create an example out of this. Create your UI so that it looks like Figure 16-1. Place a text view on the screen and a bar button item in your navigation bar. When the button is pressed, you will ask Siri to speak out the text inside the text view.
I’ve linked the text view to a property in my view controller called textView
:
@IBOutlet
var
textView
:
UITextView
!
When the read button is pressed, check first whether Alex is available:
guard
let
voice
=
AVSpeechSynthesisVoice
(
identifier
:
AVSpeechSynthesisVoiceIdentifierAlex
)
else
{
(
"Alex is not available"
)
return
}
Instances of AVSpeechSynthesisVoice
have properties such as identifier
, quality
, and name
. The identifier can be used later to reconstruct another speech object. If all you know is the identifier, then you can re-create the speech object using that. The quality
property is of type AVSpeechSynthesisVoiceQuality
and can be equal to values such as default
or enhanced
. Let’s print these values to the console:
(
"id =
(
voice
.
identifier
)
"
)
(
"quality =
(
voice
.
quality
)
"
)
(
"name =
(
voice
.
name
)
"
)
Then create the voice object (of type AVSpeechUtterance
) with your text view’s text:
let
toSay
=
AVSpeechUtterance
(
string
:
textView
.
text
)
toSay
.
voice
=
voice
Last but not least, instantiate the voice synthesizer of type AVSpeechSynthesizer
and ask it to speak out the voice object:
let
alex
=
AVSpeechSynthesizer
()
alex
.
delegate
=
self
alex
.
speak
(
toSay
)
AVURLAsset
with the URL to your asset.background(withIdentifier:)
class method on URLSessionConfiguration
to create a background session configuration.AVAssetDownloadURLSession
and pass your configuration to it.makeAssetDownloadTask(asset:destinationURL:options)
method of your session to create a download task of type AVAssetDownloadTask
.resume()
method on your task to start the task.AVAssetDownloadDelegate
protocol to get events from your task.All the classes I discussed whose names start with “AV” are in the AVFoundation framework, so make sure to import it.
Let’s imagine that you have an .mp4 file that you want to download and play back in your app. First set up your view controller:
import
UIKit
import
AVFoundation
class
ViewController
:
UIViewController
,
AVAssetDownloadDelegate
{
let
url
=
URL
(
string
:
"http://localhost:8888/video.mp4"
)
!
let
sessionId
=
"com.mycompany.background"
let
queue
=
OperationQueue
()
var
task
:
AVAssetDownloadTask
?
var
session
:
AVAssetDownloadURLSession
?
...
I am using MAMP to start a local server on my machine and host the file video.mp4 on my own computer, hence the URL that you are seeing. You can and probably should change this URL to a valid media file that AVFoundation can handle, like mov or mp4.
Now define some of the delegate methods defined in AVAssetDownloadDelegate
and URLSessionTaskDelegate
:
func
urlSession
(
_
session
:
URLSession
,
task
:
URLSessionTask
,
didCompleteWithError
error
:
Error
?)
{
// code this
}
func
urlSession
(
_
session
:
URLSession
,
assetDownloadTask
:
AVAssetDownloadTask
,
didLoad
timeRange
:
CMTimeRange
,
totalTimeRangesLoaded
loadedTimeRanges
:
[
NSValue
],
timeRangeExpectedToLoad
:
CMTimeRange
)
{
// code this
}
func
urlSession
(
_
session
:
URLSession
,
assetDownloadTask
:
AVAssetDownloadTask
,
didResolve
resolvedMediaSelection
:
AVMediaSelection
)
{
}
Next, create an asset by its URL. At the same time, tell the system that you don’t want cross-site references to be resolved using a dictionary with a key equal to AVURLAssetReferenceRestrictionsKey
and value of AVAssetReferenceRestrictions.forbidCrossSiteReference
:
let
options
=
[
AVURLAssetReferenceRestrictionsKey
:
AVAssetReferenceRestrictions
.
forbidCrossSiteReference
.
rawValue
]
let
asset
=
AVURLAsset
(
url
:
url
,
options
:
options
)
Now it’s time to create the configuration object of type URLSessionConfiguration
:
let
config
=
URLSessionConfiguration
.
background
(
withIdentifier
:
sessionId
)
Create the session of type AVAssetDownloadURLSession
:
let
session
=
AVAssetDownloadURLSession
(
configuration
:
config
,
assetDownloadDelegate
:
self
,
delegateQueue
:
queue
)
self
.
session
=
session
You must have noticed that I keep a reference to the session and the task that we are going to create soon. This is so we can refer to them later and cancel or reuse them if necessary.
And last but not least, construct the task and start it:
guard
let
task
=
session
.
makeAssetDownloadTask
(
asset
:
asset
,
assetTitle
:
"Asset title"
,
assetArtworkData
:
nil
,
options
:
nil
)
else
{
(
"Could not create the task"
)
return
}
self
.
task
=
task
task
.
resume
()
availableCategories
property of your audio session and find AVAudioSessionCategoryPlayback
.availableModes
property of your audio session (of type AVAudioSession
). If you cannot find AVAudioSessionModeSpokenAudio
, exit gracefully.AVAudioSessionModeSpokenAudio
mode, set your audio category to AVAudioSessionCategoryPlayback
using the setCategory(_:with:)
method of the audio session.setActive(_:with:)
method of your audio session.Suppose you are developing an ebook app and have a “Read” button in the UI that the user presses to ask the app to read the contents of the book out loud. For this you can use the AVAudioSessionModeSpokenAudio
audio session mode, but you have to check first whether that mode exists. To find out, use the availableModes
property of your audio session.
Let’s work on an example. Let’s find the AVAudioSessionCategoryPlayback
category and the AVAudioSessionModeSpokenAudio
mode:
guard
session
.
availableCategories
.
filter
(
{
$0
==
AVAudioSessionCategoryPlayback
}).
count
==
1
&&
session
.
availableModes
.
filter
(
{
$0
==
AVAudioSessionModeSpokenAudio
}).
count
==
1
else
{
(
"Could not find the category or the mode"
)
return
}
After you confirm that the category and mode are available, set the category and mode and then activate your audio session:
do
{
try
session
.
setCategory
(
AVAudioSessionCategoryPlayback
,
with
:
AVAudioSessionCategoryOptions
.
interruptSpokenAudioAndMixWithOthers
)
try
session
.
setMode
(
AVAudioSessionModeSpokenAudio
)
try
session
.
setActive
(
true
,
with
:
AVAudioSessionSetActiveOptions
.
notifyOthersOnDeactivation
)
}
catch
let
err
{
(
"Error =
(
err
)
"
)
}