From 0b981a481194d893705b73348a50838d8c21b942 Mon Sep 17 00:00:00 2001 From: Rudrank Riyam Date: Mon, 29 Aug 2022 23:10:18 +0530 Subject: [PATCH] Add iOS 16+ methods to library song and artist --- .../History/MusicHistoryEndpoints.swift | 26 +++++++-- .../History/MusicRecentlyAddedRequest.swift | 4 +- .../MusadoraKit/Library/LibraryArtists.swift | 22 +++++++ Sources/MusadoraKit/Library/LibrarySong.swift | 58 +++++++++++-------- .../Models/AppleMusicURLComponents.swift | 2 +- .../MusadoraKit/Models/MusadoraKitError.swift | 48 +++++++-------- .../MusadoraKit/Models/UserMusicItem.swift | 32 +++++----- 7 files changed, 120 insertions(+), 72 deletions(-) diff --git a/Sources/MusadoraKit/History/MusicHistoryEndpoints.swift b/Sources/MusadoraKit/History/MusicHistoryEndpoints.swift index 941605719..23f646d70 100644 --- a/Sources/MusadoraKit/History/MusicHistoryEndpoints.swift +++ b/Sources/MusadoraKit/History/MusicHistoryEndpoints.swift @@ -18,11 +18,27 @@ public enum MusicHistoryEndpoints { var path: String { switch self { - case .heavyRotation: return "history/heavy-rotation" - case .recentlyAdded: return "library/recently-added" - case .recentlyPlayed: return "recent/played" - case .recentlyPlayedTracks: return "recent/played/tracks" - case .recentlyPlayedStations: return "recent/radio-stations" + case .heavyRotation: + return "history/heavy-rotation" + case .recentlyAdded: + return "library/recently-added" + case .recentlyPlayed: + return "recent/played" + case .recentlyPlayedTracks: + return "recent/played/tracks" + case .recentlyPlayedStations: + return "recent/radio-stations" + } + } + + var maximumLimit: Int { + switch self { + case .heavyRotation, .recentlyPlayed, .recentlyPlayedStations: + return 10 + case .recentlyAdded: + return 25 + case .recentlyPlayedTracks: + return 30 } } } diff --git a/Sources/MusadoraKit/History/MusicRecentlyAddedRequest.swift b/Sources/MusadoraKit/History/MusicRecentlyAddedRequest.swift index 04d136425..214b3278f 100644 --- a/Sources/MusadoraKit/History/MusicRecentlyAddedRequest.swift +++ b/Sources/MusadoraKit/History/MusicRecentlyAddedRequest.swift @@ -10,7 +10,7 @@ import Foundation /// A protocol for music items that your app can fetch by /// using a recently added request. -public protocol MusicRecentlyAddedRequestable : MusicItem { +public protocol MusicRecentlyAddedRequestable: MusicItem { } /// A request that your app uses to fetch items the user has recently added. @@ -46,9 +46,7 @@ extension MusicRecentlyAddedResponse: Sendable { } extension MusicRecentlyAddedResponse: Decodable where MusicItemType: Decodable { - } extension MusicRecentlyAddedResponse: Encodable where MusicItemType: Encodable { - } diff --git a/Sources/MusadoraKit/Library/LibraryArtists.swift b/Sources/MusadoraKit/Library/LibraryArtists.swift index eab2e15ec..a9512235e 100644 --- a/Sources/MusadoraKit/Library/LibraryArtists.swift +++ b/Sources/MusadoraKit/Library/LibraryArtists.swift @@ -9,10 +9,31 @@ import MusicKit import MediaPlayer public extension MusadoraKit { + +#if compiler(>=5.7) /// Fetch an artist from the user's library by using its identifier. /// - Parameters: /// - id: The unique identifier for the artist. /// - Returns: `Artist` matching the given identifier. + /// + /// - Note: This method fetches the artist locally from the device when using iOS 16+ + /// and is faster because it uses the latest `MusicLibraryRequest` structure. + /// For iOS 15 devices, it uses the custom structure `MusicLibraryResourceRequest` + /// that fetches the data from Apple Music API. + @available(iOS 16.0, tvOS 16.0, watchOS 9.0, *) + @available(macOS, unavailable) + @available(macCatalyst, unavailable) + static func libraryArtist(id: MusicItemID) async throws -> Artist { + var request = MusicLibraryRequest() + request.filter(matching: \.id, equalTo: id) + let response = try await request.response() + + guard let artist = response.items.first else { + throw MusadoraKitError.notFound(for: id.rawValue) + } + return artist + } + #else static func libraryArtist(id: MusicItemID) async throws -> Artist { let request = MusicLibraryResourceRequest(matching: \.id, equalTo: id) let response = try await request.response() @@ -22,6 +43,7 @@ public extension MusadoraKit { } return artist } + #endif /// Fetch all artists from the user's library in alphabetical order. /// - Parameters: diff --git a/Sources/MusadoraKit/Library/LibrarySong.swift b/Sources/MusadoraKit/Library/LibrarySong.swift index 776f37905..1eb6b1fd2 100644 --- a/Sources/MusadoraKit/Library/LibrarySong.swift +++ b/Sources/MusadoraKit/Library/LibrarySong.swift @@ -9,6 +9,8 @@ import MusicKit import MediaPlayer public extension MusadoraKit { + +#if compiler(>=5.7) /// Fetch a song from the user's library by using its identifier. /// - Parameters: /// - id: The unique identifier for the song. @@ -16,30 +18,23 @@ public extension MusadoraKit { /// /// - Note: This method fetches the song locally from the device when using iOS 16+ /// and is faster because it uses the latest `MusicLibraryRequest` structure. - /// iOS 15 uses the custom structure `MusicLibraryResourceRequest` that - /// fetches the data from Apple Music API. + /// For iOS 15 devices, it uses the custom structure `MusicLibraryResourceRequest` + /// that fetches the data from Apple Music API. + @available(iOS 16.0, tvOS 16.0, watchOS 9.0, *) + @available(macOS, unavailable) + @available(macCatalyst, unavailable) static func librarySong(id: MusicItemID) async throws -> Song { -#if compiler(>=5.7) - if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) { - var request = MusicLibraryRequest() - request.filter(matching: \.id, equalTo: id) - let response = try await request.response() + var request = MusicLibraryRequest() + request.filter(matching: \.id, equalTo: id) + let response = try await request.response() - guard let song = response.items.first else { - throw MusadoraKitError.notFound(for: id.rawValue) - } - return song - } else { - return try await fetchLibrarySong(id: id) + guard let song = response.items.first else { + throw MusadoraKitError.notFound(for: id.rawValue) } -#else - return try await fetchLibrarySong(id: id) -#endif + return song } -} - -public extension MusadoraKit { - static private func fetchLibrarySong(id: MusicItemID) async throws -> Song { +#else + static func librarySong(id: MusicItemID) async throws -> Song { let request = MusicLibraryResourceRequest(matching: \.id, equalTo: id) let response = try await request.response() @@ -48,19 +43,34 @@ public extension MusadoraKit { } return song } -} +#endif -public extension MusadoraKit { +#if compiler(>=5.7) /// Fetch all songs from the user's library in alphabetical order. /// - Parameters: /// - limit: The number of songs returned. /// - Returns: `Songs` for the given limit. + /// + /// - Note: This method fetches the song locally from the device when using iOS 16+ + /// and is faster because it uses the latest `MusicLibraryRequest` structure. + /// For iOS 15 devices, it uses the custom structure `MusicLibraryResourceRequest` + /// that fetches the data from Apple Music API. + @available(iOS 16.0, tvOS 16.0, watchOS 9.0, *) + @available(macOS, unavailable) + @available(macCatalyst, unavailable) + static func librarySongs(limit: Int? = 50) async throws -> Songs { + let request = MusicLibraryRequest() + let response = try await request.response() + return response.items + } +#else static func librarySongs(limit: Int? = nil) async throws -> Songs { var request = MusicLibraryResourceRequest() request.limit = limit let response = try await request.response() return response.items } +#endif /// Fetch multiple songs from the user's library by using their identifiers. /// - Parameters: @@ -78,7 +88,9 @@ public extension MusadoraKit { /// - id: The unique identifier for the song. /// - Returns: `Song` matching the given identifier. @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) - static func localLibrarySong(id: MusicItemID, with properties: SongProperties = .all) async throws -> Song { + @available(macOS, unavailable) + @available(macCatalyst, unavailable) + static func librarySong(id: MusicItemID, with properties: SongProperties = .all) async throws -> Song { var request = MusicLibraryRequest() request.filter(matching: \.id, equalTo: id) let response = try await request.response() diff --git a/Sources/MusadoraKit/Models/AppleMusicURLComponents.swift b/Sources/MusadoraKit/Models/AppleMusicURLComponents.swift index 8ddcdc194..b977dda3a 100644 --- a/Sources/MusadoraKit/Models/AppleMusicURLComponents.swift +++ b/Sources/MusadoraKit/Models/AppleMusicURLComponents.swift @@ -15,7 +15,7 @@ struct AppleMusicURLComponents { components.scheme = "https" components.host = "api.music.apple.com" } - + var queryItems: [URLQueryItem]? { get { components.queryItems diff --git a/Sources/MusadoraKit/Models/MusadoraKitError.swift b/Sources/MusadoraKit/Models/MusadoraKitError.swift index d8dd204ce..356634a01 100644 --- a/Sources/MusadoraKit/Models/MusadoraKitError.swift +++ b/Sources/MusadoraKit/Models/MusadoraKitError.swift @@ -16,10 +16,10 @@ public enum RatingsError: Error, Equatable { extension RatingsError: CustomStringConvertible { public var description: String { switch self { - case .idMissing: - return "One or more ID must be specified to fetch the ratings for it." - case .typeMissing: - return "The music item type must be specified to fetch its ratings." + case .idMissing: + return "One or more ID must be specified to fetch the ratings for it." + case .typeMissing: + return "The music item type must be specified to fetch its ratings." } } } @@ -47,14 +47,14 @@ public enum MusadoraKitError: Error, Equatable { extension MusadoraKitError: CustomStringConvertible { public var description: String { switch self { - case let .notFound(id): - return "The specified music item could not be found for \(id)." - case .typeMissing: - return "One or more types must be specified for fetching top results in search suggestions." - case let .recommendationOverLimit(limit): - return "Value must be an integer less than or equal to 30, but was: \(limit)." - case let .historyOverLimit(limit, overLimit): - return "Value must be an integer less than or equal to \(limit), but was: \(overLimit)." + case let .notFound(id): + return "The specified music item could not be found for \(id)." + case .typeMissing: + return "One or more types must be specified for fetching top results in search suggestions." + case let .recommendationOverLimit(limit): + return "Value must be an integer less than or equal to 30, but was: \(limit)." + case let .historyOverLimit(limit, overLimit): + return "Value must be an integer less than or equal to \(limit), but was: \(overLimit)." } } } @@ -62,18 +62,18 @@ extension MusadoraKitError: CustomStringConvertible { extension MusadoraKitError: LocalizedError { public var errorDescription: String? { switch self { - case let .notFound(id): - return NSLocalizedString("The specified music item could not be found for \(id).", - comment: "Resource Not Found") - case .typeMissing: - return NSLocalizedString("One or more types must be specified for fetching top results in search suggestions.", - comment: "Missing Parameter") - case let .recommendationOverLimit(limit): - return NSLocalizedString("Value must be an integer less than or equal to 30, but was: \(limit).", - comment: "Invalid Parameter Value") - case let .historyOverLimit(limit, overLimit): - return NSLocalizedString("Value must be an integer less than or equal to \(limit), but was: \(overLimit).", - comment: "Invalid Parameter Value") + case let .notFound(id): + return NSLocalizedString("The specified music item could not be found for \(id).", + comment: "Resource Not Found") + case .typeMissing: + return NSLocalizedString("One or more types must be specified for fetching top results in search suggestions.", + comment: "Missing Parameter") + case let .recommendationOverLimit(limit): + return NSLocalizedString("Value must be an integer less than or equal to 30, but was: \(limit).", + comment: "Invalid Parameter Value") + case let .historyOverLimit(limit, overLimit): + return NSLocalizedString("Value must be an integer less than or equal to \(limit), but was: \(overLimit).", + comment: "Invalid Parameter Value") } } } diff --git a/Sources/MusadoraKit/Models/UserMusicItem.swift b/Sources/MusadoraKit/Models/UserMusicItem.swift index 8a415a319..ae4478b9f 100644 --- a/Sources/MusadoraKit/Models/UserMusicItem.swift +++ b/Sources/MusadoraKit/Models/UserMusicItem.swift @@ -23,10 +23,10 @@ extension UserMusicItem: MusicItem { let id: MusicItemID switch self { - case let .album(album): id = album.id - case let .playlist(playlist): id = playlist.id - case let .station(station): id = station.id - case let .track(track): id = track.id + case let .album(album): id = album.id + case let .playlist(playlist): id = playlist.id + case let .station(station): id = station.id + case let .track(track): id = track.id } return id @@ -55,18 +55,18 @@ extension UserMusicItem: Decodable { let type = try values.decode(HistoryMusicItemTypes.self, forKey: .type) switch type { - case .album, .libraryAlbum: - let album = try Album(from: decoder) - self = .album(album) - case .playlist, .libraryPlaylist: - let playlist = try Playlist(from: decoder) - self = .playlist(playlist) - case .station: - let station = try Station(from: decoder) - self = .station(station) - case .song, .librarySong, .musicVideo, .libraryMusicVideo: - let track = try Track(from: decoder) - self = .track(track) + case .album, .libraryAlbum: + let album = try Album(from: decoder) + self = .album(album) + case .playlist, .libraryPlaylist: + let playlist = try Playlist(from: decoder) + self = .playlist(playlist) + case .station: + let station = try Station(from: decoder) + self = .station(station) + case .song, .librarySong, .musicVideo, .libraryMusicVideo: + let track = try Track(from: decoder) + self = .track(track) } } }