AV Foundation — это фреймворк для работы с аудио и визуальными медиа на iOS и OSX. Используя AV Foundation, вы можете воспроизводить, захватывать и кодировать мультимедиа. Это довольно обширный фреймворк, и в этом уроке мы сосредоточимся на аудио части. В частности, мы будем использовать класс AVAudioPlayer
для воспроизведения файлов MP3.
Стартовый проект
Я предоставил стартовый проект, в котором все действия и выходы уже настроены, а соответствующие методы отключены. Классы, используемые в проекте, также уже заглушены, поэтому мы можем погрузиться прямо в код. Вы можете скачать стартовый проект с GitHub .
1. Связывание AV Foundation Framework
Прежде чем вы сможете использовать AV Foundation, вы должны связать проект со структурой. В Project Navigator убедитесь, что ваш проект выбран. На вкладке « Общие » перейдите в « Связанные фреймворки и библиотеки» и оттуда выберите AVFoundation.framework .
2. Класс FileReader
В начальном проекте вы найдете файл с именем FileReader.swift . Откройте этот файл, чтобы просмотреть его содержимое.
1
2
3
4
5
|
import UIKit
class FileReader: NSObject {
}
|
Это простая заглушка класса, которую мы будем использовать для чтения файлов с диска. Он наследуется от NSObject
. Мы реализуем метод readFiles
, который будет методом типа . Методы типа позволяют вам вызывать метод самого класса, в отличие от экземпляра класса. Ниже приведена реализация метода readFiles
.
1
2
3
|
class func readFiles() -> [String] {
return NSBundle.mainBundle().pathsForResourcesOfType(«mp3», inDirectory: nil) as!
}
|
Основной пакет содержит код и ресурсы для вашего проекта, и именно здесь мы найдем MP3. Мы используем метод pathsForResourcesOfType(_:inDirectory:)
, который возвращает массив, содержащий пути для указанного типа ресурса. В этом случае мы ищем тип "mp3"
. Поскольку нас не интересует конкретный каталог, мы передаем nil
.
Этот класс будет использоваться классом MP3Player
, над которым мы будем работать дальше.
3. Класс MP3Player
Далее откройте MP3Player.swift и просмотрите его содержимое.
1
2
3
4
5
6
|
import UIKit
import AVFoundation
class MP3Player: NSObject, AVAudioPlayerDelegate {
}
|
Обратите внимание, что мы принимаем протокол AVAudioPlayerDelegate
. Этот протокол объявляет ряд полезных методов, одним из которых является audioPlayerDidFinishPlaying(_:successfully:)
. audioPlayerDidFinishPlaying(_:successfully:)
метод audioPlayerDidFinishPlaying(_:successfully:)
, мы получим уведомление о завершении воспроизведения звука.
Шаг 1: Свойства
Добавьте следующее в MP3Player.swift .
1
2
3
4
5
|
class MP3Player: NSObject, AVAudioPlayerDelegate {
var player:AVAudioPlayer?
var currentTrackIndex = 0
var tracks:[String] = [String]()
}
|
Свойство player
будет экземпляром класса AVAudioPlayer
, который мы будем использовать для воспроизведения, приостановки и остановки MP3. Переменная currentTrackIndex
отслеживает, какой MP3 воспроизводится в данный момент. Наконец, переменная track будет массивом путей к списку MP3, включенных в комплект приложения.
Шаг 2: init
1
2
3
4
5
|
override init(){
tracks = FileReader.readFiles()
super.init()
queueTrack();
}
|
Во время инициализации мы readFiles
метод readFiles
чтобы readFiles
пути к файлам MP3 и сохранить этот список в массиве tracks
. Поскольку это назначенный инициализатор, мы должны вызвать метод init
суперкласса. Наконец, мы вызываем queueTrack
, о котором мы будем писать дальше.
Шаг 3: queueTrack
Добавьте следующую реализацию для метода MP3Player
класс MP3Player
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
func queueTrack(){
if (player != nil) {
player = nil
}
var error:NSError?
let url = NSURL.fileURLWithPath(tracks[currentTrackIndex] as String)
player = AVAudioPlayer(contentsOfURL: url, error: &error)
if let hasError = error {
//SHOW ALERT OR SOMETHING
} else {
player?.delegate = self
player?.prepareToPlay()
}
}
|
Поскольку мы будем создавать новый экземпляр AVAudioPlayer
каждый раз, когда вызывается этот метод, мы делаем небольшую уборку, устанавливая player
в nil
.
Мы объявляем необязательный NSError
и постоянный url
. Мы вызываем fileURLWithPath(_:)
чтобы получить путь к текущему MP3 и сохранить значение в url
. Мы передаем массив tracks
в качестве параметра, используя currentTrackIndex
в качестве индекса. Помните, что массив треков содержит пути к MP3, а не ссылку на сами файлы MP3.
Чтобы создать экземпляр player
, мы передаем константу url
и переменную error
в инициализатор AVAudioPlayer
. Если инициализация не удалась, переменная error
заполняется описанием ошибки.
Если мы не сталкиваемся с ошибкой, мы устанавливаем делегат игрока на self
и prepareToPlay
метод prepareToPlay
на плеере. Метод prepareToPlay предварительно загружает буферы и получает аудиооборудование, которое минимизирует любую задержку при вызове метода play
.
Шаг 4: play
Метод play
сначала проверяет, play
ли уже аудио, проверяя метко названное свойство playing
. Если звук не воспроизводится, он вызывает метод play
свойства player
.
1
2
3
4
|
func play() {
if player?.playing == false {
player?.play()
}
|
Шаг 5: stop
Сначала метод stop
проверяет, воспроизводится ли аудиоплеер. Если это так, он вызывает метод stop
и устанавливает для свойства currentTime
значение 0 . Когда вы вызываете метод stop
, он просто останавливает звук. Он не сбрасывает звук обратно в начало, поэтому нам нужно сделать это вручную.
1
2
3
4
5
6
|
func stop(){
if player?.playing == true {
player?.stop()
player?.currentTime = 0
}
}
|
Шаг 6: pause
Так же, как и метод stop
, мы сначала проверяем, воспроизводится ли аудиоплеер. Если это так, мы вызываем метод pause
.
1
2
3
4
5
|
func pause(){
if player?.playing == true{
player?.pause()
}
}
|
Шаг 7: nextSong
Метод nextSong(_:Bool)
очередь следующую песню и, если проигрыватель играет, воспроизводит эту песню. Мы не хотим, чтобы следующая песня играла, если проигрыватель приостановлен. Однако этот метод также вызывается, когда заканчивается воспроизведение песни. В этом случае мы хотим воспроизвести следующую песню, для чего предназначен параметр songFinishedPlaying
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
func nextSong(songFinishedPlaying:Bool){
var playerWasPlaying = false
if player?.playing == true {
player?.stop()
playerWasPlaying = true
}
currentTrackIndex++
if currentTrackIndex >= tracks.count {
currentTrackIndex = 0
}
queueTrack()
if playerWasPlaying ||
player?.play()
}
}
|
Переменная playerWasPlaying
используется, чтобы указать, играл ли игрок, когда этот метод был вызван. Если песня воспроизводилась, мы вызываем метод stop
на player
и устанавливаем для playerWasPlaying
значение true
.
Затем мы увеличиваем значение currentTrackIndex
и проверяем, больше или равно tracks.count
равно tracks.count
. Свойство count
массива дает нам общее количество элементов в массиве. Мы должны быть уверены, что не пытаемся получить доступ к элементу, который не существует в массиве track. Чтобы предотвратить это, мы устанавливаем currentTrackIndex
обратно на первый элемент массива, если это так.
Наконец, мы вызываем queueTrack
чтобы подготовить следующую песню и воспроизводить эту песню, если для playerWasPlaying
или songFinishedPlaying
true
.
Шаг 8: previousSong
Метод previousSong
работает очень похоже на nextSong
. Разница лишь в том, что мы уменьшаем значение currentTrackIndex
и проверяем, равно ли оно 0 . Если это так, мы устанавливаем его в индекс последнего элемента в массиве.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
func previousSong(){
var playerWasPlaying = false
if player?.playing == true {
player?.stop()
playerWasPlaying = true
}
currentTrackIndex—
if currentTrackIndex < 0 {
currentTrackIndex = tracks.count — 1
}
queueTrack()
if playerWasPlaying {
player?.play()
}
}
|
Используя методы nextSong
и previousSong
, мы можем циклически перебирать все MP3 и начинать nextSong
когда достигаем начала или конца списка.
Шаг 9: getCurrentTrackName
Метод getCurrentTrackName
возвращает имя MP3 без расширения.
1
2
3
4
|
func getCurrentTrackName() -> String {
let trackName = tracks[currentTrackIndex].lastPathComponent.stringByDeletingPathExtension
return trackName
}
|
Мы получаем ссылку на текущий MP3, используя tracks[currentTrackIndex]
. Помните, однако, что это пути к MP3, а не сами файлы. Пути довольно длинные, потому что это полный путь к файлам MP3.
Например, на моей машине первый элемент массива track равен » / Users / jamestyner / Library / Developer / CoreSimulator / Devices / 80C8CD34-22AE-4F00-862E-FD41E2D8D6BA / data / Containers / Bundle / Application / 3BCF8543 -BA1B-4997-9777-7EC56B1C4348 / MP3Player.app / Lonesome Road Blues.mp3 «. Этот путь будет отличаться на реальном устройстве, конечно.
У нас есть большая строка, которая содержит путь к MP3, но мы просто хотим название самого MP3. Класс NSString
определяет два свойства, которые могут нам помочь. Как видно из названия, свойство lastPathComponent
возвращает последний компонент пути. Как вы уже догадались, свойство stringByDeletingPathExtension
удаляет расширение.
Шаг 10: getCurrentTimeAsString
Метод getCurrentTimeAsString
использует свойство currentTime
экземпляра player
и возвращает его в виде удобочитаемой строки (например, 1:02) .
1
2
3
4
5
6
7
8
9
|
func getCurrentTimeAsString() -> String {
var seconds = 0
var minutes = 0
if let time = player?.currentTime {
seconds = Int(time) % 60
minutes = (Int(time) / 60) % 60
}
return String(format: «%0.2d:%0.2d»,minutes,seconds)
}
|
Свойство currentTime
имеет тип NSTimeInterval
, который является просто typealias
для Double
. Мы используем некоторую математику, чтобы получить seconds
и minutes
, убедившись, что мы конвертируем time
в Int
поскольку нам нужно работать с целыми числами Если вы не знакомы с оператором остатка (%), он находит остаток после деления одного числа на другое. Если бы переменная time
была равна 65 , то seconds
были бы равны 5, потому что мы используем 60 .
Шаг 11: getProgress
Метод getProgress
используется экземпляром UIProgressView
для указания количества воспроизведенного MP3. Этот прогресс представлен значением от 0,0 до 1,0 в виде числа с Float
.
1
2
3
4
5
6
7
8
9
|
func getProgress()->Float{
var theCurrentTime = 0.0
var theCurrentDuration = 0.0
if let currentTime = player?.currentTime, duration = player?.duration {
theCurrentTime = currentTime
theCurrentDuration = duration
}
return Float(theCurrentTime / theCurrentDuration)
}
|
Чтобы получить это значение, мы делим свойство currentTime
player
на свойство duration
player
, мы сохраняем эти значения в переменных theCurrentTime
и theCurrentDuration
. Как и currentTime
, свойство duration
имеет тип NSTimeInterval
и представляет продолжительность песни в секундах.
Шаг 12: setVolume
Метод setVolume(_:Float)
вызывает метод setVolume
экземпляра player
.
1
2
3
|
func setVolume(volume:Float){
player?.volume = volume
}
|
Шаг 13: audioPlayerDidFinishPlaying(_:successfully:)
Метод audioPlayerDidFinishPlaying(_:successfully:)
— это метод протокола AVAudioPlayerDelegate
. Этот метод принимает в качестве параметров экземпляр AVAudioPlayer
и логическое значение. Логическое значение установлено на true
если аудиоплеер завершил воспроизведение текущей песни.
1
2
3
4
5
|
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool){
if flag == true {
nextSong(true)
}
}
|
Если песня успешно завершилась, мы вызываем метод nextSong
, передавая значение true
поскольку песня закончила играть самостоятельно.
Это завершает класс MP3Player
. Мы ViewController
к нему чуть позже, после реализации действий класса ViewController
.
4. Класс ViewController
Откройте ViewController.swift и просмотрите его содержимое.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
mport UIKit
import AVFoundation
class ViewController: UIViewController {
var mp3Player:MP3Player?
var timer:NSTimer?
@IBOutlet weak var trackName: UILabel!
@IBOutlet weak var trackTime: UILabel!
@IBOutlet weak var progressBar: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func playSong(sender: AnyObject) {
}
@IBAction func stopSong(sender: AnyObject) {
}
@IBAction func pauseSong(sender: AnyObject) {
}
@IBAction func playNextSong(sender: AnyObject) {
}
@IBAction func setVolume(sender: UISlider) {
}
@IBAction func playPreviousSong(sender: AnyObject) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
|
Переменная mp3Player
является экземпляром класса MP3Player
мы реализовали ранее. Переменная timer
будет использоваться для обновления представлений trackTime
и progressBar
каждую секунду.
На следующих нескольких шагах мы реализуем действия класса ViewController
. Но сначала мы должны создать экземпляр экземпляра MP3Player
. Обновите реализацию метода viewDidLoad
как показано ниже.
1
2
3
4
|
override func viewDidLoad() {
super.viewDidLoad()
mp3Player = MP3Player()
}
|
Шаг 1: playSong(_: AnyObject)
Введите следующее в метод playSong(_: AnyObject)
.
1
2
3
|
@IBAction func playSong(sender: AnyObject) {
mp3Player?.play()
}
|
В этом методе мы вызываем метод mp3Player
объекта mp3Player
. Сейчас мы можем приступить к тестированию приложения. Запустите приложение и нажмите кнопку воспроизведения. Песня должна начать играть.
Шаг 2: stopSong(_: AnyObject)
Метод stopSong(_: AnyObject)
вызывает метод mp3Player
объекта mp3Player
.
1
2
3
|
@IBAction func stopSong(sender: AnyObject) {
mp3Player?.stop()
}
|
Запустите приложение еще раз и нажмите кнопку воспроизведения. Теперь вы сможете остановить песню, нажав кнопку остановки.
Шаг 3: pauseSong(_: AnyObject)
Как вы уже догадались, метод pauseSong(_: AnyObject)
вызывает метод mp3Player
объекта mp3Player
.
1
2
3
|
@IBAction func pauseSong(sender: AnyObject) {
mp3Player?.pause()
}
|
Шаг 4: playNextSong(_: AnyObject)
1
2
3
|
IBAction func playNextSong(sender: AnyObject) {
mp3Player?.nextSong(false)
}
|
В playNextSong(_: AnyObject)
мы nextSong
метод mp3player
объекта mp3player
. Обратите внимание, что мы передаем false
как параметр, потому что песня не закончила играть самостоятельно. Мы вручную запускаем следующую песню нажатием следующей кнопки.
Шаг 5: previousSong(_: AnyObject)
1
2
3
|
@IBAction func playPreviousSong(sender: AnyObject) {
mp3Player?.previousSong()
}
|
Как видите, реализация метода previousSong(_: AnyObject)
очень похожа на реализацию nextSong(_: AnyObject)
. Все кнопки MP3-плеера должны быть исправны. Если вы еще не тестировали приложение, сейчас самое время убедиться, что все работает как положено.
Шаг 6: setVolume(_: UISlider)
Метод setVolume(_: UISlider)
вызывает метод mp3Player
объекта mp3Player
. Свойство тома имеет тип Float
. Значение варьируется от 0,0 до 1,0 . Для объекта UISlider
установлено значение 0.0 в качестве минимального значения и 1.0 в качестве максимального значения.
1
2
3
|
@IBAction func setVolume(sender: UISlider) {
mp3Player?.setVolume(sender.value)
}
|
Запустите приложение еще раз и поиграйте с ползунком громкости, чтобы проверить, что все работает правильно.
Шаг 7: startTimer
Метод startTimer
создает новый экземпляр NSTimer
.
1
2
3
|
func startTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector(«updateViewsWithTimer:»), userInfo: nil, repeats: true)
}
|
Параметр scheduledTimerWithTimeInterval(_:target:selector:userInfo:repeats:)
инициализатор принимает в качестве параметров количество секунд между срабатыванием таймера, объект, для которого вызывается метод, указанный selector
, метод, который вызывается при срабатывании таймера , необязательный словарь userInfo
, и повторяется ли таймер, пока он не станет недействительным.
Мы используем метод с именем updateViewsWithTimer(_: NSTimer)
в качестве селектора, поэтому мы создадим его следующим.
Шаг 8: updateViewsWithTimer(_: NSTimer)
updateViewsWithTimer(_: NSTimer)
Метод updateViewsWithTimer(_: NSTimer)
вызывает метод updateViews
, который мы реализуем на следующем шаге.
1
2
3
|
func updateViewsWithTimer(theTimer: NSTimer){
updateViews()
}
|
Шаг 9: updateViews
Метод updateViews
обновляет представления trackTime
и progressBar
.
1
2
3
4
5
6
|
func updateViews(){
trackTime.text = mp3Player?.getCurrentTimeAsString()
if let progress = mp3Player?.getProgress() {
progressBar.progress = progress
}
}
|
Свойство text
trackTime
обновляется свойством currentTime
, отформатированным в виде строки с getCurrentTimeAsString
метода getCurrentTimeAsString
. Мы объявляем постоянный progress
используя метод mp3Player
getProgress
, и устанавливаем progressBar.progress
используя эту константу.
Шаг 10: Подключение таймера
Теперь нам нужно вызвать метод startTimer
в соответствующих местах. Нам нужно запустить таймер в playSong(_: AnyObject)
методе playNextSong(_ :AnyObject)
методе playPreviousSong(_ :AnyObject)
.
1
2
3
4
|
@IBAction func playSong(sender: AnyObject) {
mp3Player?.play()
startTimer()
}
|
1
2
3
4
|
@IBAction func playNextSong(sender: AnyObject) {
mp3Player?.nextSong(false)
startTimer()
}
|
1
2
3
4
|
@IBAction func playPreviousSong(sender: AnyObject) {
mp3Player?.previousSong()
startTimer()
}
|
Шаг 11: Остановка таймера
Нам также нужно остановить timer
при нажатии кнопок паузы и остановки. Вы можете остановить объект timer
, вызвав метод NSTimer
экземпляре NSTimer
.
1
2
3
4
5
|
@IBAction func stopSong(sender: AnyObject) {
mp3Player?.stop()
updateViews()
timer?.invalidate()
}
|
1
2
3
4
|
@IBAction func pauseSong(sender: AnyObject) {
mp3Player?.pause()
timer?.invalidate()
}
|
Шаг 12: setTrackName
Метод setTrackName
устанавливает text
свойство trackName
, вызывая getCurrentTrackName
mp3Player
объекта mp3Player
.
1
2
3
|
func setTrackName(){
trackName.text = mp3Player?.getCurrentTrackName()
}
|
Шаг 13: setupNotificationCenter
Когда песня завершает воспроизведение, она должна автоматически показать название следующей песни и начать воспроизведение этой песни. Кроме того, когда пользователь нажимает кнопки воспроизведения, setTrackName
или назад, должен вызываться метод setTrackName
. Идеальным местом для этого является метод MP3Player
класса MP3Player
.
Нам нужен способ, чтобы класс ViewController
классу ViewController
о вызове метода setTrackName
. Для этого мы будем использовать класс NSNotificationCenter
. Центр уведомлений предоставляет возможность транслировать информацию по всей программе. Регистрируясь в качестве наблюдателя в центре уведомлений, объект может получать эти трансляции и выполнять операцию. Другим способом решения этой задачи было бы использование шаблона делегирования .
Добавьте следующий метод в класс ViewController
.
1
2
3
4
5
6
|
func setupNotificationCenter(){
NSNotificationCenter.defaultCenter().addObserver(self,
selector:»setTrackName»,
name:»SetTrackNameText»,
object:nil)
}
|
Сначала мы получаем ссылку на центр уведомлений по умолчанию. Затем мы вызываем метод addObserver(_:selector:name:object:)
в центре уведомлений. Этот метод принимает четыре параметра:
- объект, регистрирующийся как наблюдатель,
self
в этом случае - сообщение, которое будет отправлено наблюдателю при публикации уведомления
- название уведомления, для которого необходимо зарегистрировать наблюдателя
- объект, чьи уведомления хочет получить наблюдатель
Передавая nil
в качестве последнего аргумента, мы прослушиваем каждое уведомление с именем SetTrackNameText .
Теперь нам нужно вызвать этот метод в методе viewDidLoad
контроллера представления.
1
2
3
4
5
|
override func viewDidLoad() {
super.viewDidLoad()
mp3Player = MP3Player()
setupNotificationCenter()
}
|
Шаг 14: Публикация уведомления
Чтобы опубликовать уведомление, мы вызываем метод postNotificationName(_:object:)
в центре уведомлений по умолчанию. Как я упоминал ранее, мы сделаем это в методе MP3Player
класса MP3Player
. Откройте MP3Player.swift и обновите метод queueTrack
как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
func queueTrack(){
if(player != nil){
player = nil
}
var error:NSError?
let url = NSURL.fileURLWithPath(tracks[currentTrackIndex] as String)
player = AVAudioPlayer(contentsOfURL: url, error: &error)
if let hasError = error {
//SHOW ALERT OR SOMETHING
} else {
player?.delegate = self
player?.prepareToPlay()
NSNotificationCenter.defaultCenter().postNotificationName(«SetTrackNameText», object: nil)
}
}
|
Если вы сейчас протестируете приложение и дадите песне проигрываться до конца, оно должно автоматически начать воспроизведение следующей песни. Но вам может быть интересно, почему название песни не появляется во время первой песни. Метод init
класса MP3Player
вызывает метод queueTrack
, но, поскольку он не завершил инициализацию, он не оказывает влияния.
Все, что нам нужно сделать, это вручную вызвать метод setTrackName
после того, как мы инициализируем объект mp3Player
. Добавьте следующий код в метод viewDidLoad в ViewController.swift .
1
2
3
4
5
6
7
|
override func viewDidLoad() {
super.viewDidLoad()
mp3Player = MP3Player()
setupNotificationCenter()
setTrackName()
updateViews()
}
|
Вы заметите, что я также вызвал метод updateViews
. Таким образом, игрок показывает время 0:00 в начале. Если вы сейчас тестируете приложение, у вас должен быть полнофункциональный MP3-плеер.
Вывод
Это было довольно длинное учебное пособие, но теперь у вас есть функциональный MP3-плеер для создания и расширения. Одно из предложений — позволить пользователю выбрать песню для воспроизведения, реализовав UITableView
под проигрывателем. Спасибо за чтение, и я надеюсь, что вы узнали что-то полезное.