Swift: NotificationCenter's addObserver closure keeps getting triggered constantly - swift

I have an AVPlayer to play an MP3 stream. I added an observer for when the stream is ended, the trouble is the closure seems to be stuck in a loop after it's triggered for the first time.
//// Variables
private var periodicTimeObserverToken: Any?
private var finishObserverToken: Any?
//// Setting up the player
self.playerItem = AVPlayerItem(url: filePath)
self.player = AVPlayer(playerItem: self.playerItem)
removePeriodicTimeObserver()
removePlayerFinishedObserver()
addPeriodicTimeObserver()
addPlayerFinishedObserver()
///
private func addPeriodicTimeObserver() {
periodicTimeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.05, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main) { [weak self] (CMTime) -> Void in
if self?.player?.currentItem?.status == .readyToPlay {
/// Calling some other external delegates
}
}
}
private func removePeriodicTimeObserver() {
if let token = periodicTimeObserverToken {
self.player?.removeTimeObserver(token)
periodicTimeObserverToken = nil
}
}
private func addPlayerFinishedObserver() {
finishObserverToken = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.pause()
}
}
private func removePlayerFinishedObserver() {
if let _ = finishObserverToken, let _player = self.player, let _playerCurrentItem = _player.currentItem {
NotificationCenter.default.removeObserver(_player)
NotificationCenter.default.removeObserver(_playerCurrentItem)
self.finishObserverToken = nil
}
}
public func endSession() {
player?.pause()
removePeriodicTimeObserver()
removePlayerFinishedObserver()
playerItem = nil
player = nil
}
The parent class calls endSession() after stream reaches the end, but self?.player?.pause() from addPlayerFinishedObserver() gets called non stop, anything inside the addObserver closure gets called continuously.
Am I doing something wrong?

I had to remove its token inside the closure:
private func addPlayerFinishedObserver() {
finishObserverToken = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.pause()
if let token = self?.finishObserverToken {
NotificationCenter.default.removeObserver(token)
}
}
}

Related

AVPlayer message was received but not handled

I am trying to implement a singleton class with AVPlayer in it. The key value observing throws exception. I think the object of this class is getting dealloced.
import Foundation
import UIKit
import AVKit
class Player: NSObject, ObservableObject {
var player : AVPlayer!
var playerController = AVPlayerViewController()
var presentingVC : UIViewController!
static let shared = Player()
private override init() { }
func play(urlString: String) {
let auth = Authentication()
let headers: [String: String] = ["x-auth-token" : auth.token]
let url = URL(string: urlString)
let asset = AVURLAsset(url: url!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
let playerItem = AVPlayerItem(asset: asset)
//let avPlayer = AVPlayer(url: url!)
player = AVPlayer(playerItem: playerItem)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: nil)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem.status), options: [.new, .initial], context: nil)
let center = NotificationCenter()
center.addObserver(self, selector: Selector(("newErrorLogEntry")), name: .AVPlayerItemNewErrorLogEntry, object: player.currentItem)
center.addObserver(self, selector: Selector(("failedToPlayTillEnd")), name: .AVPlayerItemFailedToPlayToEndTime, object: player.currentItem)
playerController.player = player
presentingVC.present(playerController, animated: true) {
self.player.play()
}
}
func newErrorLogEntry(_ notification: Notification) {
guard let object = notification.object, let playerItem = object as? AVPlayerItem else {
return
}
guard let errorLog: AVPlayerItemErrorLog = playerItem.errorLog() else {
return
}
print("2")
NSLog("Error: \(errorLog)")
}
func failedToPlayToEndTime(_ notification: Notification) {
let error = notification.userInfo!["AVPlayerItemFailedToPlayToEndTimeErrorKey"]
print("3")
NSLog("Error: \(String(describing: error))")
DispatchQueue.main.async {
let alert = UIAlertController(title: "Playback error {}{}", message: "Unable to Play Channel {}{} \n", preferredStyle: UIAlertController.Style.alert)
self.playerController.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { (UIAlertAction) in
self.playerController.dismiss(animated: true, completion: nil)
print("Cancel")
}))
}
}
}
Calling from ViewController class
Player.shared.presentingVC = self
Player.shared.play(urlString: url)
Following is the exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<VideoPlayer_v2.Player: 0x600001f84cc0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: status
Observed object: <AVPlayer: 0x600001dc1550>
Change: {
kind = 1;
new = 0;
}
Looks like AVPlayer.status message is received. But the object is already de-allocated. Correct me if I am wrong.
What is the best way to separate out AVPlayer functions in a separate class other than UIViewController class?
The reason for the exception is that you are adding observers inside your Player class, but you are not observing the values. To do that, add this method in side Player class then you handle the observed values as you need:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as AnyObject? === player {
if keyPath == "status" {
// Do something like this or whatever you need to do:
if player.status == .readyToPlay {
player.play()
}
} else if keyPath == "currentItem.status" {
// Do something
} else {
// Do something
}
}
}

