'timedMetadata' Deprecated. Another method? <UPDATED> - swift

Been using the PlayerItem.timedMetadata (pasted below) for quite a while and has worked very well. However, it seems that Apple has marked this method as 'Deprecated in iOS 13' and might (or will) be removed.
Xcode does inform me that I have to use another method called "AVPlayerItemMetadataOutput" to which I've never tried. So, looking on the internet (google) I found nothing at all apart from the apple docs (https://developer.apple.com/documentation/avfoundation/avplayeritemmetadataoutput).
override open func observeValue(forKeyPath: String?, of: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard forKeyPath == "timedMetadata" else { return }
guard let meta = PlayerItem.timedMetadata else { return }
for metadata in meta {
if let songName = metadata.value(forKey: "value") as? String {
Variables.MediaInfo = (songName)
self.MediaBox.text = ("Now Playing \n \(songName)")
setupNowPlaying()
}
}
}
UPDATE!
I have banged my head in Apple Docs for a day or so then it hits me like a brick in the face.
class ViewController: UIViewController,AVPlayerItemMetadataOutputPushDelegate {
weak var myDelegate: AVPlayerItemMetadataOutputPushDelegate?
var playerItem: AVPlayerItem?
var player = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
print("lets go!")
let url = URL(string: "<URLREMOVED>")!
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
let metadataOutput = AVPlayerItemMetadataOutput(identifiers: nil)
metadataOutput.setDelegate(self, queue: DispatchQueue.main)
playerItem.add(metadataOutput)
player = AVPlayer(playerItem: playerItem)
player.play()
print("END")
print(playerItem.automaticallyLoadedAssetKeys.description)
}
func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
}
}
SO, this reads the stream for timed Metadata changes and then displays this output:
AVMutableMetadataItem: 0x600002064020, identifier=icy/StreamTitle, keySpace=icy, key class = __NSCFConstantString, key=StreamTitle, commonKey=title, extendedLanguageTag=(null), dataType=(null), time={102328704/44100 = 2320.379}, duration={1/44100 = 0.000}, startDate=(null), extras={\n}, value class=__NSCFString, value=Tina Turner - Way Of The World>
Neat right? SO now all I have to to is filter the icy/StreamTitle into a string and I'm golden! :D

func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
if let item = groups.first?.items.first // make this an AVMetadata item
{
item.value(forKeyPath: "value") // looking for that key bro
let Song = (item.value(forKeyPath: "value")!)
MetaData = "Now Playing: \n \(Song)" // print the results
} else {
MetaData = "MetaData Error" // No Metadata or Could not read
}

Related

Why does all formatting disappear from an NSTextView when using NSViewRepresentable and SwiftUI?

