#18 積功德App- ④ background music & audio effect 讓App更生動

Stu
彼得潘的 Swift iOS / Flutter App 開發教室
10 min readDec 3, 2023

在我們寫程式控制音效的使用時機之前,第一步當然就是找到好的音檔!

音效準備

木魚的音效真的有點難找,所以我利用之前學姊的GitHub檔案中的音檔值接下載(這裡先謝謝學姊,音效找得好適合!我好懶XD)

背景音樂準備

在youtube找到自己喜歡的背景音樂 > 複製網址 > 到下方網站貼上網址就可以選擇你要下載mp3, mp4檔案了

音檔匯入Xcode

音檔準備好之後,直接拖曳到 Navigator area 之中即可,放到Assets之中會找不到而出現錯誤

這裡建議音檔的檔名可以更改為簡潔清楚的,因為程式呼叫時會用到

拖曳進去之後記得勾選

✔️ Destination: Copy items if needed

✔️ Add to targets: 你的專案名稱

音效設定為點擊按鈕時播放

部分程式碼如下

class ViewController: UIViewController {

//宣告播放器
let woodenPlayer = AVPlayer()
let bellPlayer = AVPlayer()
var touchPlayer = AVPlayer()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//設定起始畫面

//宣告音效的player和url
let bellUrl = Bundle.main.url(forResource: "bellSound", withExtension: "mp3")!
let bellPlayerItam = AVPlayerItem(url: bellUrl)

let woodenUrl = Bundle.main.url(forResource: "muyuSound", withExtension: "mp3")!
let woodenPlayerItem = AVPlayerItem(url: woodenUrl)

bellPlayer.replaceCurrentItem(with: bellPlayerItam)
woodenPlayer.replaceCurrentItem(with: woodenPlayerItem)
// 這裡要先把touchPlayer賦予woodenPlayer值,是因為App一開始所使用的音效,將其預設為木魚的音效
touchPlayer = woodenPlayer
}
}

viewDidLoad() 中的變數宣告

這裡特別說明為何這些播放器就是在 ViewDidLoad() 這個function 之外宣告的>>>>>

因為function執行完之後裡面的變數就會被消除掉,以節省資源,所以如果我們在ViewDidLoad() 裡面宣告這些變數,當function被執行時,這些變數確實會存在,但當Xcode跑完這個funcrtion時就會無情的把他們抹殺掉,因此App來不及播放你的音樂,就結束了QQ

所以我們會在 ViewDidLoad() 之外先創建好變數,接著!在ViewDidLoad() 執行時將程式的”結果“賦予值給在外面宣告的變數(存回外面的變數之中)這樣值就不會消失摟~讓我們後續可以使用。

如何從Xcode讀取音樂檔案

以大笨鐘音效舉例

//宣告音效的player和url
let bellUrl = Bundle.main.url(forResource: "bellSound", withExtension: "mp3")!
let bellPlayerItam = AVPlayerItem(url: bellUrl)

bellPlayer.replaceCurrentItem(with: bellPlayerItam)

如上圖我們提到直接把音樂檔案拖曳到Navigator area之中,所以音樂檔案其實也會在整個App這個資料夾之中,我們的目的使利用url去尋找這個檔案,url代表了檔案的路徑,除了網址之外,也可以代表本地電腦中的資料夾位置

Bundle意思為一束或一包可以想像成整個一包App程式,其中包含了音樂檔案,所以 “Bundle.main.url” 可以解讀成整包App資料夾中的 ”url” 其中

forResource: 表示檔案的名字(所以前面才說音樂檔名自己取一個簡單明瞭的好)

withExtension: 傳入音樂檔案的副檔名

音效如何隨著segmented control 而切換

    //木魚聲音的player
let woodenPlayer = AVPlayer()
//鐘聲音的player
let bellPlayer = AVPlayer()
//這比較特別叫做觸碰player意思是觸碰到按鈕時我們要發出的聲響
var touchPlayer = AVPlayer()
//背景音樂的player
var looper : AVPlayerLooper?

上方程式碼中有一個比較特別的player 叫做touchPlayer 顧名思義是觸碰到按鈕時我們要發出的聲響(音效),因為我們的專案是有兩種敲擊聲音的:木魚、大笨鐘。而我們的介面是利用segmented control 去做切換,切換畫面的同時也切換要使用的音效,所以我們是利用“var”去宣告它,因為var有一個特性是:用var宣告的變數是可以做修改的(賦予新的值)相反let宣告的變數就不能修改!

下方程式碼是segmented control的@IBAction 利用簡單的 if else 去判斷現在要顯示的圖案以及要使用的音效因為在 viewDidLoad() 之中我們已經分別宣告好woodenPlayer, bellPlayer 且也把值存回變數之中,所以值是存在的,不會因為viewDidLoad() 這個function 使用完而消失。接著我們在下方segmented control去判斷目前所使用的”音效“ 將woodenPlayer, bellPlayer其中一個的值賦予給touchPlayer,讓我們在Button的function呼叫它,這樣就可以正確地在對的情境使用音效摟~

@IBAction func changePage(_ sender: Any) {

if selectedSegment.selectedSegmentIndex == 0 {
muyuBell.setImage(UIImage(named: "wooden_fish"), for: .normal)
// 音效的選則,這裡是需要使用到木魚的音效,將值賦予給touchplayer
touchPlayer = woodenPlayer
} else if selectedSegment.selectedSegmentIndex == 1 {
muyuBell.setImage(UIImage(named: "bell"), for: .normal)
// 音效的選則,這裡是需要使用到大笨鐘的音效,將值賦予給touchplayer
touchPlayer = bellPlayer
}
}

在Button 的@IBAction 中呼叫touchPlayer(音效)

前面我們完成了音效的匯入、讀取url、存入變數等等終於剩下最後一個步驟了!

一個音檔若想要在播放一次需要將音檔的進度條拉回開頭,才可以再放。在Xcode裡的音樂播放之後,Xcode中的媒體檔案播放位置為.end也就是播完了,如果要在播放一次就必須把播放位置條為0(一開始)

舉例來說這就像是我們在看一些影片時,若想要從頭開始看依樣要把播放的時間拉回到開頭

@IBAction func touchWoodenfish() {
// 呼叫播放器放音樂
touchPlayer.play()
//將音檔的進度條拉回開頭(.zero)
self.touchPlayer.seek(to: .zero)
}

背景音樂設定為重複循環播放

我們希望“背景音樂”是在程式開啟時,就會自動播放,因此會將音樂的播放放在 ViewDidLoad() 這的function之中。

我們可以發現有背景音樂的讀取與音效有許相同之處,如url的讀取方式、playItem的宣告

差別在於我們會宣告一個序列播放器去當作我們背景音樂的播放器,並利用looper去設定要循環播放的播放器以及音檔的url

最後在呼叫播放器:backgroundPlayer.play()去讓背景音樂播放

class ViewController: UIViewController {

//宣告播放器
var looper : AVPlayerLooper?

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//設定起始畫面, 背景音樂重複回放
let backgroundUrl = Bundle.main.url(forResource: "backgroundmusic", withExtension: "mp3")!
let playerItem = AVPlayerItem(url: backgroundUrl)
let backgroundPlayer = AVQueuePlayer()
self.looper = AVPlayerLooper(player: backgroundPlayer, templateItem: playerItem)
backgroundPlayer.play()
}
}

--

--