Execute func after first func

self.werteEintragen() should start after weatherManager.linkZusammenfuegen() is done. Right now I use DispatchQueue and let it wait two seconds. I cannot get it done with completion func because I dont know where to put the completion function.
This is my first Swift file:
struct DatenHolen {
let fussballUrl = "deleted="
func linkZusammenfuegen () {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString)
}
func perfromRequest(urlString: String)
{
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error != nil{
print(error!)
return
}
if let safeFile = gettingInfo {
self.parseJSON(datenEintragen: safeFile)
}
}
task.resume()
}
}
func parseJSON(datenEintragen: Data) {
let decoder = JSONDecoder()
do {
let decodedFile = try decoder.decode(JsonDaten.self, from: datenEintragen)
TeamOne = decodedFile.data[0].home_name
} catch {
print(error)
}
}
}
And this is my second Swift File as Viewcontroller.
class HauptBildschirm: UIViewController {
func werteEintragen() {
Tone.text = TeamOne
}
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [unowned self] in
self.werteEintragen()
}
}
}
How can I implement this and where?
func firstTask(completion: (_ success: Bool) -> Void) {
// Do something
// Call completion, when finished, success or faliure
completion(true)
}
firstTask { (success) in
if success {
// do second task if success
secondTask()
}
}
You can have a completion handler which will notify when a function finishes, also you could pass any value through it. In your case, you need to know when a function finishes successfully.
Here is how you can do it:
func linkZusammenfuegen (completion: #escaping (_ successful: Bool) -> ()) {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString, completion: completion)
}
func perfromRequest(urlString: String, completion: #escaping (_ successful: Bool) -> ()) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
guard error == nil else {
print("Error: ", error!)
completion(false)
return
}
guard let safeFile = gettingInfo else {
print("Error: Getting Info is nil")
completion(false)
return
}
self.parseJSON(datenEintragen: safeFile)
completion(true)
}
task.resume()
} else {
//can't create URL
completion(false)
}
}
Now, in your second view controller, call this func like this:
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen { [weak self] successful in
guard let self = self else { return }
DispatchQueue.main.async {
if successful {
self.werteEintragen()
} else {
//do something else
}
}
}
}
I highly recommend Google's Promises Framework:
https://github.com/google/promises/blob/master/g3doc/index.md
It is well explained and documented. The basic concept works like this:
import Foundation
import Promises
struct DataFromServer {
var name: String
//.. and more data fields
}
func fetchDataFromServer() -> Promise <DataFromServer> {
return Promise { fulfill, reject in
//Perform work
//This block will be executed asynchronously
//call fulfill() if your value is ready
//call reject() if an error occurred
fulfill(data)
}
}
func visualizeData(data: DataFromServer) {
// do something with data
}
func start() {
fetchDataFromServer
.then { dataFromServer in
visualizeData(data: dataFromServer)
}
}
The closure after "then" will always be executed after the previous Promise has been resolved, making it easy to fulfill asynchronous tasks in order.
This is especially helpful to avoid nested closures (pyramid of death), as you can chain promises instead.

NSOperation has not deallocated

