I am building an app for MacOS and I am trying to implement the code to add songs to a library playlist (which is added below). The issue I am having is that if I use Music Kit to load a users library playlists, the ID for the playlist (which is just a string of numbers) does not work with the Add tracks to a Library Playlist endpoint of Apple Music API. If I retrieve the playlists from the Apple Music API and use that playlist ID (which is different than the id I get from MusicKit) my code works fine and adds the song to the playlist. The problem is that when getting a users library playlists from Apple Music API is that it does not give me all of the library playlists that I get when using Music Kit and it also does not give me Artwork for playlists that have the collage of album covers, so I would prefer to use Music Kit to get the playlists.
I have also tested trying to retrieve a single playlist using the Apple Music API with the playlist Id from Music Kit and it does not work. I get the error that the resource cannot be found. Since this is a macOs app I cannot use MusicKit to add songs to library playlists.
Does anyone know a way to resolve this? Or a possible workaround? Ideally I want to use MusicKit to get the library playlists and have some way to use the playlist Id and add songs to that playlist. Below is my code for adding a song to a playlist using the Apple Music API, which works correctly only if I originally get the library playlist's id value from a playlist retrieved from the Apple Music API.
Also, does anyone know why the playlist Id's are not universal and are different when using Music Kit and Apple Music API? For songs and tracks it does not seem to matter if I use music kit or Apple Music API, the Id's are in the correct format for Apple Music API to use and work with my code. Thanks everyone for any and all help!
func addToPlaylist(songs: [Track], playlist: Playlist, alert: Binding<AlertItem?>) async {
let tracks = AppleMusicPlaylistPostRequestBody(data: songs.compactMap {
AppleMusicPlaylistPostRequestItem(id: $0.id.rawValue, type: "songs") // or "library-songs"
})
let playlistID = playlist.id
// Build the request URL for adding a song to a playlist
guard let url = URL(string: "https://api.music.apple.com/v1/me/library/playlists/\(playlistID)/tracks") else {
alert.wrappedValue = AlertItem(title: "Error", message: "Invalid URL for the playlist.")
return
}
// Authorization Header
guard let musicUserToken = try? await MusicUserTokenProvider().getUserMusicToken() else {
alert.wrappedValue = AlertItem(title: "Error", message: "Unable to retrieve Music User Token.")
return
}
do {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(musicUserToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
let data = try encoder.encode(tracks)
request.httpBody = data
let musicRequest = MusicDataRequest(urlRequest: request)
let musicRequestResponse = try await musicRequest.response()
// Check if the request was successful (status 201)
if musicRequestResponse.urlResponse.statusCode == 201 {
alert.wrappedValue = AlertItem(title: "Success", message: "Song successfully added to the playlist.")
} else {
print("Status Code: \(musicRequestResponse.urlResponse.statusCode)")
print("Response Data: \(String(data: musicRequestResponse.data, encoding: .utf8) ?? "No Data")")
// Attempt to decode the error response into the AppleMusicErrorResponse model
if let appleMusicError = try? JSONDecoder().decode(AppleMusicErrorResponse.self, from: musicRequestResponse.data) {
let errorMessage = appleMusicError.errors.first?.detail ?? "Unknown error occurred."
alert.wrappedValue = AlertItem(title: "Error", message: errorMessage)
} else {
alert.wrappedValue = AlertItem(title: "Error", message: "Failed to add song to the playlist.")
}
}
} catch {
alert.wrappedValue = AlertItem(title: "Error", message: "Network error: \(error.localizedDescription)")
}
}
Post
Replies
Boosts
Views
Activity
Hello everyone!
I am trying to create a spectrogram like in the attached image for a macOs App. I am using Cocoa/AppKit but also have some SwiftUI views, so I can use either. I have found the sample app Visualizing Sound as an Audio Spectogram that apple provides but I do not want a real-time spectrogram. I want a spectrogram of the whole audio file. I have been trying to convert the sample app to what I need but I have been unsuccessful so far. Here is how I changed the code in the delegate
public func captureBuffer() {
let asset = AVAsset(url: audioFileUrl)
let reader = try! AVAssetReader(asset: asset)
let track = asset.tracks(withMediaType: AVMediaType.audio)[0]
let settings = [
AVFormatIDKey : kAudioFormatLinearPCM
]
let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: settings)
reader.add(readerOutput)
reader.startReading()
while let buffer = readerOutput.copyNextSampleBuffer() {
var audioBufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: AudioBuffer(mNumberChannels: 0, mDataByteSize: 0, mData: nil))
var blockBuffer: CMBlockBuffer?
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
buffer,
bufferListSizeNeededOut: nil,
bufferListOut: &audioBufferList,
bufferListSize: MemoryLayout<AudioBufferList>.size,
blockBufferAllocator: nil,
blockBufferMemoryAllocator: nil,
flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
blockBufferOut: &blockBuffer
);
let buffers = UnsafeBufferPointer<AudioBuffer>(start: &audioBufferList.mBuffers, count: Int(audioBufferList.mNumberBuffers))
for buffer in buffers {
let samplesCount = Int(buffer.mDataByteSize) / MemoryLayout<Int16>.size
let samplesPointer = audioBufferList.mBuffers.mData!.bindMemory(to: Int16.self, capacity: samplesCount)
let samples = UnsafeMutableBufferPointer<Int16>(start: samplesPointer, count: samplesCount)
}
guard let data = audioBufferList.mBuffers.mData else {
return
}
/// The _Nyquist frequency_ is the highest frequency that a sampled system can properly
/// reproduce and is half the sampling rate of such a system. Although this app doesn't use
/// `nyquistFrequency` you may find this code useful to add an overlay to the user interface.
if nyquistFrequency == nil {
let duration = Float(CMSampleBufferGetDuration(buffer).value)
let timescale = Float(CMSampleBufferGetDuration(buffer).timescale)
let numsamples = Float(CMSampleBufferGetNumSamples(buffer))
nyquistFrequency = 0.5 / (duration / timescale / numsamples)
}
if self.rawAudioData.count < AudioSpectrogram.sampleCount * 2 {
let actualSampleCount = CMSampleBufferGetNumSamples(buffer)
let ptr = data.bindMemory(to: Int16.self, capacity: actualSampleCount)
let buf = UnsafeBufferPointer(start: ptr, count: actualSampleCount)
rawAudioData.append(contentsOf: Array(buf))
}
while self.rawAudioData.count >= AudioSpectrogram.sampleCount {
let dataToProcess = Array(self.rawAudioData[0 ..< AudioSpectrogram.sampleCount])
self.rawAudioData.removeFirst(AudioSpectrogram.hopCount)
self.processData(values: dataToProcess)
}
createAudioSpectrogram()
}
}
}
I am sure there are different or better ways to go about this, but the only examples I can find are on iOS and use UIKit, but I am building for MacOs. Does anyone know how display a spectrogram for an audio file without having to play the audio file? I dont mind using sox or ffmpeg if that is easier.
Greatly appreciated!
This Mac can’t connect to Apple Media Services because of a problem with "my Apple ID email address"
I am using Xcode 13.2 on MacOS 12.2. I have been building and running my app in Xcode for months with no issues. I stopped working on it for a couple of days while out of town. The next time I ran my app, I get this pop-up warning saying "This Mac can’t connect to Apple Media Services because of a problem with "my Apple ID email address" when the app uses MusicKit to search for an album.
When I push "Apple ID Preferences...", it takes me there and another pop-up asks me to "sign in to Media Services using my Apple ID", even though I am already signed in. Once I sign in, my app continues to run. If I hit the "Later" button, the pop-up goes away and my app continues to run just fine.
My computer was off when I was out of town, so no one did anything to it and no auto-updates were performed or anything. I tried signing out of iCloud, Apple Music... etc, restarting and signing back into everything as suggested by Apple customer service but that did not help. They say everything is fine with my Apple ID so it is an issue with Xcode. This happens every time I run my app in Xcode. Does anyone have any idea what is going on? Please help. Much appreciated!
Hi,
From a navigation view, I open a sheet view (instead of having a 3-column view). While I can make a toolbar, it only shows at the bottom of the window. Does anyone know how to get the toolbar at the top of the window, if it is even possible? Or is the toolbar only allowed at the bottom of the window in a sheet view?
Thanks!
I am trying to get the data for each track in a MusicItemCollection into a DataFrame. The only way I know to go about this is to use a for-in loop, but this creates a DataFrame for each track instead of a DataFrame for all of the tracks. My code is:
let albumTracks = album?.tracks
for tracks in albumTracks {
let dataFrame: DataFrame = [
"track": [tracks.trackNumber],
"title": [tracks.title],
"artist": [tracks.artistName],
"release date": [tracks.releaseDate?.formatted(date: .long, time: .omitted) ?? "not available"],
"duration": [tracks.duration],
"isrc": [tracks.isrc]
]
print(dataFrame)
I have also tried
for tracks in self.albumTracks {
var dataFrame = DataFrame.init()
let trackNumColumn = Column.init(name: "track", contents: [String(tracks.trackNumber!)])
dataFrame.append(column: trackNumColumn)
let titleColumn = Column.init(name: "title", contents: [tracks.title])
dataFrame.append(column: titleColumn)
let artistColumn = Column.init(name: "artist", contents: [tracks.artistName])
dataFrame.append(column: artistColumn)
let releaseDateColumn = Column.init(name: "release date", contents: [tracks.releaseDate?.formatted(date: .long, time: .omitted)])
dataFrame.append(column: releaseDateColumn)
let idColumn = Column.init(name: "id", contents: [String(tracks.id.rawValue)])
dataFrame.append(column: idColumn)
print(dataFrame)
Both of these methods work outside of a loop according to the tech talks video: https://developer.apple.com/videos/play/tech-talks/10100/ , but I cannot figure out how to call the individual tracks outside of a loop
Thanks
I am using SwiftUI for Mac OS and so far I have not been able to implement tap gestures. I want to double click on a row of the Table and have that perform an action using the contents of that row. Does anyone know if it is even possible to use the .onTapGesture or anything similar to a SwiftUI Table? Any help is appreciated!
Thanks!
Having trouble getting the data from MusicKit Searches back to my SwiftUI view. Normally I would use a @State variable for the decodable structure, but with MusicKit you dont need to decode anything and I can't figure out how to best get the data back to my view. I call the MusicKit function using .onAppear in my SwiftUI view
func getAlbum() async throws -> MusicItemCollection<Album> {
var request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1440881121")
let response = try await request.response()
print(response.debugDescription)
return response.items
printing debugDescription give these results:
MusicCatalogResourceResponse<Album>(
items: [
Album(
id: "1440881121",
title: "Colors",
artistName: "Beck",
contentRating: "explicit",
copyright: Fonograf Records/Capitol Records; ℗ 2017 Fonograf Records, under exclusive license to UMG Recordings, Inc.,
genreNames: [
"Alternative",
"Music"
],
isCompilation: false,
isComplete: true,
isDigitalMaster: true,
isSingle: false,
releaseDate: "2017-10-13",
trackCount: 11,
upc: "00602557176827"
Hoe would you access all of the data in the debugDescription and use it in your SwiftUI view? I can only access the standard name and id. Is anyone using Codable to move the data through a data model? The documentation is not making sense to me even though I am usually ok using it.
Also, should I be using `func getAlbum() async throws -> MusicCatalogResourceResponse {' instead of using MusicItemCollection?
Thanks
Hello again,
I have no ides where to start with this. I am using SwiftUI for Mac OS and I need to be able to create a Data Frame straight from the JSON returned from an API call. So far I have been using Table since I know what the JSON will contain. Now I need a way to get the JSON data straight to a a table that I can display in Swift UI because the contents will vary. I think I need to use @dynamicMemberLookup struct DataFrame with the init(jsonData: Data, columns: [String]?, types: [String : JSONType], options: JSONReadingOptions) throws
init method, but I am not sure how to set it up and where to set it up. My app is a view-data-model structure:
A SwiftUI view that calls the functions to search the APIs.
A class that contains all of the API search functions. The returned JSON data is parsed here for the dataModel
A dataModel with structures for the json data.
The data from the dataModel gets returned to the SwiftUI as a @State variable data = [dataModel]()
I would assume the DataFrame would go in the SwiftUI view but I am not sure how to get the JSON data to the Dataframe since I am not going to decode it to a dataModel. Any help Appreciated! Thanks!
This is a followup to SwiftUI Table does not work when trying to use Int values
After getting a result from looking up an album or song in AppleMusic API, I parse the returned json using SwiftyJSON as shown here:
URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in
guard error == nil else { return }
if let json = try? JSON(data: data!) {
let result = (json["data"]).array!
for songDetail in result {
let attributes = songDetail["attributes"]
let song = SongDetails(albumName: attributes["albumName"].string!,
artistName: attributes["artistName"].string!,
artworkURL: attributes["artwork"]["url"].string!,
composerName: attributes["composerName"].string ?? "-",
discNumber: attributes["discNumber"].int!,
durationInMillis: attributes["durationInMillis"].int!,
genreNames: attributes["genreNames"].arrayValue,
isrc: attributes["isrc"].string!,
name: attributes["name"].string!,
id: attributes["playParams"]["id"].string!,
releaseDate: attributes["releaseDate"].string!,
trackNumber: attributes["trackNumber"].int!)
songDetails.append(song)
print(songDetails)
}
Notice how [genreNames] needs ".arrayValue" instead of ".string!". I Think the reason is because "genreNames" are listed in an Array of Strings.
Here is the data model I use for the parsed results:
struct SongDetails {
var albumName: String
var artistName: String
var artworkURL: String
var composerName: String
var discNumber: Int
var durationInMillis: Int
var genreNames: Array<Any>
var isrc: String
var name: String
var id: String
var releaseDate: String
var trackNumber: Int
init(albumName: String, artistName: String, artworkURL: String, composerName: String, discNumber: Int, durationInMillis: Int, genreNames: Array<Any>, isrc: String, name: String, id: String, releaseDate: String, trackNumber: Int) {
self.albumName = albumName
self.artistName = artistName
self.artworkURL = artworkURL
self.composerName = composerName
self.discNumber = discNumber
self.durationInMillis = durationInMillis
self.genreNames = genreNames
self.isrc = isrc
self.name = name
self.id = id
self.releaseDate = releaseDate
self.trackNumber = trackNumber
}
}
Here I have genreNames as "Array". This set up works and I can call and print jus the genreNames, but everything I have read online says that "[String]" should be used instead of "Array", but if I do that, I get an error in the previous code when using [genreNames].string!. The way I currently have it set up, genreNames show up in the console as genreNames: [Pop, Music] , so I think it is correct. The issue arises when I try to use genreNames in a SwiftUI view.
In my SwiftUI view I have a Table where these results go.
HStack {
WebImage(url: URL(string: songDetail.artworkURL.replacingOccurrences(of: "{w}", with: "5000").replacingOccurrences(of: "{h}", with: "5000")))
.resizable()
.indicator(.activity)
.frame(width: 400, height: 400)
.cornerRadius(5)
.shadow(radius: 2)
VStack (alignment: .leading) {
Text("song id: \(songDetail.id)")
.foregroundColor(.secondary)
.padding(2)
Text("title: \(songDetail.name)")
.foregroundColor(.secondary)
.padding(2)
Text("artist: \(songDetail.artistName)")
.foregroundColor(.secondary)
.padding(2)
Text("composer: \(songDetail.composerName)")
.foregroundColor(.secondary)
.padding(2)
Text("disc number \(songDetail.discNumberString)")
.foregroundColor(.secondary)
.padding(2)
Text("track number: \(songDetail.trackNumberString)")
.foregroundColor(.secondary)
.padding(2)
Text("duration in ms: \(songDetail.durationInMillisString)")
.foregroundColor(.secondary)
.padding(2)
Text("release date: \(songDetail.releaseDate)")
.foregroundColor(.secondary)
.padding(2)
Text("isrc: \(songDetail.isrc)")
.foregroundColor(.secondary)
.padding(2)
}
.font(.system(size: 14))
}
}
}.padding(.leading, 20.0)
.frame(minWidth:800, idealWidth: 1000, maxWidth: .infinity, minHeight:600, idealHeight: 800, maxHeight: .infinity)
.onAppear {
SKCloudServiceController.requestAuthorization { (status) in
if status == .authorized {
self.searchResults = AppleMusicAPI().getSongDetails(self.songId)
}
}
}
}
}
extension SongDetails {
var discNumberString: String {
String(discNumber)
}
var trackNumberString: String {
String(trackNumber)
}
var durationInMillisString: String {
String(durationInMillis)
}
}
The extension at the bottom is how the issue of the original post was solved. It seems that the new SwiftUI Table can only accept String values. To get the genreNames into this table I tried making a similar extension where I used join() to try and make a string of the genreResults, but that did not work either. I can't remember the exact code, but it could have been wrong.
So how would this be accomplished? Anyone know? This is the last thing that I need to get into my Table. Thanks! I appreciate the help!
I am trying to use MusicDataRequest to search for albums, artists, and tracks using a search term like the album's names. If I use an albums Id, for example, I get results. The following code gets me results. First is the data model, the I have a class with code for authorization and then the function that searches using Music Kit
struct MyAlbumResponse: Decodable {
let data: [Album]
}
class music {
@State var isAuthorizedForMusicKit = false
var searchTerm = ""
func requestMusicAuthorization() {
Task.detached {
let authorizationStatus = await MusicAuthorization.request()
if authorizationStatus == .authorized {
self.isAuthorizedForMusicKit = true
} else {
}
}
}
func album() async throws {
let auth = requestMusicAuthorization()
let url = URL(string: "https://api.music.apple.com/v1/catalog/US/albums/1440881121")!
let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))
let dataResponse = try await dataRequest.response()
let decoder = JSONDecoder()
let albumResponse = try decoder.decode(MyAlbumResponse.self, from: dataResponse.data)
print(albumResponse.data)
}
The above code returns the results I expect. But If I change the function to use the search endpoint, I do not get any results. I dont get any errors either. The data model is also different.
struct AlbumSearchResponse: Decodable {
var albums: Albums
}
struct Albums: Codable {
var data: [AlbumsDatum]
var href, next: String
}
struct AlbumsDatum: Codable {
var attributes: Attributes
var href, id, type: String
}
struct Attributes: Codable {
var artistName: String
var artwork: Artwork
var copyright: String
var genreNames: [String]
var isComplete, isMasteredForItunes, isSingle: Bool
var name: String
var playParams: PlayParams
var releaseDate: String
var trackCount: Int
var url: String
var editorialNotes: EditorialNotes?
}
class music {
@State var isAuthorizedForMusicKit = false
var searchTerm = ""
func requestMusicAuthorization() {
Task.detached {
let authorizationStatus = await MusicAuthorization.request()
if authorizationStatus == .authorized {
self.isAuthorizedForMusicKit = true
} else {
}
}
}
func musicSearch() async throws {
var auth = requestMusicAuthorization()
let url = URL(string: "https://api.music.apple.com/v1/catalog/us/search?term=colors&limit=10&types=albums")!
let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))
let dataResponse = try await dataRequest.response()
let decoder = JSONDecoder()
let albumSearchResponse = try decoder.decode(AlbumSearchResponse.self, from: dataResponse.data)
print(albumSearchResponse.albums)
}
I have gone through all of the documentation and example applications but can't find an answer to this. I dont understand why the search endpoint would be not usable, if that is the case.
The workaround I have been using is to use MusicCatalogSearchRequest instead, but this does not return enough attributes and I cannot figure out how to have it return more attributes such as totalTracks, copyright, etc. I would be fine using the method if there is a way to add attributes to be included with the results. Here is the code I am using that works. It is also under the same class as the other functions.
func Search() async throws {
let auth = requestMusicAuthorization()
let searchTerm = "colors"
Task {
do {
// Issue a catalog search request for albums matching the search term.
var searchRequest = MusicCatalogSearchRequest(term: searchTerm, types: [Album.self])
searchRequest.limit = 20
let searchResponse = try await searchRequest.response()
print(searchResponse)
}
}
}
any and all help is much appreciated. Thanks
Hello Everyone,
I am trying to get a SwiftUI Table to display content from my data model. It works as expected if I only use String values in the table, but if I try and use an Int value my app will no longer compile. It just sits there and I have to stop it manually. The only error I receive says "CompileSwiftSources Failed with a nonzero exit code"
My code of the data model:
import Foundation
struct AlbumTracks: Identifiable {
var artistName: String
var artworkURL: String
var composerName: String
var discNumber: Int
var durationInMillis: Int
var isrc: String
var name: String
var id: String
var releaseDate: String
var trackNumber: Int
init(artistName: String, artworkURL: String, composerName: String, discNumber: Int!, durationInMillis: Int!, isrc: String, name: String, id: String, releaseDate: String, trackNumber: Int!) {
self.artistName = artistName
self.artworkURL = artworkURL
self.composerName = composerName
self.discNumber = discNumber
self.durationInMillis = durationInMillis
self.isrc = isrc
self.name = name
self.id = id
self.releaseDate = releaseDate
self.trackNumber = trackNumber
}
}
Here is the code for my view:
struct AlbumDetailView: View {
@Environment(\.presentationMode) var presentationMode
@Binding var albumId: String
@State var searchResults = [AlbumDetails]()
@State var albumTracks = [AlbumTracks]()
var body: some View {
VStack (alignment: .leading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Image(systemName: "arrow.left")
}).padding(.top).foregroundColor(.red)
ForEach(searchResults, id:\.id) { albumDetail in
HStack {
WebImage(url: URL(string: albumDetail.artworkURL.replacingOccurrences(of: "{w}", with: "5000").replacingOccurrences(of: "{h}", with: "5000")))
.resizable()
.padding(.top, 10.0)
.frame(width: 400, height: 400)
.cornerRadius(5)
.shadow(radius: 2)
VStack (alignment: .leading) {
Text("song id: \(albumDetail.id)")
.foregroundColor(.secondary)
.padding(2)
Text("title: \(albumDetail.name)")
.foregroundColor(.secondary)
.padding(2)
Text("artist: \(albumDetail.artistName)")
.foregroundColor(.secondary)
.padding(2)
Text("composer: \(albumDetail.copyright)")
.foregroundColor(.secondary)
.padding(2)
Text("track number: \(albumDetail.trackCount)")
.foregroundColor(.secondary)
.padding(2)
Text("release date: \(albumDetail.releaseDate)")
.foregroundColor(.secondary)
.padding(2)
Text("isrc: \(albumDetail.upc)")
.foregroundColor(.secondary)
.padding(2)
}
.font(.system(size: 14))
}
Text("\(albumDetail.editorialNotesStandard)")
.font(.system(size: 14))
.foregroundColor(.secondary)
.padding(2)
Table(albumTracks) {
TableColumn("disc", Int: \.discNumber)
TableColumn("track", value: \.releaseDate)
TableColumn("title", value: \.name)
TableColumn("artist", value: \.artistName)
TableColumn("duration ms", value: \.releaseDate)
TableColumn("release date", value: \.releaseDate)
TableColumn("isrc", value: \.isrc)
TableColumn("track id", value: \.id)
}
}
}
.padding(.leading, 20.0)
.frame(minWidth:800, idealWidth: 1000, maxWidth: .infinity, minHeight:600, idealHeight: 800, maxHeight: .infinity)
.onAppear {
SKCloudServiceController.requestAuthorization { (status) in
if status == .authorized {
self.searchResults = AppleMusicAPI().getAlbumDetails(self.albumId)
self.albumTracks = AppleMusicAPI().getAlbumTracks(self.albumId)
}
}
}
}
}
Any Help is much appreciated! Thanks!