You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:使用AVFoundation播放iOS音乐库内容并实现后台播报

Solution for AVFoundation Playback of Apple Music Library with Background Support (Private API allowed)

Hey there, I’ve dealt with this exact frustration before—Media Player’s background notifications are notoriously unreliable, and AVFoundation’s public API doesn’t play nice with those ipod-library:// URLs. Since you’re only using this app personally, let’s leverage private APIs to get this working smoothly.

First, Setup Background Audio

Before writing any code, make sure your app is configured for background playback:

  • Open your Info.plist
  • Add the UIBackgroundModes array if it doesn’t exist
  • Add the string value audio to this array

Core Implementation: Music Player Manager

We’ll create a singleton manager to handle playback, music library integration, and speech synthesis. This uses a private MPMediaItem property to get the actual local file path of each song (since the public ipod-library URL won’t work with AVPlayer).

import UIKit
import AVFoundation
import MediaPlayer
import AVFAudio

class MusicPlayerManager: NSObject {
    static let shared = MusicPlayerManager()
    
    private var player: AVPlayer?
    private var currentPlaylist: [MPMediaItem] = []
    private var currentIndex: Int = 0
    private let speechSynthesizer = AVSpeechSynthesizer()
    
    private override init() {
        super.init()
        setupAudioSession()
        setupPlaybackNotifications()
    }
    
    // Configure audio session for background playback
    private func setupAudioSession() {
        do {
            let session = AVAudioSession.sharedInstance()
            try session.setCategory(.playback, mode: .default, options: [.mixWithOthers])
            try session.setActive(true)
        } catch {
            print("Audio session setup failed: \(error.localizedDescription)")
        }
    }
    
    // Listen for AVFoundation playback events (works in background)
    private func setupPlaybackNotifications() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(onTrackFinished),
            name: .AVPlayerItemDidPlayToEndTime,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(onPlaybackStateChanged),
            name: .AVPlayerRateDidChange,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(onItemStatusUpdated),
            name: .AVPlayerItemStatusDidChange,
            object: nil
        )
    }
    
    // Load a playlist from Media Player framework
    func loadPlaylist(_ playlist: MPMediaItemCollection) {
        currentPlaylist = playlist.items
        currentIndex = 0
        if !currentPlaylist.isEmpty {
            playTrack(at: currentIndex)
        }
    }
    
    // Play a track using its private local file path
    private func playTrack(at index: Int) {
        guard index < currentPlaylist.count else { return }
        let mediaItem = currentPlaylist[index]
        
        // Access private MPMediaItem property to get local file path
        guard let localFilePath = mediaItem.value(forKey: "_path") as? String else {
            print("Couldn't retrieve local path for track")
            playNextTrack()
            return
        }
        
        let fileURL = URL(fileURLWithPath: localFilePath)
        let playerItem = AVPlayerItem(url: fileURL)
        
        if player == nil {
            player = AVPlayer(playerItem: playerItem)
        } else {
            player?.replaceCurrentItem(with: playerItem)
        }
        
        player?.play()
        announceTrackInfo(mediaItem)
    }
    
    // Use AVSpeechSynthesizer to announce track details
    private func announceTrackInfo(_ item: MPMediaItem) {
        if speechSynthesizer.isSpeaking {
            speechSynthesizer.stopSpeaking(at: .immediate)
        }
        
        let title = item.title ?? "Unknown Track"
        let artist = item.artist ?? "Unknown Artist"
        let announcement = AVSpeechUtterance(string: "Now playing \(title) by \(artist)")
        
        announcement.voice = AVSpeechSynthesisVoice(language: "en-US")
        announcement.rate = 0.5 // Adjust speech rate as needed
        speechSynthesizer.speak(announcement)
    }
    
    // Handle track completion
    @objc private func onTrackFinished() {
        playNextTrack()
    }
    
    // Handle play/pause state changes
    @objc private func onPlaybackStateChanged() {
        guard let player = player else { return }
        let announcementText = player.rate == 0 ? "Playback paused" : "Playback resumed"
        let utterance = AVSpeechUtterance(string: announcementText)
        speechSynthesizer.speak(utterance)
    }
    
    // Handle AVPlayerItem status changes (e.g., playback errors)
    @objc private func onItemStatusUpdated(notification: Notification) {
        guard let item = notification.object as? AVPlayerItem else { return }
        if item.status == .failed {
            print("Track playback failed: \(item.error?.localizedDescription ?? "Unknown error")")
            playNextTrack()
        }
    }
    
    // Public control methods
    func togglePlayPause() {
        player?.rate == 0 ? player?.play() : player?.pause()
    }
    
    func playNextTrack() {
        currentIndex = (currentIndex + 1) % currentPlaylist.count
        playTrack(at: currentIndex)
    }
    
    func playPreviousTrack() {
        currentIndex = (currentIndex - 1 + currentPlaylist.count) % currentPlaylist.count
        playTrack(at: currentIndex)
    }
}

Using the Manager in Your View Controller

Here’s how to integrate this with a basic UI to load playlists and control playback:

class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        requestMusicLibraryAccess()
    }
    
    // Request permission to access the user's music library
    private func requestMusicLibraryAccess() {
        MPMediaLibrary.requestAuthorization { status in
            guard status == .authorized else {
                print("Music library access denied")
                return
            }
            DispatchQueue.main.async {
                self.loadUserPlaylists()
            }
        }
    }
    
    // Load playlists and start playing the first one
    private func loadUserPlaylists() {
        let playlistQuery = MPMediaQuery.playlists()
        guard let playlists = playlistQuery.collections else { return }
        
        // Replace this with playlist selection logic if needed
        if let firstPlaylist = playlists.first {
            MusicPlayerManager.shared.loadPlaylist(firstPlaylist)
        }
    }
    
    // Example button actions (connect these to UI buttons)
    @IBAction func playPauseTapped(_ sender: UIButton) {
        MusicPlayerManager.shared.togglePlayPause()
    }
    
    @IBAction func nextTapped(_ sender: UIButton) {
        MusicPlayerManager.shared.playNextTrack()
    }
    
    @IBAction func previousTapped(_ sender: UIButton) {
        MusicPlayerManager.shared.playPreviousTrack()
    }
}

Key Notes

  • Private API Warning: The _path property on MPMediaItem is not public, so this code will be rejected by the App Store. But since you’re using this personally, that’s not an issue. Keep in mind that private APIs can break with iOS updates—if this stops working in a new iOS version, you may need to find the updated private property name (tools like class-dump can help here).
  • Background Stability: As long as the audio session is active, AVFoundation’s notifications will fire in the background, which fixes your original Media Player framework issue.
  • Speech Synthesis: The speech synthesizer will work in the background while audio is playing, since the audio session is already active. If you need to announce when no audio is playing, you may need additional configuration for background audio tasks.

内容的提问来源于stack exchange,提问作者Uncle Bill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:59:45