How to get meta data from a live streaming radio Service online? - swift

I am trying to get the metadata (artwork and song name) from a livestreaming audio URL.
http://page.streamerportal.com/play/ASX/44/play
http://page.streamerportal.com/play/PLS/44/play
http://page.streamerportal.com/play/AUDIO/44/play
I have tried looking around and found an accepted answer (On stack overflow) and tried it
var Player: AVPlayer!
var PlayerItem: AVPlayerItem!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://page.streamerportal.com/play/ASX/44/play")
let playBackURL = URL(string: "http://page.streamerportal.com/play/ASX/44/play")
PlayerItem = AVPlayerItem(url: playBackURL!)
self.PlayerItem.addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions(), context: nil)
Player = AVPlayer(playerItem: PlayerItem)
Player.play()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath != "timedMetadata" { return }
let data: AVPlayerItem = object as! AVPlayerItem
for item in data.timedMetadata! {
print(item.value!)
}
}
When i try this my function observeValue(.......) is never being called, can anyone please help me with this or provide a solution for how to retrieve meta data ?

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
}
}
}

How to set an observer on the queue player's currentItem?

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>) {
}
}

How in the hoot do you successfully stream a remote mp3 file in swift 4?

I am working with swift 4 and cannot successfully stream an mp3 file to my device. There are no errors to lead me anywhere. What am I missing as far as AVPlayers capabilities? It seems others have had success streaming remote mp3 files. Additionally, the server is https.
let newUrl = NSURL(string: urlMain)
let test = AVPlayer(url: newUrl as! URL)
test.play()
Create a variable of AVPlayer insider your class.
let player: AVPlayer = AVPlayer()
Then create AVPlayerItem and set to player.
func prepareToPlayAudio() {
let url = newUrl
asset = AVAsset(url: url)
let assetKeys = ["playable", "hasProtectedContent"]
playerItem = AVPlayerItem(asset: asset,
automaticallyLoadedAssetKeys: assetKeys)
playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: &playerItemContext)
player = AVPlayer(playerItem: playerItem)
}
Then add observers to track the state of playing item.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItemStatus
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
switch status {
case .readyToPlay:
// Ready to play.
case .failed:
// Player item error.
case .unknown:
// Item is not ready.
}
}
}

Get metadata from MP3 HTTP stream

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)'")
}
}
}

Swift2 setup a MTAudioProcessingTap to make a image bounce on the music beat

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()
}