Swift - 制作一个在线流媒体音乐播放器(使用StreamingKit库)
本文演示如何使用第三方的 StreamingKit 库,来实现网络流音频的播放。
一、StreamingKit介绍和配置
1,基本介绍
(1)StreamingKit 是一个适用于 iOS 和 Mac OSX 的音频播放流媒体库。StreamingKit 提供了一个简洁的面向对象 API,用于在 CoreAudio 框架下进行音频的解压和播放(采用硬件或软件编解码器)处理。
(2)StreamingKit 的主要机制是对从播放器输入的数据源进行解耦,从而使高级定制的数据源可以进行诸如基于流媒体的渐进式下载、编码解码、自动恢复、动态缓冲之类的处理。StreamingKit 是唯一支持不同格式音频文件无缝播放的音频播放流媒体库。
(3)Github 主页:https://github.com/tumtumtum/StreamingKit
2,主要特点
- 免费开源
- 简洁的 API
- 可读性很强的源代码
- 精心使用多线程提供了一个快速响应的 API,既能防止线程阻塞,又能保证缓冲流畅
- 缓冲并无缝播放所有不同格式的音频文件
- 容易实现的音频数据源(支持本地、HTTP、AutoRecovering HTTP 作为数据源)
- 容易 kuo 扩展数据源以支持自动缓冲、编码等
- 低耗电和低 CPU 使用率(CPU 使用率 0%,流式处理时使用率为 1%)
- 优化线性数据源,仅随机访问数据源需要搜索
- StreamingKit0.2.0 使用 AudioUnit API 而不是速度较慢的音频队列 API,允许对原始 PCM 数据进行实时截取以获得并行测量、EQ 等特征
- 电能计量
- 内置的均衡器(iOS5.0 及以上版本、OSX10.9 及以上版本)支持音频播放的同时动态改变、启用、禁用均衡器
- 提供了 iOS 和 Mac OSX 应用实例
3,安装配置
(1)将源码包下载下来后,将其中的 StreamingKit/StreamingKit 文件夹复制到项目中来。

(2)创建桥接头文件,内容如下:
1 | # import "STKAudioPlayer.h" |
二、制作一个网络音频播放器
1,效果图
(1)程序运行后自动开始播放音乐(整个队列一个有 3 首歌曲,默认先播放第一首)
(2)点击“上一曲”“下一曲”按钮可以切换当前播放歌曲。
(3)歌曲播放过程中进度条会随之变化,进度条右侧会显示出当前歌曲播放时间。
(4)进度条可以拖动,拖动结束后自动播放该时间点的音乐。
(5)点击“暂停”按钮可以交替切换播放器暂停、继续状态。
(6)点击“结束”按钮,结束整个播放器的音乐播放。
(2)为了让播放器能在后台持续播放,我们需要将 Targets -> Capabilities -> BackgroundModes 设为 ON,同时勾选“Audio, AirPlay, and Picture in Picture”。
(3)主视图代码(ViewController.swift)
源码下载:
hangge_1667.zip

2,实现步骤
(1)在 info.plist 中添加如下配置以支持 http 传输。
1 2 3 4 5 | <key> NSAppTransportSecurity </key> <dict> <key> NSAllowsArbitraryLoads </key> < true /> </dict> |
(2)为了让播放器能在后台持续播放,我们需要将 Targets -> Capabilities -> BackgroundModes 设为 ON,同时勾选“Audio, AirPlay, and Picture in Picture”。

