๐ต MusicKit
Apple Music์ ์ฑ์ ํตํฉํ๋ ๊ฐ๋ ฅํ ํ๋ ์์ํฌ
iOS 9.3+Apple Music ํตํฉ
โจ MusicKit์ด๋?
MusicKit์ Apple Music์ ๋ฐฉ๋ํ ์นดํ๋ก๊ทธ์ ์ฌ์ฉ์์ ๊ฐ์ธ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ๊ทผํ ์ ์๊ฒ ํด์ฃผ๋ ํ๋ ์์ํฌ์ ๋๋ค. ์์ ์ฌ์, ๊ฒ์, ํ๋ ์ด๋ฆฌ์คํธ ์์ฑ, ์ถ์ฒ ์์ ํ์ ๋ฑ์ ์ฑ์์ ์ง์ ๊ตฌํํ ์ ์์ผ๋ฉฐ, Apple Music ๊ตฌ๋ ์์๊ฒ ์์ ํ ์คํธ๋ฆฌ๋ฐ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
๐ก ํต์ฌ ๊ธฐ๋ฅ: Apple Music ์นดํ๋ก๊ทธ ๊ฒ์ ยท ์์
์ฌ์ ยท ์ฌ์ฉ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๊ทผ ยท ํ๋ ์ด๋ฆฌ์คํธ ๊ด๋ฆฌ ยท ์ต๊ทผ ์ฌ์ ๋ชฉ๋ก ยท ์ถ์ฒ ์์
ยท ๊ตฌ๋
์ํ ํ์ธ
๐ 1. ๊ถํ ์์ฒญ ๋ฐ ์ด๊ธฐํ
MusicKit ์ฌ์ฉ ์ ์ฌ์ฉ์์ Apple Music ๊ถํ์ ์์ฒญํฉ๋๋ค.
MusicAuthManager.swift โ ๊ถํ ์์ฒญ
import MusicKit import SwiftUI @Observable class MusicAuthManager { var authorizationStatus: MusicAuthorization.Status = .notDetermined var isSubscriptionActive = false // ๊ถํ ์์ฒญ func requestAuthorization() async { let status = await MusicAuthorization.request() authorizationStatus = status switch status { case .authorized: print("โ Apple Music ๊ถํ ์น์ธ๋จ") await checkSubscriptionStatus() case .denied: print("โ Apple Music ๊ถํ ๊ฑฐ๋ถ๋จ") case .restricted: print("โ ๏ธ Apple Music ๊ถํ ์ ํ๋จ") case .notDetermined: print("๐ค Apple Music ๊ถํ ๋ฏธ๊ฒฐ์ ") @unknown default: break } } // ๊ตฌ๋ ์ํ ํ์ธ func checkSubscriptionStatus() async { let subscription = MusicSubscription.current isSubscriptionActive = subscription?.canPlayCatalogContent ?? false if isSubscriptionActive { print("๐ต Apple Music ๊ตฌ๋ ํ์ฑ") } else { print("๐ฑ Apple Music ๊ตฌ๋ ํ์") } } // ํ์ฌ ๊ถํ ์ํ ํ์ธ func checkCurrentStatus() { authorizationStatus = MusicAuthorization.currentStatus } }
๐ง 2. ์์ ์ฌ์
ApplicationMusicPlayer๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ ๋ด์์ ์์ ์ ์ฌ์ํฉ๋๋ค.
MusicPlayer.swift โ ์์
์ฌ์
import MusicKit @Observable class MusicPlayerManager { let player = ApplicationMusicPlayer.shared var currentSong: Song? var isPlaying = false var playbackTime: TimeInterval = 0 // ๋จ์ผ ๊ณก ์ฌ์ func play(song: Song) async { do { player.queue = [song] try await player.play() currentSong = song isPlaying = true print("โถ๏ธ ์ฌ์: \(song.title)") } catch { print("โ ์ฌ์ ์คํจ: \(error)") } } // ์จ๋ฒ ์ฌ์ func play(album: Album) async { do { // ์จ๋ฒ์ ๋ชจ๋ ํธ๋ ๊ฐ์ ธ์ค๊ธฐ let detailedAlbum = try await album.with([.tracks]) guard let tracks = detailedAlbum.tracks else { return } player.queue = ApplicationMusicPlayer.Queue(tracks) try await player.play() isPlaying = true } catch { print("โ ์จ๋ฒ ์ฌ์ ์คํจ: \(error)") } } // ์ฌ์/์ผ์์ ์ง ํ ๊ธ func togglePlayPause() async { if player.state.playbackStatus == .playing { player.pause() isPlaying = false } else { do { try await player.play() isPlaying = true } catch { print("โ ์ฌ์ ์คํจ: \(error)") } } } // ๋ค์ ๊ณก func skipToNext() async { do { try await player.skipToNextEntry() } catch { print("โ ๋ค์ ๊ณก ์ด๋ ์คํจ") } } // ์ด์ ๊ณก func skipToPrevious() async { do { try await player.skipToPreviousEntry() } catch { print("โ ์ด์ ๊ณก ์ด๋ ์คํจ") } } // ์ฌ์ ์์น ๋ณ๊ฒฝ func seek(to time: TimeInterval) { player.playbackTime = time } // ์ ํ ๋ชจ๋ ํ ๊ธ func toggleShuffle() { player.state.shuffleMode = player.state.shuffleMode == .off ? .songs : .off } // ๋ฐ๋ณต ๋ชจ๋ ๋ณ๊ฒฝ func cycleRepeatMode() { switch player.state.repeatMode { case .none: player.state.repeatMode = .all case .all: player.state.repeatMode = .one case .one: player.state.repeatMode = .none @unknown default: player.state.repeatMode = .none } } }
๐ 3. ์นดํ๋ก๊ทธ ๊ฒ์
Apple Music ์นดํ๋ก๊ทธ์์ ์์ ์ ๊ฒ์ํฉ๋๋ค.
MusicSearch.swift โ ์นดํ๋ก๊ทธ ๊ฒ์
import MusicKit @Observable class MusicSearchManager { var searchResults: MusicCatalogSearchResponse? var songs: [Song] = [] var albums: [Album] = [] var artists: [Artist] = [] var playlists: [Playlist] = [] // ํตํฉ ๊ฒ์ func search(term: String) async { do { var request = MusicCatalogSearchRequest( term: term, types: [Song.self, Album.self, Artist.self, Playlist.self] ) request.limit = 25 let response = try await request.response() searchResults = response songs = Array(response.songs) albums = Array(response.albums) artists = Array(response.artists) playlists = Array(response.playlists) print("๐ ๊ฒ์ ๊ฒฐ๊ณผ: ๋ ธ๋ \(songs.count), ์จ๋ฒ \(albums.count)") } catch { print("โ ๊ฒ์ ์คํจ: \(error)") } } // ๋ ธ๋๋ง ๊ฒ์ func searchSongs(term: String) async { do { var request = MusicCatalogSearchRequest(term: term, types: [Song.self]) request.limit = 50 let response = try await request.response() songs = Array(response.songs) } catch { print("โ ๋ ธ๋ ๊ฒ์ ์คํจ: \(error)") } } // ์ํฐ์คํธ ๊ฒ์ func searchArtists(term: String) async { do { var request = MusicCatalogSearchRequest(term: term, types: [Artist.self]) request.limit = 25 let response = try await request.response() artists = Array(response.artists) } catch { print("โ ์ํฐ์คํธ ๊ฒ์ ์คํจ: \(error)") } } // ํน์ ID๋ก ๋ ธ๋ ๊ฐ์ ธ์ค๊ธฐ func fetchSong(id: MusicItemID) async -> Song? { do { let request = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: id) let response = try await request.response() return response.items.first } catch { print("โ ๋ ธ๋ ๊ฐ์ ธ์ค๊ธฐ ์คํจ: \(error)") return nil } } }
๐ 4. ์ฌ์ฉ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
์ฌ์ฉ์์ Apple Music ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ๊ทผํฉ๋๋ค.
MusicLibrary.swift โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๊ทผ
import MusicKit @Observable class MusicLibraryManager { var librarySongs: [Song] = [] var libraryPlaylists: [Playlist] = [] var recentlyPlayed: [RecentlyPlayedMusicItem] = [] // ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ชจ๋ ๋ ธ๋ ๊ฐ์ ธ์ค๊ธฐ func fetchLibrarySongs() async { do { let request = MusicLibraryRequest<Song>() let response = try await request.response() librarySongs = Array(response.items) print("๐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ ธ๋: \(librarySongs.count)๊ฐ") } catch { print("โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ ธ๋ ๊ฐ์ ธ์ค๊ธฐ ์คํจ: \(error)") } } // ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๋ ์ด๋ฆฌ์คํธ ๊ฐ์ ธ์ค๊ธฐ func fetchLibraryPlaylists() async { do { let request = MusicLibraryRequest<Playlist>() let response = try await request.response() libraryPlaylists = Array(response.items) print("๐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๋ ์ด๋ฆฌ์คํธ: \(libraryPlaylists.count)๊ฐ") } catch { print("โ ํ๋ ์ด๋ฆฌ์คํธ ๊ฐ์ ธ์ค๊ธฐ ์คํจ: \(error)") } } // ์ต๊ทผ ์ฌ์ ๋ชฉ๋ก func fetchRecentlyPlayed() async { do { let request = MusicRecentlyPlayedRequest<RecentlyPlayedMusicItem>() let response = try await request.response() recentlyPlayed = Array(response.items) print("๐ ์ต๊ทผ ์ฌ์: \(recentlyPlayed.count)๊ฐ") } catch { print("โ ์ต๊ทผ ์ฌ์ ๊ฐ์ ธ์ค๊ธฐ ์คํจ: \(error)") } } // ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ ธ๋ ์ถ๊ฐ func addToLibrary(song: Song) async { do { try await MusicLibrary.shared.add(song) print("โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ถ๊ฐ: \(song.title)") } catch { print("โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ ์คํจ: \(error)") } } // ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์จ๋ฒ ์ถ๊ฐ func addToLibrary(album: Album) async { do { try await MusicLibrary.shared.add(album) print("โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์จ๋ฒ ์ถ๊ฐ: \(album.title)") } catch { print("โ ์จ๋ฒ ์ถ๊ฐ ์คํจ: \(error)") } } }
๐ผ 5. ํ๋ ์ด๋ฆฌ์คํธ ๊ด๋ฆฌ
ํ๋ ์ด๋ฆฌ์คํธ๋ฅผ ์์ฑํ๊ณ ๊ด๋ฆฌํฉ๋๋ค.
PlaylistManager.swift โ ํ๋ ์ด๋ฆฌ์คํธ
import MusicKit @Observable class PlaylistManager { var playlists: [Playlist] = [] // ํ๋ ์ด๋ฆฌ์คํธ ์์ฑ func createPlaylist(name: String, description: String, songs: [Song]) async { do { let playlist = try await MusicLibrary.shared.createPlaylist( name: name, description: description, items: songs ) print("โ ํ๋ ์ด๋ฆฌ์คํธ ์์ฑ: \(playlist.name)") } catch { print("โ ํ๋ ์ด๋ฆฌ์คํธ ์์ฑ ์คํจ: \(error)") } } // ํ๋ ์ด๋ฆฌ์คํธ์ ๊ณก ์ถ๊ฐ func addSongs(to playlist: Playlist, songs: [Song]) async { do { try await MusicLibrary.shared.add(songs, to: playlist) print("โ \(songs.count)๊ณก ์ถ๊ฐ๋จ") } catch { print("โ ๊ณก ์ถ๊ฐ ์คํจ: \(error)") } } // ํ๋ ์ด๋ฆฌ์คํธ ์์ธ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ func fetchPlaylistDetails(playlist: Playlist) async -> Playlist? { do { let detailed = try await playlist.with([.tracks, .curator]) return detailed } catch { print("โ ํ๋ ์ด๋ฆฌ์คํธ ์์ธ ์ ๋ณด ์คํจ: \(error)") return nil } } }
๐ก HIG ๊ฐ์ด๋๋ผ์ธ
- ๊ถํ ์์ฒญ: ์ฌ์ฉ์์๊ฒ ๋ช ํํ ์ด์ ๋ฅผ ์ ๊ณตํ๊ณ ๊ถํ ์์ฒญ
- ๊ตฌ๋ ์ํ: ๋น๊ตฌ๋ ์์๊ฒ Apple Music ๊ฐ์ ์๋ด
- ์คํ๋ผ์ธ: ๋คํธ์ํฌ ์ค๋ฅ ์ ์ ์ ํ ์๋ด ๋ฉ์์ง
- ์จ๋ฒ ์ํธ: AsyncImage๋ก ์จ๋ฒ ์ปค๋ฒ ๋ก๋
- ์ฌ์ ์ํ: ํ์ฌ ์ฌ์ ์ค์ธ ๊ณก์ ์๊ฐ์ ์ผ๋ก ํ์
๐ฏ ์ค์ ํ์ฉ
- ์์ ํ๋ ์ด์ด: ์ ์ฒด ๊ธฐ๋ฅ์ ๊ฐ์ถ ์์ ์ฌ์ ์ฑ
- ์ด๋ ์ฑ: ์ด๋ ์ค ์์ ์ฌ์
- ๋๋ผ์ด๋น ์ฑ: ์ด์ ์ค ์์ ์ปจํธ๋กค
- ์์ ์ฑ: ์น๊ตฌ์ ์์ ๊ณต์
- ๋ถ์ ์ฑ: ์์ ์ทจํฅ ๋ถ์ ๋ฐ ์ถ์ฒ
๐ ๋ ์์๋ณด๊ธฐ
โก๏ธ ์ฑ๋ฅ ํ:
MusicCatalogSearchRequest์ limit์ ์ ์ ํ ์ค์ ํ์ฌ ๋ถํ์ํ ๋ฐ์ดํฐ ๋ก๋๋ฅผ ๋ฐฉ์งํ์ธ์. ํ์ด์ง๋ค์ด์
์ ๊ตฌํํ๋ฉด ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.