I have
class BoxSizeViewDetailController: UIViewController {
weak var output: BoxSizeViewDetailControllerOutput?
#objc
private func save() {
SLPWait.show()
Thread.run {
let operation = MoveBoxWithSizeToAssemblyOperation(boxBarcode: self.boxBarcode, assemblyRef: self.assemblyRef, boxSize: self.boxSize)
operation.completion = { [weak self] error in
Thread.main {
SLPWait.hide()
if let error = error {
error.alert()
return
}
self?.output?.didSaved()
self?.pop()
}
}
let operationQueue = OperationQueue()
operationQueue.addOperations(operation, waitUntilFinished: true)
}
}
}
And
class MoveBoxWithSizeToAssemblyOperation: SLPAsyncOperation {
private let boxBarcode: String
private let assemblyRef: String
private let boxSize: BoxSize
var withSize: Bool = false
var completion: ((NSError?) -> Void)?
init(boxBarcode: String, assemblyRef: String, boxSize: BoxSize) {
self.boxBarcode = boxBarcode
self.assemblyRef = assemblyRef
self.boxSize = boxSize
}
deinit {
SLPLog.debug("Deinit", String(describing: self))
}
override func start() {
if isCancelled {
self.finish()
return
}
super.start()
}
override func main() {
defer {
self.finish()
}
let sendSizeOperation = SendSizeOperation(barcode: boxBarcode, boxSize: boxSize)
let moveBoxToAssemblyOperation = MoveBoxToAssemblyOperation(boxBarcode: boxBarcode, assemblyRef: assemblyRef)
sendSizeOperation.completion = { [unowned moveBoxToAssemblyOperation, weak self] error in
//Here is main thread
if let error = error {
moveBoxToAssemblyOperation.cancel()
self?.completion?(error)
}
}
moveBoxToAssemblyOperation.completion = { [weak self] error in
//Here is main thread
self?.completion?(error)
}
moveBoxToAssemblyOperation.addDependency(sendSizeOperation)
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .userInitiated
operationQueue.addOperations([sendSizeOperation, moveBoxToAssemblyOperation], waitUntilFinished: true)
}
}
And
public extension Thread {
class func run(execute: #escaping #convention(block) () -> Void) {
DispatchQueue.global(qos: .userInitiated).async(execute: execute)
}
class func run(execute: DispatchWorkItem) {
DispatchQueue.global(qos: .userInitiated).async(execute: execute)
}
class func main(execute: #escaping #convention(block) () -> Void) {
DispatchQueue.main.async(execute: execute)
}
class func main(execute: DispatchWorkItem) {
DispatchQueue.main.async(execute: execute)
}
}
So the problem is in this part of code
Thread.run {
let operation = MoveBoxWithSizeToAssemblyOperation(boxBarcode: self.boxBarcode, assemblyRef: self.assemblyRef, boxSize: self.boxSize)
operation.completion = { [weak self] error in
Thread.main {
SLPWait.hide()
if let error = error {
error.alert()
return
}
self?.output?.didSaved()
self?.pop()
}
}
let operationQueue = OperationQueue()
operationQueue.addOperations(operation, waitUntilFinished: true)
}
SLPWait.show() and SLPWait.hide() just show and hide an ActivityIndicator.
The operations MoveBoxWithSizeToAssemblyOperation is not dealloceted (sometimes) and sometimes is dealloceted. Why is that?
My way of thinking. Because I use Thread.run to dispatch from main queue, I think I need wait until "MoveBoxWithSizeToAssemblyOperation" completes and handle completion.
But "MoveBoxWithSizeToAssemblyOperation" completes and handle "defer { self.finish }" but not deallocated.
My misunderstanding questions:
I don't understand should I set waitUntilFinished" = true ? Because I block thread.run and not the main thread though MoveBoxToAssemblyOperation completes on main thread. Is the MoveBoxToAssemblyOperation block main thread when completes ?
When I write Thread.run { should I capture [weak self] or not ?
Why MoveBoxWithSizeToAssemblyOperation is not deallocated ?

cancel button using MBProgressView

I am trying to use cancel button with the MBProgressView. I am getting the error "cannot convert value of type '()' to expected argument type 'Selector'"
hud.button.addTarget(hud.progressObject, action: cancelButton(), for: .touchUpInside)
I have also tried doing this:
hud.button.addTarget(hud.progressObject, action: #selector(cancelButton), for: .touchUpInside)
and I got the error "Argument of #selector cannot refer to local function 'cancelButton()'".
Can anyone explain to me what am i doing wrong?
cancelButton should be in viewDidLoad or at least I need to find a way to access what's inside viewDidload, because I need to use hud and snapshot.progress to cancel the download:
override func viewDidLoad() {
super.viewDidLoad()
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.orintation = UIInterfaceOrientationMask.allButUpsideDown
if book?.bookPath != book?.bookPath {
print("HERE \(book?.bookPath)")
loadReader(filePaht: (book?.bookPath)!)
} else {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let strName = book?.id
let filePath = "\(documentsPath)/"+strName!+".pdf"
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
loadReader(filePaht: filePath)
return;
}
print("DOWNLOAD #1")
let reference = FIRStorage.storage().reference(forURL: (self.book?.bookURL)!)
let downloadTask = reference.data(withMaxSize: 50 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
} else {
if ((try! data?.write(to: URL.init(fileURLWithPath: filePath, isDirectory: false))) != nil) {
self.db.upDate(id: (self.book?.id)!, bookPath: filePath)
self.loadReader(filePaht: filePath)
}
}
}
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in
DispatchQueue.global(qos: .default).async(execute: {() -> Void in
self.showHUDWithCancel("Downloading")
DispatchQueue.main.async(execute: {() -> Void in
})
})
self.hud.progressObject = snapshot.progress
}
downloadTask.observe(.success) { (snapshot) -> Void in
// Download completed successfully
print("Download Success")
SwiftLoader.hide()
}
downloadTask.observe(.failure) { (snapshot) -> Void in
//Download failed
print("Download failed")
}
}
}
func showHUDWithCancel(_ aMessage: String) {
self.hud = MBProgressHUD.showAdded(to: self.view, animated: true)
self.hud.mode = MBProgressHUDMode.annularDeterminate
self.hud.label.text = aMessage
self.hud.detailsLabel.text = "Tap to cancel"
let tap = UITapGestureRecognizer(target: self, action: #selector(cancelButton))
self.hud.addGestureRecognizer(tap)
}
func cancelButton() {
self.hud.hide(animated: true)
self.hud.progressObject?.cancel()
print("cancel button is working")
}
This is the Cancel Button function
func cancelButton() {
MBProgressHUD.hide(for: view, animated: true)
snapshot.progress?.pause()
}
Try this -
Call below showHUDWithCancel from where you want to add hud with Cancel.
class ViewController: UIViewController {
var hud = MBProgressHUD()
override func viewDidLoad() {
super.viewDidLoad()
}
func showHUDWithCancel(_ aMessage: String) {
self.hud = MBProgressHUD.showAdded(to: self.view, animated: true)
self.hud.label.text = aMessage
self.hud.detailsLabel.text = "Tap to cancel"
let tap = UITapGestureRecognizer(target: self, action: #selector(cancelButton))
self.hud.addGestureRecognizer(tap)
}
func cancelButton() {
self.hud.hide(animated: true)
// do your other stuff here.
}
}
Add this code within your viewDidLoad it will work.
override func viewDidLoad() {
super.viewDidLoad()
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.orintation = UIInterfaceOrientationMask.allButUpsideDown
if book?.bookPath != book?.bookPath {
print("HERE \(book?.bookPath)")
loadReader(filePaht: (book?.bookPath)!)
} else {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let strName = book?.id
let filePath = "\(documentsPath)/"+strName!+".pdf"
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
loadReader(filePaht: filePath)
return;
}
print("DOWNLOAD #1")
let reference = FIRStorage.storage().reference(forURL: (self.book?.bookURL)!)
downloadTask = reference.data(withMaxSize: 50 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
} else {
if ((try! data?.write(to: URL.init(fileURLWithPath: filePath, isDirectory: false))) != nil) {
self.db.upDate(id: (self.book?.id)!, bookPath: filePath)
self.loadReader(filePaht: filePath)
}
}
}
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in OperationQueue.main.addOperation {
OperationQueue.main.addOperation {
self.hud.progressObject = snapshot.progress
self.showHUDWithCancel("Downloading")
}
}
}
downloadTask.observe(.success) { (snapshot) -> Void in OperationQueue.main.addOperation {
// Download completed successfully
print("Download Success")
OperationQueue.main.addOperation {
SwiftLoader.hide()
}
}
}
downloadTask.observe(.failure) { (snapshot) -> Void in OperationQueue.main.addOperation {
//Download failed
print("Download failed")
OperationQueue.main.addOperation {
_ = self.navigationController?.popViewController(animated: false)
}
}
}
}
}
Move definition of downloadTask outside of the viewDidLoad method scope into the class itself. This way you'll be able to access task directly, not via snapshot passed in observers, or progress attached to either downloadTask or progressHUD. Doing so you could access task from any method of your view controller including cancelButton():
task.pause()
instead of
snapshot.progress?.pause()
Final code could look like:
class ViewController: UIViewController {
var downloadTask: FIRStorageDownloadTask!
...
override func viewDidLoad() {
super.viewDidLoad()
...
let reference = FIRStorage.storage().reference(forURL: (self.book?.bookURL)!)
downloadTask = reference...
...
}
}
NOTICE: For those of you who use the latest version of MBProgressView, the button documentation has been changed:
/**
* A button that is placed below the labels. Visible only if a target / action is added and a title is assigned..
*/
So, the creation should look something like the following:
class Tools {
static func popLoadingDialog(viewParent: UIView,
label: String,
cancelTarget: Any? = nil,
cancelSelector: Selector? = nil) -> MBProgressHUD {
let loadingNotification = MBProgressHUD.showAdded(to: viewParent, animated: true)
loadingNotification.mode = MBProgressHUDMode.indeterminate
loadingNotification.label.text = label
if(cancelSelector != nil) {
loadingNotification.button.setTitle("Cancel", for: .normal)
loadingNotification.button.addTarget(cancelTarget, action: cancelSelector!, for: .touchUpInside)
}
return loadingNotification
}
}
and call it:
loadingIndicator = Tools.createLoadingDialog(viewParent: view,
label: "Please wait...",
cancelTarget: self,
cancelSelector: #selector(onCancelClick))
loadingIndicator?.show(animated: true)
}
#objc func onCancelClick(){
// do something when the user click on cancel...
}