同时还要在 AppDelegate.swift 中注册后台播放。
1 2 3 4 5 6 7 8 9 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 | import UIKit import AVFoundation @UIApplicationMain class AppDelegate : UIResponder , UIApplicationDelegate { var window: UIWindow ? func application(_ application: UIApplication , didFinishLaunchingWithOptions launchOptions: [ UIApplicationLaunchOptionsKey : Any ]?) -> Bool { // 注册后台播放 let session = AVAudioSession .sharedInstance() do { try session.setActive( true ) try session.setCategory( AVAudioSessionCategoryPlayback ) } catch { print (error) } return true } func applicationWillResignActive(_ application: UIApplication ) { } func applicationDidEnterBackground(_ application: UIApplication ) { } func applicationWillEnterForeground(_ application: UIApplication ) { } func applicationDidBecomeActive(_ application: UIApplication ) { } func applicationWillTerminate(_ application: UIApplication ) { } } |
(3)主视图代码(ViewController.swift)
1 2 3 4 5 6 7 8 9 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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | import UIKit class ViewController : UIViewController { //显示歌曲标题 @IBOutlet weak var titleLabel: UILabel ! //暂停按钮 @IBOutlet weak var pauseBtn: UIButton ! //可拖动的进度条 @IBOutlet weak var playbackSlider: UISlider ! //当前播放时间标签 @IBOutlet weak var playTime: UILabel ! //更新进度条定时器 var timer: Timer ! //音频播放器 var audioPlayer: STKAudioPlayer ! //播放列表 var queue = [ Music (name: "歌曲1" , Music (name: "歌曲2" , Music (name: "歌曲3" , //当前播放音乐索引 var currentIndex: Int = -1 //是否循环播放 var loop: Bool = false //当前播放状态 var state: STKAudioPlayerState = [] override func viewDidLoad() { super .viewDidLoad() //设置进度条相关属性 playbackSlider!.minimumValue = 0 playbackSlider!.isContinuous = false //重置播放器 resetAudioPlayer() //开始播放歌曲列表 playWithQueue(queue: queue) //设置一个定时器,每三秒钟滚动一次 timer = Timer .scheduledTimer(timeInterval: 0.1, target: self , selector: #selector(tick), userInfo: nil , repeats: true ) } //重置播放器 func resetAudioPlayer() { var options = STKAudioPlayerOptions () options.flushQueueOnSeek = true options.enableVolumeMixer = true audioPlayer = STKAudioPlayer (options: options) audioPlayer.meteringEnabled = true audioPlayer.volume = 1 audioPlayer.delegate = self } //开始播放歌曲列表(默认从第一首歌曲开始播放) func playWithQueue(queue: [ Music ], index: Int = 0) { guard index >= 0 && index < queue.count else { return } self .queue = queue audioPlayer.clearQueue() let url = queue[index].url audioPlayer.play(url) for i in 1 ..< queue.count { audioPlayer.queue(queue[ Int ((index + i) % queue.count)].url) } currentIndex = index loop = false } //停止播放 func stop() { audioPlayer.stop() queue = [] currentIndex = -1 } //单独播放某个歌曲 func play(file: Music ) { audioPlayer.play(file.url) } //下一曲 func next() { guard queue.count > 0 else { return } currentIndex = (currentIndex + 1) % queue.count playWithQueue(queue: queue, index: currentIndex) } //上一曲 func prev() { currentIndex = max (0, currentIndex - 1) playWithQueue(queue: queue, index: currentIndex) } //下一曲按钮点击 @IBAction func nextBtnTapped(_ sender: Any ) { next() } //上一曲按钮点击 @IBAction func prevBtnTapped(_ sender: Any ) { prev() } //暂停继续按钮点击 @IBAction func pauseBtnTapped(_ sender: Any ) { //在暂停和继续两个状态间切换 if self .state == .paused { audioPlayer.resume() } else { audioPlayer.pause() } } //结束按钮点击 @IBAction func stopBtnTapped(_ sender: Any ) { stop() } //定时器响应,更新进度条和时间 func tick() { if state == .playing { //更新进度条进度值 self .playbackSlider!.value = Float (audioPlayer.progress) //一个小算法,来实现00:00这种格式的播放时间 let all: Int = Int (audioPlayer.progress) let m: Int =all % 60 let f: Int = Int (all/60) var time: String = "" if f<10{ time= "0\(f):" } else { time= "\(f)" } if m<10{ time+= "0\(m)" } else { time+= "\(m)" } //更新播放时间 self .playTime!.text=time } } //拖动进度条改变值时触发 @IBAction func playbackSliderValueChanged(_ sender: Any ) { //播放器定位到对应的位置 audioPlayer.seek(toTime: Double (playbackSlider.value)) //如果当前时暂停状态,则继续播放 if state == .paused { audioPlayer.resume() } } override func didReceiveMemoryWarning() { super .didReceiveMemoryWarning() } } //Audio Player相关代理方法 extension ViewController : STKAudioPlayerDelegate { //开始播放歌曲 func audioPlayer(_ audioPlayer: STKAudioPlayer , didStartPlayingQueueItemId queueItemId: NSObject ) { if let index = (queue.index { $0.url == queueItemId as ! URL }) { currentIndex = index } } //缓冲完毕 func audioPlayer(_ audioPlayer: STKAudioPlayer , didFinishBufferingSourceWithQueueItemId queueItemId: NSObject ) { updateNowPlayingInfoCenter() } //播放状态变化 func audioPlayer(_ audioPlayer: STKAudioPlayer , stateChanged state: STKAudioPlayerState , previousState: STKAudioPlayerState ) { self .state = state if state != .stopped && state != .error && state != .disposed { } updateNowPlayingInfoCenter() } //播放结束 func audioPlayer(_ audioPlayer: STKAudioPlayer , didFinishPlayingQueueItemId queueItemId: NSObject , with stopReason: STKAudioPlayerStopReason , andProgress progress: Double , andDuration duration: Double ) { if let index = (queue.index { $0.url == audioPlayer.currentlyPlayingQueueItemId() as ! URL }) { currentIndex = index } //自动播放下一曲 if stopReason == .eof { next() } else if stopReason == .error { stop() resetAudioPlayer() } } //发生错误 func audioPlayer(_ audioPlayer: STKAudioPlayer , unexpectedError errorCode: STKAudioPlayerErrorCode ) { print ( "Error when playing music \(errorCode)" ) resetAudioPlayer() playWithQueue(queue: queue, index: currentIndex) } //更新当前播放信息 func updateNowPlayingInfoCenter() { if currentIndex >= 0 { let music = queue[currentIndex] //更新标题 titleLabel.text = "当前播放:\(music.name)" //更新暂停按钮名字 let pauseBtnTitle = self .state == .playing ? "暂停" : "继续" pauseBtn.setTitle(pauseBtnTitle, for : .normal) //设置进度条相关属性 playbackSlider!.maximumValue = Float (audioPlayer.duration) } else { //停止播放 titleLabel.text = "播放停止!" //更新进度条和时间标签 playbackSlider.value = 0 playTime.text = "--:--" } } } //歌曲类 class Music { var name: String var url: URL //类构造函数 init (name: String , url: URL ){ self .name = name self .url = url } } |
