阿杰,使用 AVPlayer 播放在线音频 时,和视频类似,但有一些特别需要注意的点,我帮你整理如下:
1. URL 与网络要求
- 确保 URL 是 有效的音频文件地址,常见格式:
mp3
,aac
,m4a
。 - URL 支持 HTTPS,iOS 9+ 默认要求 ATS (App Transport Security) 支持 HTTPS,如果是 HTTP,需要在
Info.plist
添加例外:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
2. AVPlayer 的创建
import AVFoundation
let url = URL(string: "https://example.com/audio.mp3")!
let playerItem = AVPlayerItem(url: url)
let player = AVPlayer(playerItem: playerItem)
player.play()
⚠️ 注意:
AVPlayer
默认不会缓冲完整音频,需要依赖网络和系统缓存。- 对于长音频,建议观察缓冲状态,避免播放中断。
3. 缓冲状态监控
playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "loadedTimeRanges" {
if let timeRanges = player.currentItem?.loadedTimeRanges,
let timeRange = timeRanges.first?.timeRangeValue {
let bufferedTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration)
print("已缓冲: \(bufferedTime) 秒")
}
}
}
- 可根据缓冲时间调整播放逻辑,比如 等待缓冲完成再播放。
4. 后台播放支持
- 如果希望音频在 后台播放,需配置:
Info.plist
添加:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
- 设置音频会话:
import AVFoundation
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try? AVAudioSession.sharedInstance().setActive(true)
5. 处理中断和网络变化
- 注册通知处理中断(如来电、锁屏):
NotificationCenter.default.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: nil)
@objc func handleInterruption(notification: Notification) {
// 判断中断类型,暂停或恢复播放
}
- 网络切换或断网可能导致播放失败,需要监听:
playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
并根据 AVPlayerItem.Status.failed
做重试或提示用户。
6. 音频格式和缓存
- 常见格式推荐:
mp3
,aac
,m4a
,兼容性最好。 - 如果需要频繁播放同一音频,可以先下载到本地再播放,提高流畅度和减少网络依赖。
7. 总结
注意点 | 建议做法 |
---|---|
网络缓冲 | 监听 loadedTimeRanges ,避免卡顿 |
后台播放 | 配置 UIBackgroundModes + AVAudioSession |
中断处理 | 监听 AVAudioSession 中断通知,暂停/恢复 |
网络失败 | 监听 status ,做重试或提示 |
URL 安全 | 尽量使用 HTTPS,ATS 配置合适 |
音频格式 | 推荐 mp3/aac/m4a,避免不支持格式 |
阿杰,如果你愿意,我可以帮你写一个 完整 AVPlayer 在线音频播放器封装类,支持:
- 播放/暂停
- 缓冲进度显示
- 后台播放
- 网络中断重试
这样就可以直接在项目里使用。
明白阿杰,我帮你写一个 Swift 封装类 OnlineAudioPlayer
,实现 在线音频播放、缓冲监控、后台播放、网络中断处理,方便直接使用。
import AVFoundation
import UIKit
class OnlineAudioPlayer: NSObject {
static let shared = OnlineAudioPlayer()
private var player: AVPlayer?
private var playerItem: AVPlayerItem?
/// 播放音频
/// - Parameter urlString: 在线音频 URL
func play(urlString: String) {
guard let url = URL(string: urlString) else { return }
// 创建 AVPlayerItem
playerItem = AVPlayerItem(url: url)
// 监听缓冲状态
playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
// 初始化 AVPlayer
player = AVPlayer(playerItem: playerItem)
player?.automaticallyWaitsToMinimizeStalling = true
// 后台播放设置
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try? AVAudioSession.sharedInstance().setActive(true)
// 播放
player?.play()
// 监听播放结束
NotificationCenter.default.addObserver(self,
selector: #selector(audioDidFinish),
name: .AVPlayerItemDidPlayToEndTime,
object: playerItem)
}
/// 暂停播放
func pause() {
player?.pause()
}
/// 停止播放并释放
func stop() {
player?.pause()
removeObservers()
player = nil
playerItem = nil
}
// MARK: - KVO 监听
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let item = object as? AVPlayerItem else { return }
if keyPath == "loadedTimeRanges" {
if let timeRange = item.loadedTimeRanges.first?.timeRangeValue {
let bufferedTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration)
print("已缓冲: \(bufferedTime) 秒")
}
} else if keyPath == "status" {
switch item.status {
case .readyToPlay:
print("准备播放")
case .failed:
print("播放失败: \(item.error?.localizedDescription ?? "未知错误")")
default:
break
}
}
}
@objc private func audioDidFinish() {
print("音频播放完成")
stop()
}
// 移除 KVO 和通知
private func removeObservers() {
if let item = playerItem {
item.removeObserver(self, forKeyPath: "loadedTimeRanges")
item.removeObserver(self, forKeyPath: "status")
}
NotificationCenter.default.removeObserver(self)
}
}
使用示例
// 播放音频
OnlineAudioPlayer.shared.play(urlString: "https://www.example.com/audio.mp3")
// 暂停播放
OnlineAudioPlayer.shared.pause()
// 停止播放
OnlineAudioPlayer.shared.stop()
功能特点
- 支持 在线音频播放(mp3 / aac / m4a 等)
- 缓冲进度监控,可根据需要更新 UI
- 后台播放支持(来电或锁屏仍能继续播放)
- 播放完成自动停止
- 错误监听,支持网络失败提示
明白阿杰,我帮你升级 OnlineAudioPlayer
,增加以下功能:
- 网络中断自动重试
- 缓冲进度回调(可更新 UI)
- 播放进度回调(可更新进度条)
- 支持外部监听播放状态(开始/暂停/完成/失败)
import AVFoundation
import UIKit
protocol OnlineAudioPlayerDelegate: AnyObject {
func playbackDidStart()
func playbackDidPause()
func playbackDidFinish()
func playbackDidFail(error: Error?)
func playbackBufferProgress(buffered: Double)
func playbackTimeProgress(current: Double, duration: Double)
}
class OnlineAudioPlayer: NSObject {
static let shared = OnlineAudioPlayer()
private var player: AVPlayer?
private var playerItem: AVPlayerItem?
private var timeObserver: Any?
private var retryCount = 0
private let maxRetry = 3
weak var delegate: OnlineAudioPlayerDelegate?
// MARK: - 播放音频
func play(urlString: String) {
guard let url = URL(string: urlString) else { return }
retryCount = 0
startPlayer(with: url)
}
private func startPlayer(with url: URL) {
// 创建 AVPlayerItem
playerItem = AVPlayerItem(url: url)
// 监听缓冲和状态
playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
// 初始化 AVPlayer
player = AVPlayer(playerItem: playerItem)
player?.automaticallyWaitsToMinimizeStalling = true
// 后台播放
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try? AVAudioSession.sharedInstance().setActive(true)
// 播放
player?.play()
delegate?.playbackDidStart()
// 监听播放结束
NotificationCenter.default.addObserver(self,
selector: #selector(audioDidFinish),
name: .AVPlayerItemDidPlayToEndTime,
object: playerItem)
// 播放进度回调
timeObserver = player?.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1),
queue: DispatchQueue.main) { [weak self] time in
guard let self = self,
let duration = self.playerItem?.duration else { return }
let current = CMTimeGetSeconds(time)
let total = CMTimeGetSeconds(duration)
self.delegate?.playbackTimeProgress(current: current, duration: total)
}
}
// MARK: - 暂停播放
func pause() {
player?.pause()
delegate?.playbackDidPause()
}
// MARK: - 停止播放
func stop() {
player?.pause()
removeObservers()
player = nil
playerItem = nil
}
// MARK: - KVO 监听
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let item = object as? AVPlayerItem else { return }
if keyPath == "loadedTimeRanges" {
if let timeRange = item.loadedTimeRanges.first?.timeRangeValue {
let bufferedTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration)
delegate?.playbackBufferProgress(buffered: bufferedTime)
}
} else if keyPath == "status" {
switch item.status {
case .readyToPlay:
print("准备播放")
case .failed:
print("播放失败: \(item.error?.localizedDescription ?? "未知错误")")
retryOrFail()
default:
break
}
}
}
// MARK: - 播放完成
@objc private func audioDidFinish() {
delegate?.playbackDidFinish()
stop()
}
// MARK: - 网络中断自动重试
private func retryOrFail() {
guard retryCount < maxRetry, let urlAsset = playerItem?.asset as? AVURLAsset else {
delegate?.playbackDidFail(error: playerItem?.error)
stop()
return
}
retryCount += 1
print("播放失败,正在重试 \(retryCount)/\(maxRetry)")
startPlayer(with: urlAsset.url)
}
// MARK: - 移除 KVO 和通知
private func removeObservers() {
if let item = playerItem {
item.removeObserver(self, forKeyPath: "loadedTimeRanges")
item.removeObserver(self, forKeyPath: "status")
}
if let observer = timeObserver {
player?.removeTimeObserver(observer)
timeObserver = nil
}
NotificationCenter.default.removeObserver(self)
}
}
使用示例
class ViewController: UIViewController, OnlineAudioPlayerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
OnlineAudioPlayer.shared.delegate = self
OnlineAudioPlayer.shared.play(urlString: "https://www.example.com/audio.mp3")
}
func playbackDidStart() {
print("播放开始")
}
func playbackDidPause() {
print("播放暂停")
}
func playbackDidFinish() {
print("播放完成")
}
func playbackDidFail(error: Error?) {
print("播放失败: \(error?.localizedDescription ?? "未知错误")")
}
func playbackBufferProgress(buffered: Double) {
print("已缓冲: \(buffered) 秒")
}
func playbackTimeProgress(current: Double, duration: Double) {
print("播放进度: \(current)/\(duration)")
}
}
功能亮点
- 网络中断自动重试(最大 3 次)
- 缓冲进度实时回调
- 播放进度实时回调
- 后台播放支持
- 播放完成自动停止
- 播放状态回调给外部 UI 使用
阿杰,这个封装类几乎可以直接在项目中使用,并且可以方便地绑定 UI,比如缓冲条、播放进度条等。
如果你愿意,我还可以帮你加一个 缓存音频到本地的功能,让重复播放同一个 URL 时无需再次下载,提高体验和节省流量。
你希望我加缓存功能吗?
发表回复