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
}
}
}
Related
I am trying to set an observer for the currentItem of the AVQueueplayer but I get an error called EXC_BAD_ACCESS. Can someone help me please? Thanks for your attention. I’m looking forward to your reply. Here is my code:
struct VideoPlayerS : UIViewControllerRepresentable {
var work : WorkoutDeS
#Binding var player : AVQueuePlayer
var playerLayer = AVPlayerLayer()
public func makeUIViewController(context: Context) -> AVPlayerViewController {
let items = [
AVPlayerItem(url: URL(fileURLWithPath: String(work.url1))),
AVPlayerItem(url: URL(fileURLWithPath: String(work.url2)))
]
let player = AVQueuePlayer(items: items)
let controller = AVPlayerViewController()
DispatchQueue.main.async {
self.player = player
}
controller.player = player
controller.videoGravity = .resizeAspectFill
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { _ in
self.player.seek(to: CMTime.zero)
self.player.play()
}
player.play()
player.currentItem?.addObserver(AVQueuePlayer(), forKeyPath: "status", options: NSKeyValueObservingOptions(), context: nil)
func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "status" {
print("Hello")
player.currentItem?.removeObserver(AVQueuePlayer(), forKeyPath: "status")
}
}
return controller
}
func rewindVideo(notification: Notification) {
playerLayer.player?.seek(to: .zero)
}
public func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayerS>) {
}
}
Representable, which is struct, cannot be used KVO observer. You can to use Coordinator as observer.
Here is modified code with possible approach:
struct VideoPlayerS : UIViewControllerRepresentable {
var work : WorkoutDeS
#Binding var player : AVQueuePlayer
var playerLayer = AVPlayerLayer()
public func makeUIViewController(context: Context) -> AVPlayerViewController {
let items = [
AVPlayerItem(url: URL(fileURLWithPath: String(work.url1))),
AVPlayerItem(url: URL(fileURLWithPath: String(work.url2)))
]
let player = AVQueuePlayer(items: items)
let controller = AVPlayerViewController()
DispatchQueue.main.async {
self.player = player
}
controller.player = player
controller.videoGravity = .resizeAspectFill
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { _ in
self.player.seek(to: CMTime.zero)
self.player.play()
}
player.currentItem?.addObserver(context.coordinator, forKeyPath: "status", options: [.new], context: nil)
player.play()
return controller
}
func makeCoordinator() -> Coordinator {
Coordinator(owner: self)
}
class Coordinator: NSObject {
var owner : VideoPlayerS
init(owner: VideoPlayerS) {
self.owner = owner
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let item = object as? AVPlayerItem else { return }
if keyPath == "status" {
print("Hello")
item.removeObserver(self, forKeyPath: "status")
}
}
}
func rewindVideo(notification: Notification) {
playerLayer.player?.seek(to: .zero)
}
public func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayerS>) {
}
}
I am making a file encryption app in Swift for iOS. I want to support video files using the stock, native system video player. I currently have code that does it well, but it falls short because of one issue - if the file is too big, the app fills the phone's memory. I wanna avoid that by streaming chunks of data to AVKit (NOT AVFoundation) and play the chunks while staying in the context of that single video. How do I do that?
Note: sending raw data IS possible with AVKit, I have found code that does it very nicely (you can pass data to an init() function that gets data and it is transformed into an AVPlayerItem. The code is complicated and I don't understand much of it, the only thing that I found is that it uses a custom URL and passes it to a normal AVPlayerItem and (I think) every time data is requested, it is being sent to that custom URL. The weirdest part is that I haven't set up ANY sort of custom URL scheme which is associated with my app's project file.
Here is the code for the Data to AVPlayerItem that I was talking about:
import Foundation
import AVFoundation
fileprivate extension URL {
func withScheme(_ scheme: String) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)
components?.scheme = scheme
return components?.url
}
}
#objc protocol CachingPlayerItemDelegate {
/// Is called when the media file is fully downloaded.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data)
/// Is called every time a new portion of data is received.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int)
/// Is called after initial prebuffering is finished, means
/// we are ready to play.
#objc optional func playerItemReadyToPlay(_ playerItem: CachingPlayerItem)
/// Is called when the data being downloaded did not arrive in time to
/// continue playback.
#objc optional func playerItemPlaybackStalled(_ playerItem: CachingPlayerItem)
/// Is called on downloading error.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, downloadingFailedWith error: Error)
}
open class CachingPlayerItem: AVPlayerItem {
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
var playingFromData = false
var mimeType: String? // is required when playing from Data
var session: URLSession?
var mediaData: Data?
var response: URLResponse?
var pendingRequests = Set<AVAssetResourceLoadingRequest>()
weak var owner: CachingPlayerItem?
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
if playingFromData {
// Nothing to load.
} else if session == nil {
// If we're playing from a url, we need to download the file.
// We start loading the file on first request only.
guard let initialUrl = owner?.url else {
fatalError("internal inconsistency")
}
startDataRequest(with: initialUrl)
}
pendingRequests.insert(loadingRequest)
processPendingRequests()
return true
}
func startDataRequest(with url: URL) {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
session?.dataTask(with: url).resume()
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
pendingRequests.remove(loadingRequest)
}
// MARK: URLSession delegate
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
mediaData?.append(data)
processPendingRequests()
owner?.delegate?.playerItem?(owner!, didDownloadBytesSoFar: mediaData!.count, outOf: Int(dataTask.countOfBytesExpectedToReceive))
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(Foundation.URLSession.ResponseDisposition.allow)
mediaData = Data()
self.response = response
processPendingRequests()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let errorUnwrapped = error {
owner?.delegate?.playerItem?(owner!, downloadingFailedWith: errorUnwrapped)
return
}
processPendingRequests()
owner?.delegate?.playerItem?(owner!, didFinishDownloadingData: mediaData!)
}
// MARK: -
func processPendingRequests() {
// get all fullfilled requests
let requestsFulfilled = Set<AVAssetResourceLoadingRequest>(pendingRequests.compactMap {
self.fillInContentInformationRequest($0.contentInformationRequest)
if self.haveEnoughDataToFulfillRequest($0.dataRequest!) {
$0.finishLoading()
return $0
}
return nil
})
// remove fulfilled requests from pending requests
_ = requestsFulfilled.map { self.pendingRequests.remove($0) }
}
func fillInContentInformationRequest(_ contentInformationRequest: AVAssetResourceLoadingContentInformationRequest?) {
// if we play from Data we make no url requests, therefore we have no responses, so we need to fill in contentInformationRequest manually
if playingFromData {
contentInformationRequest?.contentType = self.mimeType
contentInformationRequest?.contentLength = Int64(mediaData!.count)
contentInformationRequest?.isByteRangeAccessSupported = true
return
}
guard let responseUnwrapped = response else {
// have no response from the server yet
return
}
contentInformationRequest?.contentType = responseUnwrapped.mimeType
contentInformationRequest?.contentLength = responseUnwrapped.expectedContentLength
contentInformationRequest?.isByteRangeAccessSupported = true
}
func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool {
let requestedOffset = Int(dataRequest.requestedOffset)
let requestedLength = dataRequest.requestedLength
let currentOffset = Int(dataRequest.currentOffset)
guard let songDataUnwrapped = mediaData,
songDataUnwrapped.count > currentOffset else {
// Don't have any data at all for this request.
return false
}
let bytesToRespond = min(songDataUnwrapped.count - currentOffset, requestedLength)
let dataToRespond = songDataUnwrapped.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond)))
dataRequest.respond(with: dataToRespond)
return songDataUnwrapped.count >= requestedLength + requestedOffset
}
deinit {
session?.invalidateAndCancel()
}
}
fileprivate let resourceLoaderDelegate = ResourceLoaderDelegate()
fileprivate let url: URL
fileprivate let initialScheme: String?
fileprivate var customFileExtension: String?
weak var delegate: CachingPlayerItemDelegate?
open func download() {
if resourceLoaderDelegate.session == nil {
resourceLoaderDelegate.startDataRequest(with: url)
}
}
private let cachingPlayerItemScheme = "cachingPlayerItemScheme"
/// Is used for playing remote files.
convenience init(url: URL) {
self.init(url: url, customFileExtension: nil)
}
/// Override/append custom file extension to URL path.
/// This is required for the player to work correctly with the intended file type.
init(url: URL, customFileExtension: String?) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let scheme = components.scheme,
var urlWithCustomScheme = url.withScheme(cachingPlayerItemScheme) else {
fatalError("Urls without a scheme are not supported")
}
self.url = url
self.initialScheme = scheme
if let ext = customFileExtension {
urlWithCustomScheme.deletePathExtension()
urlWithCustomScheme.appendPathExtension(ext)
self.customFileExtension = ext
}
let asset = AVURLAsset(url: urlWithCustomScheme)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
super.init(asset: asset, automaticallyLoadedAssetKeys: nil)
resourceLoaderDelegate.owner = self
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)
}
/// Is used for playing from Data.
init(data: Data, mimeType: String, fileExtension: String) {
guard let fakeUrl = URL(string: cachingPlayerItemScheme + "://whatever/file.\(fileExtension)") else {
fatalError("internal inconsistency")
}
self.url = fakeUrl
self.initialScheme = nil
resourceLoaderDelegate.mediaData = data
resourceLoaderDelegate.playingFromData = true
resourceLoaderDelegate.mimeType = mimeType
let asset = AVURLAsset(url: fakeUrl)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
super.init(asset: asset, automaticallyLoadedAssetKeys: nil)
resourceLoaderDelegate.owner = self
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)
}
// MARK: KVO
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
delegate?.playerItemReadyToPlay?(self)
}
// MARK: Notification hanlers
#objc func playbackStalledHandler() {
delegate?.playerItemPlaybackStalled?(self)
}
// MARK: -
override init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?) {
fatalError("not implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
removeObserver(self, forKeyPath: "status")
resourceLoaderDelegate.session?.invalidateAndCancel()
}
}
I put it here in case anybody wants to use it. I didn't write this, this works was made by GitHub user "neekeetab". Here's the file.
If anyone can answer me about how to send AVKit a stream of data chunks that are inside of the iDevice's memory, I will be happy to hear from you. Even if you don't, thanks for stopping by!
I want to get the Metadata from a MP3 HTTP stream. The stream is played with AVPlayer.
I've tried it with the AVPlayerItemMetadataCollector but it returns nil.
The alternative is to get the specific part on the website of the stream where the current title is displayed.
Here is my player code:
let playerItem = AVPlayerItem(url: URL(string: "stream.p4.no/p5trondheim_mp3_hq")!)
player = AVPlayer(playerItem: playerItem)
player.rate = 1.0;
player.play()
What can I do?
You can observe timedMetadata on your AVPlayerItem :
{
...
// Add observer
playerItem.addObserver(self, forKeyPath: "timedMetadata", options: .new, context: nil)
...
}
// Observe
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard keyPath == "timedMetadata" else { return }
guard let meta = playerItem.timedMetadata else { return }
for metadata in meta {
if let songName = metadata.value(forKey: "value") as? String {
print("song name is '\(songName)'")
}
}
}
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
}
}
He all, is anyone able to guide me a bit how to setup a audioprocessingtap in the keypath status, my goal is to make a image bounce on the beat of the music
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
let data: AVPlayerItem = object as! AVPlayerItem
if keyPath == "status" {
let track = data.tracks[0].assetTrack
let avAudioMix = AVMutableAudioMixInputParameters.init(track: track)
let callbacks = MTAudioProcessingTapCallbacks(avAudioMix)
let session = AVAudioSession.sharedInstance()
print("SESSION \(session)")
}
}
the audio is streaming audio with avplayer
func methodOfReceivedNotification(notification: NSNotification){
self.stream = notification.object! as! NSDictionary
let stationid = notification.object!["stationid"]!
let url = NSURL(scheme: "http", host: "adomain.com", path: "/listenstream/\(stationid!)/stream2.pls")
if player != nil {
do {
PlayerItem.removeObserver(self, forKeyPath: "timedMetadata")
PlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
PlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
}
}
PlayerItem = AVPlayerItem(URL: url!)
PlayerItem.addObserver(self, forKeyPath: "timedMetadata", options: [], context: nil)
PlayerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [], context: nil)
PlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [], context: nil)
PlayerItem.addObserver(self, forKeyPath: "status", options: [], context: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "gotMetaData:", name: "AVPlayerItemMetadataOutput", object: PlayerItem)
player = AVPlayer(playerItem: PlayerItem)
NSNotificationCenter.defaultCenter().postNotificationName("StreamDataSet", object: stream)
NSNotificationCenter.defaultCenter().postNotificationName("BufferSetter", object: "buffering...")
bufferText = "buffering..."
startTimer()
}