mcpField
A
axiom-now-playing-musickit

axiom-now-playing-musickit

github.com/charleswiltgen開発1.2k166 回インストール今日更新
リポジトリ

axiom-now-playing-musickit について

MusicKitでApple Musicを統合するためのSwiftスキルです。認証、サブスクリプション確認、再生とキュー管理に加え、ApplicationMusicPlayerがNow Playing情報を自動更新する仕組みを説明します。

主な機能

  1. 01MusicKitの認証要求
  2. 02Apple Musicのサブスクリプション確認
  3. 03再生とキューの制御
  4. 04再生状態とキューの監視
  5. 05ハイブリッドコンテンツの再生

活用シーン

  1. 01Apple Musicの再生フロー構築
  2. 02サブスクリプションに応じたカタログ再生制御
  3. 03現在の曲とアートワークの表示
  4. 04Apple Musicとアプリ内コンテンツの併用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-now-playing-musickit

インストール後、Claude Code で/axiom-now-playing-musickitを入力して読み込まれたか確認します。

詳細

MusicKit の統合(Apple Music)

所要時間:5~10 分

重要なポイント

MusicKit の ApplicationMusicPlayer は、MPNowPlayingInfoCenter に情報を自動的に公開します。 Apple Music のコンテンツを再生する場合、「再生中」の情報を手動で更新する必要はありません。

自動で処理される項目

ApplicationMusicPlayer を使用すると、次の項目が自動的に処理されます。

  • トラック名、アーティスト、アルバム
  • アートワーク(Apple のアルバムアート)
  • 再生時間と経過時間
  • 再生レート(再生中/一時停止の状態)

MPNowPlayingInfoCenter の更新は、システムがすべて処理します。

自動で処理されない項目

  • カスタムメタデータ(チャプターマーカー、カスタムのアーティストノート)
  • 標準のコントロールを超えるリモートコマンドのカスタマイズ
  • MusicKit のコンテンツと独自のコンテンツの混在

サブスクリプションと認証

Music の認証を確認する
import MusicKit

func requestMusicAccess() async -> Bool {
    let status = await MusicAuthorization.request()
    return status == .authorized
}

// Check current status without prompting
let currentStatus = MusicAuthorization.currentStatus
// .authorized, .denied, .notDetermined, .restricted
Apple Music のサブスクリプションを確認する
func checkSubscription() async -> Bool {
    do {
        let subscription = try await MusicSubscription.current
        return subscription.canPlayCatalogContent
    } catch {
        return false
    }
}

// Observe subscription changes
func observeSubscription() {
    Task {
        for await subscription in MusicSubscription.subscriptionUpdates {
            if subscription.canPlayCatalogContent {
                // Full Apple Music access
            } else if subscription.canBecomeSubscriber {
                // Show subscription offer
                showSubscriptionOffer()
            }
        }
    }
}
サブスクリプションのオファーシート
import MusicKit
import StoreKit

// Present Apple Music subscription offer
MusicSubscriptionOffer.Options(
    messageIdentifier: .playMusic,
    itemID: song.id
)

// In SwiftUI
.musicSubscriptionOffer(isPresented: $showOffer, options: offerOptions)
サブスクリプションがない場合の適切なフォールバック
@MainActor
class MusicPlayer: ObservableObject {
    @Published var canPlay = false

    func handlePlayRequest(song: Song) async {
        let authorized = await requestMusicAccess()
        guard authorized else {
            showAuthorizationDeniedAlert()
            return
        }

        do {
            let subscription = try await MusicSubscription.current
            if subscription.canPlayCatalogContent {
                // Full playback
                try await play(song: song)
            } else {
                // Preview only (30-second clips)
                if let previewURL = song.previewAssets?.first?.url {
                    playPreview(url: previewURL)
                }
            }
        } catch {
            handleError(error)
        }
    }
}

再生

基本的な再生
import MusicKit

@MainActor
class MusicKitPlayer {
    private let player = ApplicationMusicPlayer.shared

    func play(song: Song) async throws {
        // ✅ Just play - MPNowPlayingInfoCenter updates automatically
        player.queue = [song]
        try await player.play()

        // ❌ DO NOT manually set nowPlayingInfo here
        // MPNowPlayingInfoCenter.default().nowPlayingInfo = [...] // WRONG!
    }

    func pause() {
        player.pause()
    }

