如何在 iOS 中使用 AVPlayer 缓冲音频?
Posted
技术标签:
【中文标题】如何在 iOS 中使用 AVPlayer 缓冲音频?【英文标题】:How to buffer audio using AVPlayer in iOS? 【发布时间】:2016-08-08 10:50:55 【问题描述】:我想播放来自 Internet 的流式音频。我编写了播放流的代码,但它没有任何缓冲区,因此如果信号较弱,应用程序停止播放音频。这是我的代码:
import UIKit
import AVFoundation
import MediaPlayer
import AudioToolbox
class ViewController: UIViewController
var playerItem:AVPlayerItem?
var player:AVPlayer?
@IBOutlet weak var PlayButton: UIButton!
override func viewDidLoad()
super.viewDidLoad()
var buffer = AVAudioBuffer ()
let url = NSURL (string: "http://radio.afera.com.pl/afera64.aac")
playerItem = AVPlayerItem(URL: url!)
player = AVPlayer(playerItem: playerItem!)
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
@IBAction func PlayButtonTapped(sender: AnyObject)
if ((player!.rate != 0) && (player!.error == nil))
player!.pause()
PlayButton.setImage(UIImage(named:"Play"), forState: UIControlState.Normal)
else
player!.play()
PlayButton.setImage(UIImage(named:"Stop"), forState:UIControlState.Normal)
我不知道如何缓冲这个流。我搜索了苹果文档,但在 Swift 中找不到任何内容。
我找到了类似AVAudioBuffer
的东西,但我不知道如何使用它,也不知道它是否正确解决了这个问题。
附: C#
在 MSDN
上有文档,如果是 Swift,Apple 上是否有类似的东西?
【问题讨论】:
Swift 文档:developer.apple.com/library/ios/documentation/Swift/Conceptual/… // iOS 文档:developer.apple.com/library/ios/navigation 【参考方案1】:答案是创建一个错误委托,每次播放器停止时都会启动一个选择器(当网络连接中断或流未正确加载时,错误会发生变化):
这里是代表,就在我的 RadioPlayer 类之外和之上:
protocol errorMessageDelegate
func errorMessageChanged(newVal: String)
类:
import Foundation
import AVFoundation
import UIKit
class RadioPlayer : NSObject
static let sharedInstance = RadioPlayer()
var instanceDelegate:sharedInstanceDelegate? = nil
var sharedInstanceBool = false
didSet
if let delegate = self.instanceDelegate
delegate.sharedInstanceChanged(self.sharedInstanceBool)
private var player = AVPlayer(URL: NSURL(string: Globals.radioURL)!)
private var playerItem = AVPlayerItem?()
private var isPlaying = false
var errorDelegate:errorMessageDelegate? = nil
var errorMessage = ""
didSet
if let delegate = self.errorDelegate
delegate.errorMessageChanged(self.errorMessage)
override init()
super.init()
errorMessage = ""
let asset: AVURLAsset = AVURLAsset(URL: NSURL(string: Globals.radioURL)!, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronouslyForKeys([statusKey], completionHandler:
var error: NSError? = nil
dispatch_async(dispatch_get_main_queue(),
let status: AVKeyValueStatus = asset.statusOfValueForKey(statusKey, error: &error)
if status == AVKeyValueStatus.Loaded
let playerItem = AVPlayerItem(asset: asset)
self.player = AVPlayer(playerItem: playerItem)
self.sharedInstanceBool = true
else
self.errorMessage = error!.localizedDescription
print(error!)
)
)
NSNotificationCenter.defaultCenter().addObserverForName(
AVPlayerItemFailedToPlayToEndTimeNotification,
object: nil,
queue: nil,
usingBlock: notification in
print("Status: Failed to continue")
self.errorMessage = "Stream was interrupted"
)
print("Initializing new player")
func resetPlayer()
errorMessage = ""
let asset: AVURLAsset = AVURLAsset(URL: NSURL(string: Globals.radioURL)!, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronouslyForKeys([statusKey], completionHandler:
var error: NSError? = nil
dispatch_async(dispatch_get_main_queue(),
let status: AVKeyValueStatus = asset.statusOfValueForKey(statusKey, error: &error)
if status == AVKeyValueStatus.Loaded
let playerItem = AVPlayerItem(asset: asset)
//playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: &ItemStatusContext)
self.player = AVPlayer(playerItem: playerItem)
self.sharedInstanceBool = true
else
self.errorMessage = error!.localizedDescription
print(error!)
)
)
func bufferFull() -> Bool
return bufferAvailableSeconds() > 45.0
func bufferAvailableSeconds() -> NSTimeInterval
// Check if there is a player instance
if ((player.currentItem) != nil)
// Get current AVPlayerItem
let item: AVPlayerItem = player.currentItem!
if (item.status == AVPlayerItemStatus.ReadyToPlay)
let timeRangeArray: NSArray = item.loadedTimeRanges
if timeRangeArray.count < 1 return(CMTimeGetSeconds(kCMTimeInvalid))
let aTimeRange: CMTimeRange = timeRangeArray.objectAtIndex(0).CMTimeRangeValue
//let startTime = CMTimeGetSeconds(aTimeRange.end)
let loadedDuration = CMTimeGetSeconds(aTimeRange.duration)
return (NSTimeInterval)(loadedDuration);
else
return(CMTimeGetSeconds(kCMTimeInvalid))
else
return(CMTimeGetSeconds(kCMTimeInvalid))
func play()
player.play()
isPlaying = true
print("Radio is \(isPlaying ? "" : "not ")playing")
func pause()
player.pause()
isPlaying = false
print("Radio is \(isPlaying ? "" : "not ")playing")
func currentlyPlaying() -> Bool
return isPlaying
protocol sharedInstanceDelegate
func sharedInstanceChanged(newVal: Bool)
RadioViewController:
import UIKit
import AVFoundation
class RadioViewController: UIViewController, errorMessageDelegate, sharedInstanceDelegate
// MARK: Properties
var firstErrorSkip = true
var firstInstanceSkip = true
@IBOutlet weak var listenLabel: UILabel!
@IBOutlet weak var radioSwitch: UIImageView!
@IBAction func back(sender: AnyObject)
print("Dismissing radio view")
if let navigationController = self.navigationController
navigationController.popViewControllerAnimated(true)
@IBAction func switched(sender: AnyObject)
toggle()
override func viewDidLoad()
super.viewDidLoad()
do
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession Category Playback OK")
do
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
catch let error as NSError
print(error.localizedDescription)
catch let error as NSError
print(error.localizedDescription)
RadioPlayer.sharedInstance.errorDelegate = self
RadioPlayer.sharedInstance.instanceDelegate = self
if RadioPlayer.sharedInstance.currentlyPlaying()
radioSwitch.image = UIImage(named: "Radio_Switch_Active")
listenLabel.text = "Click to Pause Radio Stream:"
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
func toggle()
if RadioPlayer.sharedInstance.currentlyPlaying()
pauseRadio()
else
playRadio()
func playRadio()
firstErrorSkip = false
firstInstanceSkip = false
if RadioPlayer.sharedInstance.errorMessage != "" || RadioPlayer.sharedInstance.bufferFull()
resetStream()
else
radioSwitch.image = UIImage(named: "Radio_Switch_Active")
listenLabel.text = "Click to Pause Radio Stream:"
RadioPlayer.sharedInstance.play()
func pauseRadio()
RadioPlayer.sharedInstance.pause()
radioSwitch.image = UIImage(named: "Radio_Switch_Inactive")
listenLabel.text = "Click to Play Radio Stream:"
func resetStream()
print("Reloading interrupted stream");
RadioPlayer.sharedInstance.resetPlayer()
//RadioPlayer.sharedInstance = RadioPlayer();
RadioPlayer.sharedInstance.errorDelegate = self
RadioPlayer.sharedInstance.instanceDelegate = self
if RadioPlayer.sharedInstance.bufferFull()
radioSwitch.image = UIImage(named: "Radio_Switch_Active")
listenLabel.text = "Click to Pause Radio Stream:"
RadioPlayer.sharedInstance.play()
else
playRadio()
func errorMessageChanged(newVal: String)
if !firstErrorSkip
print("Error changed to '\(newVal)'")
if RadioPlayer.sharedInstance.errorMessage != ""
print("Showing Error Message")
let alertController = UIAlertController(title: "Stream Failure", message: RadioPlayer.sharedInstance.errorMessage, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
pauseRadio()
else
print("Skipping first init")
firstErrorSkip = false
func sharedInstanceChanged(newVal: Bool)
if !firstInstanceSkip
print("Detected New Instance")
if newVal
RadioPlayer.sharedInstance.play()
else
firstInstanceSkip = false
希望这会有所帮助:)
【讨论】:
谢谢! :) 我不明白一件事。这是什么:“Globals.radioURL”?项目中的一些全局设置?我可以在哪里编辑它们? 不客气。 :) 它只是 url 没有更多声明可以在任何地方使用。 如何声明全局使用带有“Global”前缀的变量? global 是自定义类,radioURL 是它的属性。 好的。现在我明白了一切。问题已解决,感谢您的耐心等待。【参考方案2】:改变
playerItem = AVPlayerItem?()
到
playerItem:AVPlayerItem?
【讨论】:
以上是关于如何在 iOS 中使用 AVPlayer 缓冲音频?的主要内容,如果未能解决你的问题,请参考以下文章