I am making a small program using SwiftUI that allows users to create rich text "notes" in an NSTextView. I have enabled all of the formatting features from NSTextView, including the ability to work with images. The program is only for macOS and not for iOS/iPadOS.
The problem I am facing is that whenever the user types anything in the NSTextView, the caret moves to the end and all formatting and images disappear.
Since I am just using the standard formatting options provided by Apple, I have not subclassed NSTextStorage or anything like that. My use-case should be pretty simple.
The program is tiny so far and the entire source code is on GitHub (https://github.com/eiskalteschatten/ScratchPad), but I'll post the relevant code here.
This is my NSViewRepresentable class for the NSTextView:
import SwiftUI
struct RichTextEditor: NSViewRepresentable {
#EnvironmentObject var noteModel: NoteModel
func makeNSView(context: Context) -> NSScrollView {
let scrollView = NSTextView.scrollableTextView()
guard let textView = scrollView.documentView as? NSTextView else {
return scrollView
}
textView.isRichText = true
textView.allowsUndo = true
textView.allowsImageEditing = true
textView.allowsDocumentBackgroundColorChange = true
textView.allowsCharacterPickerTouchBarItem = true
textView.isAutomaticLinkDetectionEnabled = true
textView.displaysLinkToolTips = true
textView.isAutomaticDataDetectionEnabled = true
textView.isAutomaticTextReplacementEnabled = true
textView.isAutomaticDashSubstitutionEnabled = true
textView.isAutomaticSpellingCorrectionEnabled = true
textView.isAutomaticQuoteSubstitutionEnabled = true
textView.isAutomaticTextCompletionEnabled = true
textView.isContinuousSpellCheckingEnabled = true
textView.usesAdaptiveColorMappingForDarkAppearance = true
textView.usesInspectorBar = true
textView.usesRuler = true
textView.usesFindBar = true
textView.usesFontPanel = true
textView.importsGraphics = true
textView.delegate = context.coordinator
context.coordinator.textView = textView
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
context.coordinator.textView?.textStorage?.setAttributedString(noteModel.noteContents)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, NSTextViewDelegate {
var parent: RichTextEditor
var textView : NSTextView?
init(_ parent: RichTextEditor) {
self.parent = parent
}
func textDidChange(_ notification: Notification) {
guard let _textView = notification.object as? NSTextView else {
return
}
self.parent.noteModel.noteContents = _textView.attributedString()
}
}
}
On GitHub: https://github.com/eiskalteschatten/ScratchPad/blob/main/ScratchPad/Notes/RichTextEditor.swift
And this is my NoteModel class responsible for managing the NSTextView content:
import SwiftUI
import Combine
final class NoteModel: ObservableObject {
private var switchingPages = false
#Published var pageNumber = UserDefaults.standard.value(forKey: "pageNumber") as? Int ?? 1 {
didSet {
UserDefaults.standard.set(pageNumber, forKey: "pageNumber")
switchingPages = true
noteContents = NSAttributedString(string: "")
openNote()
switchingPages = false
}
}
#Published var noteContents = NSAttributedString(string: "") {
didSet {
if !switchingPages {
saveNote()
}
}
}
private var noteName: String {
return "\(NoteManager.NOTE_NAME_PREFIX)\(pageNumber).rtfd"
}
init() {
openNote()
}
private func openNote() {
// This is necessary, but macOS seems to recover the stale bookmark automatically, so don't handle it for now
var isStale = false
guard let bookmarkData = UserDefaults.standard.object(forKey: "storageLocationBookmarkData") as? Data,
let storageLocation = try? URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
else {
ErrorHandling.showErrorToUser("No storage location for your notes could be found!", informativeText: "Please try re-selecting your storage location in the settings.")
return
}
let fullURL = storageLocation.appendingPathComponent(noteName)
let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtfd]
do {
guard storageLocation.startAccessingSecurityScopedResource() else {
ErrorHandling.showErrorToUser("ScratchPad is not allowed to access the storage location for your notes!", informativeText: "Please try re-selecting your storage location in the settings.")
return
}
if let _ = try? fullURL.checkResourceIsReachable() {
let attributedString = try NSAttributedString(url: fullURL, options: options, documentAttributes: nil)
noteContents = attributedString
}
fullURL.stopAccessingSecurityScopedResource()
} catch {
print(error)
ErrorHandling.showErrorToUser(error.localizedDescription)
}
}
private func saveNote() {
// This is necessary, but macOS seems to recover the stale bookmark automatically, so don't handle it for now
var isStale = false
guard let bookmarkData = UserDefaults.standard.object(forKey: "storageLocationBookmarkData") as? Data,
let storageLocation = try? URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
else {
ErrorHandling.showErrorToUser("No storage location for your notes could be found!", informativeText: "Please try re-selecting your storage location in the settings.")
return
}
let fullURL = storageLocation.appendingPathComponent(noteName)
do {
guard storageLocation.startAccessingSecurityScopedResource() else {
ErrorHandling.showErrorToUser("ScratchPad is not allowed to access the storage location for your notes!", informativeText: "Please try re-selecting your storage location in the settings.")
return
}
let rtdf = noteContents.rtfdFileWrapper(from: .init(location: 0, length: noteContents.length))
try rtdf?.write(to: fullURL, options: .atomic, originalContentsURL: nil)
fullURL.stopAccessingSecurityScopedResource()
} catch {
print(error)
ErrorHandling.showErrorToUser(error.localizedDescription)
}
}
}
On GitHub: https://github.com/eiskalteschatten/ScratchPad/blob/main/ScratchPad/Notes/NoteModel.swift
Does anyone have any idea why this is happening and/or how to fix it?
I have found these similar issues, but they don't really help me much:
Replacing NSAttributedString in NSTextStorage Moves NSTextView Cursor - I don't have any custom syntax highlighting or anything like that.
Cursor always jumps to the end of the UIViewRepresentable TextView when a newline is started before the final line + after last character on the line - Only solves the caret issue and causes jerky scroll behavior in longer documents.
Edit: I forgot to mention that I'm using macOS Ventura, but am targeting 12.0 or higher.
Edit #2: I have significantly updated the question to reflect what I've found through more debugging.