    func stop() {
        player.stop()
    }
}
再生状態の監視
@MainActor
class PlayerViewModel: ObservableObject {
    private let player = ApplicationMusicPlayer.shared
    @Published var isPlaying = false
    @Published var currentEntry: ApplicationMusicPlayer.Queue.Entry?
    @Published var playbackTime: TimeInterval = 0

    func observeState() {
        // Observe playback status
        Task {
            for await state in player.state.objectWillChange.values {
                isPlaying = player.state.playbackStatus == .playing
            }
        }

        // Observe current entry (track changes)
        Task {
            for await queue in player.queue.objectWillChange.values {
                currentEntry = player.queue.currentEntry
            }
        }
    }
}

キュー管理

キューの設定
let player = ApplicationMusicPlayer.shared

// Single song
player.queue = [song]

// Album
player.queue = ApplicationMusicPlayer.Queue(album: album)

// Playlist
player.queue = ApplicationMusicPlayer.Queue(playlist: playlist)

// Multiple items
player.queue = ApplicationMusicPlayer.Queue(for: [song1, song2, song3])

// Start at specific item
player.queue = ApplicationMusicPlayer.Queue(for: songs, startingAt: songs[2])
キューの操作
// Skip to next
try await player.skipToNextEntry()

// Skip to previous
try await player.skipToPreviousEntry()

// Restart current track
player.restartCurrentEntry()

// Append to queue
try await player.queue.insert(song, position: .afterCurrentEntry)
try await player.queue.insert(song, position: .tail)  // End of queue

// Shuffle and repeat
player.state.shuffleMode = .songs    // .off, .songs
player.state.repeatMode = .all       // .none, .one, .all
キューの変更の監視
// Current track info
if let entry = player.queue.currentEntry {
    let title = entry.title
    let subtitle = entry.subtitle      // Artist name
    let artwork = entry.artwork         // Artwork for display

    // Get full Song object if needed
    if case .song(let song) = entry.item {
        let albumTitle = song.albumTitle
    }
}

ハイブリッドアプリ(独自コンテンツ+Apple Music)

アプリで Apple Music と独自のコンテンツの両方を再生する場合:

import MusicKit

@MainActor
class HybridPlayer {
    private let musicKitPlayer = ApplicationMusicPlayer.shared
    private var avPlayer: AVPlayer?
    private var currentSource: ContentSource = .none

    enum ContentSource {
        case none
        case appleMusic      // MusicKit handles Now Playing
        case ownContent  // We handle Now Playing
    }

    func playAppleMusicSong(_ song: Song) async throws {
        // Switch to MusicKit
        avPlayer?.pause()
        currentSource = .appleMusic

        musicKitPlayer.queue = [song]
        try await musicKitPlayer.play()
        // ✅ MusicKit handles Now Playing automatically
    }

    func playOwnContent(_ url: URL) {
        // Switch to AVPlayer
        musicKitPlayer.pause()
        currentSource = .ownContent

        avPlayer = AVPlayer(url: url)
        avPlayer?.play()

        // ✅ Manually update Now Playing (see axiom-now-playing)
        updateNowPlayingForOwnContent()
    }

    private func updateNowPlayingForOwnContent() {
        var nowPlayingInfo = [String: Any]()
        nowPlayingInfo[MPMediaItemPropertyTitle] = "My Track"
        // ... rest of manual setup
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }
}

よくある間違い

// ❌ WRONG - Overwrites MusicKit's automatic Now Playing data
func playAppleMusicSong(_ song: Song) async throws {
    try await ApplicationMusicPlayer.shared.play()

    // ❌ This clears MusicKit's Now Playing info!
    var nowPlayingInfo = [String: Any]()
    nowPlayingInfo[MPMediaItemPropertyTitle] = song.title
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}

// ✅ CORRECT - Let MusicKit handle it
func playAppleMusicSong(_ song: Song) async throws {
    try await ApplicationMusicPlayer.shared.play()
    // That's it! MusicKit publishes Now Playing automatically.
}

MusicKit で手動更新を使用する場合

次の場合に限り、MPNowPlayingInfoCenter を上書きしてください。

  • メタデータを追加する場合(例:ポッドキャストのチャプターマーカー)
  • Apple Music と並べてカスタムコンテンツを表示する場合
  • MusicKit の自動動作を置き換える具体的な理由がある場合

デフォルト:MusicKit に「再生中」の情報を自動管理させます。

リソース

ドキュメント:/musickit、/musickit/applicationmusicplayer、/musickit/musicsubscription

スキル:axiom-now-playing、axiom-now-playing-carplay

コメント