AVPlayer audio buffering in swift 3 source disconnected observer

I have app that plays AAC audio stream. Everything works fine, but when I disconnect stream and connect again after one second audio stop playing after half minute. When i don't reconnect i have error after one- two minutes. To reconnect i must stop AVPlayer and start again. I want to reconnect stream or show message immediately after player stops play music. How can I do that? Moreover i have another question: I convert my code to swift 3 and I have problem with one line:
fileprivate var playerItem = AVPlayerItem?()
error: cannot invoke initializer without argument
How i can fix that? Maybe this is the problem?
My Radio Player class:
import Foundation
import AVFoundation
import UIKit
protocol errorMessageDelegate {
func errorMessageChanged(_ newVal: String)
}
protocol sharedInstanceDelegate {
func sharedInstanceChanged(_ newVal: Bool)
}
class RadioPlayer : NSObject {
static let sharedInstance = RadioPlayer()
var instanceDelegate:sharedInstanceDelegate? = nil
var sharedInstanceBool = false {
didSet {
if let delegate = self.instanceDelegate {
delegate.sharedInstanceChanged(self.sharedInstanceBool)
}
}
}
fileprivate var player = AVPlayer(url: URL(string: Globals.radioURL)!)
// fileprivate var playerItem = AVPlayerItem?()
fileprivate 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: URL(string: Globals.radioURL)!, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronously(forKeys: [statusKey], completionHandler: {
var error: NSError? = nil
DispatchQueue.main.async(execute: {
let status: AVKeyValueStatus = asset.statusOfValue(forKey: 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!)
}
})
})
NotificationCenter.default.addObserver(
forName: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
object: nil,
queue: nil,
using: { notification in
print("Status: Failed to continue")
self.errorMessage = NSLocalizedString("STREAM_INTERUPT", comment:"Stream was interrupted")
})
print("Initializing new player")
}
func resetPlayer() {
errorMessage = ""
let asset: AVURLAsset = AVURLAsset(url: URL(string: Globals.radioURL)!, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronously(forKeys: [statusKey], completionHandler: {
var error: NSError? = nil
DispatchQueue.main.async(execute: {
let status: AVKeyValueStatus = asset.statusOfValue(forKey: 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() -> TimeInterval {
// 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 as NSArray
if timeRangeArray.count < 1 { return(CMTimeGetSeconds(kCMTimeInvalid)) }
let aTimeRange: CMTimeRange = (timeRangeArray.object(at: 0) as AnyObject).timeRangeValue
// let startTime = CMTimeGetSeconds(aTimeRange.end)
let loadedDuration = CMTimeGetSeconds(aTimeRange.duration)
return (TimeInterval)(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
}
}
I will be grateful for help ;)
For the second issue fileprivate var playerItem = AVPlayerItem?()
write this and it should work fileprivate var playerItem: AVPlayerItem?.
For the first issue
when I disconnect stream and connect again after one second audio stop
playing after half minute. When i don't reconnect i have error after
one- two minutes. To reconnect i must stop AVPlayer and start again. I
want to reconnect stream or show message immediately after player
stops play music. How can I do that?
I don't get what's wrong ? You pause the player by pressing the button then you press the button again and after one - two minutes it stops by itself ?
I have tested the same class today and it works just fine, even after the connection is lost to the server (when the connection resumes you can click the play button and it will play)
I'll leave you my code here, give it a try
import Foundation
import AVFoundation
import UIKit
protocol errorMessageDelegate {
func errorMessageChanged(newVal: String)
}
protocol sharedInstanceDelegate {
func sharedInstanceChanged(newVal: Bool)
}
class RadioPlayer : NSObject {
static let sharedInstance = RadioPlayer()
var instanceDelegate:sharedInstanceDelegate? = nil
var sharedInstanceBool = false {
didSet {
if let delegate = self.instanceDelegate {
delegate.sharedInstanceChanged(newVal: self.sharedInstanceBool)
}
}
}
private var player = AVPlayer(url: NSURL(string: "<# YOUR STREAM HERE #>")! as URL)
private var playerItem: AVPlayerItem?
private var isPlaying = false
var errorDelegate:errorMessageDelegate? = nil
var errorMessage = "" {
didSet {
if let delegate = self.errorDelegate {
delegate.errorMessageChanged(newVal: self.errorMessage)
}
}
}
override init() {
super.init()
errorMessage = ""
let asset: AVURLAsset = AVURLAsset(url: NSURL(string: "<# YOUR STREAM HERE #>")! as URL, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronously(forKeys: [statusKey], completionHandler: {
var error: NSError? = nil
DispatchQueue.main.async(execute: {
let status: AVKeyValueStatus = asset.statusOfValue(forKey: 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!)
}
})
})
NotificationCenter.default.addObserver(
forName: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
object: nil,
queue: nil,
using: { 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: "<# YOUR STREAM HERE #>")! as URL, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronously(forKeys: [statusKey], completionHandler: {
var error: NSError? = nil
DispatchQueue.main.async(execute: {
let status: AVKeyValueStatus = asset.statusOfValue(forKey: 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() -> TimeInterval {
// 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 as NSArray
if timeRangeArray.count < 1 { return(CMTimeGetSeconds(kCMTimeInvalid)) }
let aTimeRange: CMTimeRange = (timeRangeArray.object(at: 0) as AnyObject).timeRangeValue
//let startTime = CMTimeGetSeconds(aTimeRange.end)
let loadedDuration = CMTimeGetSeconds(aTimeRange.duration)
return (TimeInterval)(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
}
}