Pausing Animation in SceneKit

I loaded into SceneKit a .usdz file which has an animation attached to it. I want to stop this animation to play.. but I can't find the right way.
I load the .usdz asset file with the following method:
func loadIdle() {
let urlfile = Bundle.main.path(forResource: "armi_idle",
ofType: "usdz",
inDirectory: "Asset.scnassets")!
let scene = try! SCNScene(url: URL(string: urlfile)!)
guard let findNode = scene.rootNode.childNode(withName: "armi_idle",
recursively: true)
else {
print("err finde idle")
return
}
// try to pause, but not work
findNode.isPaused = true
// add to main scene
self.scene.rootNode.addChildNode(findNode)
}
A picture of the asset:
Declare the following properties:
import SceneKit
#IBOutlet var sceneView: SCNView!
var notWalking: Bool = true
var animations = [String: CAAnimation]()
var node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene()
self.animation()
}
Then try the following logic (you need Idle and Walking files) :
fileprivate func loadAnimation(withKey: String, scene: String, id: String) {
let url = Bundle.main.url(forResource: scene, withExtension: "dae")!
let source = SCNSceneSource(url: url, options: nil)
guard let character = source?.entryWithIdentifier(id,
withClass: CAAnimation.self) else { return }
character.fadeInDuration = 0.5
character.fadeOutDuration = 0.5
self.animations[withKey] = character
}
then:
fileprivate func animation() {
let standStill = SCNScene(named: "art.scnassets/Idle")!
for childNode in standStill.rootNode.childNodes {
self.node.addChildNode(childNode)
}
sceneView.scene.rootNode.addChildNode(self.node)
self.loadAnimation(withKey: "walking", scene: "art.scnassets/Walking",
id: "Walking-1")
}
and then:
func playWalking(key: String) {
sceneView.scene.rootNode.addAnimation(animations[key]!, forKey: key)
}
func stopWalking(key: String) {
sceneView.scene.rootNode.removeAnimation(forKey: key,
blendOutDuration: 0.75)
}
and at last:
#IBAction func pressed(_ sender: UIButton) {
notWalking ? playWalking(key: "walking") : stopWalking(key: "walking")
notWalking.toggle()
}

How to prevent song item to start over again after being selected?

I have a collection view, that has a few songs, and upon selecting these songs, the app moves into a playerVC where the pause button is and the song image.
If the user selects song a for example, the music starts so "player.play()" is true, and then the user is moved into a playerVC with it's image etc, now say the user leaves this playerVC and wants to return to it to pause the song, the whole song restarts. How can I prevent this from happening? I know I have to implement an If statement to do something is "player.isPlaying" but do not know what to put in it.
This is the class where the audio is initialised
class SoundManager {
public var position = 0
public var songs: [Song] = []
var player: AVAudioPlayer?
func playNavPlayer() {
let song = songs[position]
let urlString = Bundle.main.path(forResource: song.trackName, ofType: "mp3")
do {
try AVAudioSession.sharedInstance().setMode(.default) ///app needs to know what mode we are working on
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let urlString = urlString else { return }
player = try AVAudioPlayer(contentsOf: URL(string: urlString)!)
guard let player = player else {return}
player.volume = 0.5
// player.play()
// player.delegate = self
player.numberOfLoops = -1
} catch {
print ("Error")
}
}
}
Updated Singleton Class
class SoundManager {
private init(){ }
static let shared = SoundManager()
public var position = 0
public var songs: [Song] = []
var player: AVAudioPlayer?
var songURL: URL!
func playNavPlayer() {
let song = songs[position]
let urlString = Bundle.main.path(forResource: song.trackName, ofType: "mp3")
do {
try AVAudioSession.sharedInstance().setMode(.default) ///app needs to know what mode we are working on
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard let urlString = urlString else { return }
player = try AVAudioPlayer(contentsOf: URL(string: urlString)!)
guard let player = player else {return}
player.volume = 0.5
player.play()
//player.delegate = self
player.numberOfLoops = -1
}
catch {
print ("Error")
}
}
How this class is called
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
//present player
let position = indexPath.item
guard let vc = storyboard?.instantiateViewController(identifier: "player") as? PlayerViewController else {
return
}
func push() {
vc.songs = songs
vc.position = position
vc.soundManager = SoundManager.shared
navigationController?.pushViewController(vc, animated: true)
}
SoundManager.shared.songs = songs
SoundManager.shared.position = position
if SoundManager.shared.player?.isPlaying == true && SoundManager.shared.player?.url (This is where I need to see compare song playing with song clicked) {
push()
}
else {
SoundManager.shared.playNavPlayer()
push()
}
SoundManager.shared.setupRemoteTransportControls()
SoundManager.shared.setupNowPlaying()
//songs
}
Song Declaration
var songs = [Song]()
func configureSongs() {
songs.append(Song(name: "Empire", imageName: "cover1", trackName: "Empire"))
songs.append(Song(name: "FirstSample", imageName: "cover2", trackName: "FirstSample"))
songs.append(Song(name: "TBH", imageName: "cover3", trackName: "TBH"))
songs.append(Song(name: "Trials", imageName: "cover4", trackName: "Trials"))
songs.append(Song(name: "Magic Spells", imageName: "cover5", trackName: "MagicSpells"))
songs.append(Song(name: "Still Goin", imageName: "cover6", trackName: "StillGoin"))
}
Song, is a struct
import Foundation
struct Song {
let name: String
let imageName: String
let trackName: String
}
So the Song struct should conform the Equatable protocol, in order for us to use a function in which we compare two instances of our Song struct, like this
struct Song: Equatable {
let name: String
let imageName: String
let trackName: String
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.trackName == rhs.trackName
}
}
And then when we select a song(item) in didSelect we implement an if function to check wether the player is playing and also to compare the two track names, the one that is playing and the one we just clicked on, then if true we specify what we want to do in the curly brackets.
if SoundManager.shared.player?.isPlaying == true && songs[position] == SoundManager.shared.songs[SoundManager.shared.position] {
//Whatever you want to do
}

Objects Track using vision framework in iOS 11

I want to detect object and track that object using vision framework. I am successfully done with detect objects and little bit with tracking also but I don't get so much accuracy with tracking.
I want much more accuracy while converting frames as its frequently lost the accuracy while track the objects.
Please check the below code for detect and track the objects:
import UIKit
import AVFoundation
import Vision
class ViewController: UIViewController {
private lazy var captureSession: AVCaptureSession = {
let session = AVCaptureSession()
session.sessionPreset = AVCaptureSession.Preset.photo
guard let backCamera = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: backCamera) else
{
return session
}
session.addInput(input)
return session
}()
private lazy var cameraLayer: AVCaptureVideoPreviewLayer =
AVCaptureVideoPreviewLayer(session: self.captureSession)
private let handler = VNSequenceRequestHandler()
fileprivate var lastObservation: VNDetectedObjectObservation?
lazy var highlightView: UIView = {
let view = UIView()
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 4
view.backgroundColor = .clear
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(cameraLayer)
view.addSubview(highlightView)
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label:
"queue"))
captureSession.addOutput(output)
captureSession.startRunning()
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(tapAction))
view.addGestureRecognizer(tapGestureRecognizer)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
cameraLayer.frame = view.bounds
}
// MARK: - Actions
#objc private func tapAction(recognizer: UITapGestureRecognizer) {
highlightView.frame.size = CGSize(width: 120, height: 120)
highlightView.center = recognizer.location(in: view)
let originalRect = highlightView.frame
var convertedRect =
cameraLayer.metadataOutputRectConverted(fromLayerRect:
originalRect)
convertedRect.origin.y = 1 - convertedRect.origin.y
lastObservation = VNDetectedObjectObservation(boundingBox:
convertedRect)
}
fileprivate func handle(_ request: VNRequest, error: Error?) {
DispatchQueue.main.async {
guard let newObservation = request.results?.first as?
VNDetectedObjectObservation else {
return
}
self.lastObservation = newObservation
var transformedRect = newObservation.boundingBox
transformedRect.origin.y = 1 - transformedRect.origin.y
let convertedRect =
self.cameraLayer.layerRectConverted(fromMetadataOutputRect:
transformedRect)
self.highlightView.frame = convertedRect
}
}
}
extension ViewController:
AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer:
CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer =
CMSampleBufferGetImageBuffer(sampleBuffer),
let observation = lastObservation else {
return
}
let request = VNTrackObjectRequest(detectedObjectObservation:
observation) { [unowned self] request, error in
self.handle(request, error: error)
}
request.trackingLevel = .accurate
do {
try handler.perform([request], on: pixelBuffer)
}
catch {
print(error)
}
}
}
Any help will be appreciated!!
Thanks.
I am not so good at vision and core ml, but apparently your code looks fine. One thing you can do is check when vision does not get any tracking in the buffer, you have to mark its property isLastFrame true if tracking request confidence value falls to 0.
if !trackingRequest.isLastFrame {
if observation.confidence > 0.7 {
trackingRequest.inputObservation = observation
} else {
trackingRequest.isLastFrame = true
}
newTrackingRequests.append(trackingRequest)
}
This way its easy to find out whether vision tracking request lost tracking object or it just tracking the wrong object.

Displaying Artwork for .MP3 file

I am trying to currently display the album artwork for a locally stored .MP3 track in an ImageView. Does anyone know how to fetch this artwork in Swift in order to accomplish this?
I have found this solution (iOS AVFoundation: How do I fetch artwork from an mp3 file?) but the code is written in Objective C. I simply want to grab the image embedded in my MP3 and display it in my ImageView.
I've looked at the API documentation for the MPMediaItemArtwork and found an example that also accomplishes what I am trying to accomplish in Objective C as well here(http://www.codeitive.com/0zHjkUjUWX/not-able-to-get-the-uiimage-from-mpmediaitempropertyartwork.html) but cannot come up with a solution. My code is as follows:
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController {
let audioPath:NSURL! = NSBundle.mainBundle().URLForResource("SippinOnFire", withExtension: "mp3")
#IBOutlet var artistImage: UIImageView!
#IBOutlet var trackLabel: UILabel!
#IBOutlet var artistLabel: UILabel!
#IBOutlet var sliderValue: UISlider!
var player:AVAudioPlayer = AVAudioPlayer()
#IBAction func play(sender: AnyObject) {
let audioInfo = MPNowPlayingInfoCenter.defaultCenter()
println(audioInfo)
player.play()
//println("Playing \(audioPath)")
let playerItem = AVPlayerItem(URL: audioPath)
let metadataList = playerItem.asset.metadata as! [AVMetadataItem]
for item in metadataList {
if let stringValue = item.value {
println(item.commonKey)
if item.commonKey == "title" {
trackLabel.text = stringValue as? String
}
if item.commonKey == "artist" {
artistLabel.text = stringValue as? String
}
if item.commonKey == "artwork" {
if let audioImage = UIImage(data: item.value as! NSData) {
let audioArtwork = MPMediaItemArtwork(image: audioImage)
println(audioImage.description)
}
}
}
}
}
#IBAction func pause(sender: AnyObject) {
player.pause()
}
#IBAction func stop(sender: AnyObject) {
player.stop()
player.currentTime = 0;
}
#IBAction func sliderChanged(sender: AnyObject) {
player.volume = sliderValue.value
}
override func viewDidLoad() {
super.viewDidLoad()
var error:NSError? = nil
player = AVAudioPlayer(contentsOfURL: audioPath!, error: &error)
player.volume = 0.5;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is a screen shot of my sample .mp3 file. As you can see there is indeed album artwork that is both visible in the "get info" section of Finder. I've also opened the .mp3 in my iTunes to make sure and have confirmed there is artwork in the "get info" section of it there as well as under the "artwork" tab.
However, when trying to use the commonKey to assign the image to my imageView I find that there is no commonKey for "artwork".
Thanks
Change your snippet of code into this (I already tested it):
I added println lines commented in places of interest, Feel free to uncomment in order to see what is happening.
for item in metadataList {
if item.commonKey == nil{
continue
}
if let key = item.commonKey, let value = item.value {
//println(key)
//println(value)
if key == "title" {
trackLabel.text = value as? String
}
if key == "artist" {
artistLabel.text = value as? String
}
if key == "artwork" {
if let audioImage = UIImage(data: value as! NSData) {
//println(audioImage.description)
artistImage.image = audioImage
}
}
}
}
UPDATE: A bit of clean up of this code
for item in metadataList {
guard let key = item.commonKey, let value = item.value else{
continue
}
switch key {
case "title" : trackLabel.text = value as? String
case "artist": artistLabel.text = value as? String
case "artwork" where value is NSData : artistImage.image = UIImage(data: value as! NSData)
default:
continue
}
}
UPDATE: For Swift 4
for item in metadataList {
guard let key = item.commonKey?.rawValue, let value = item.value else{
continue
}
switch key {
case "title" : trackLabel.text = value as? String
case "artist": artistLabel.text = value as? String
case "artwork" where value is Data : artistImage.image = UIImage(data: value as! Data)
default:
continue
}
}
edit/update Swift 4 or later:
import MediaPlayer
var nowPlayingInfo: [String: Any] = [:]
let playerItem = AVPlayerItem(url: url)
let metadataList = playerItem.asset.metadata
for item in metadataList {
switch item.commonKey {
case .commonKeyTitle?:
nowPlayingInfo[MPMediaItemPropertyTitle] = item.stringValue ?? ""
case .commonKeyType?:
nowPlayingInfo[MPMediaItemPropertyGenre] = item.stringValue ?? ""
case .commonKeyAlbumName?:
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = item.stringValue ?? ""
case .commonKeyArtist?:
nowPlayingInfo[MPMediaItemPropertyArtist] = item.stringValue ?? ""
case .commonKeyArtwork?:
if let data = item.dataValue,
let image = UIImage(data: data) {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
}
case .none: break
default: break
}
}
let audioInfo = MPNowPlayingInfoCenter.default()
audioInfo.nowPlayingInfo = nowPlayingInfo
Note: You will have to invoke beginReceivingRemoteControlEvents() otherwise it will not work on the actual device. You will also need to set your app Background Modes (Audio and AirPlay) and set your AVAudioSession category to AVAudioSessionCategoryPlayback and set it active:
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
Try this:
It appears that sometimes iOS 8 returns nil at first attempt of obtaining this info:
if let audioCenter = MPNowPlayingInfoCenter.defaultCenter(){
if let audioInfo = audioCenter.nowPlayingInfo{
if let artwork = audioInfo[MPMediaItemPropertyArtwork] as? MPMediaItemArtwork
{
var image: UIImage? = artwork.imageWithSize(artwork.bounds.size)
if image == nil {
image = artwork.imageWithSize(artwork.bounds.size);
}
if image != nil{
println("image loaded")
}
}
}
}