Swift4で音楽プレーヤーを作ってみた！(MPMusicPlayerController)

# **目次**
- はじめに
- 開発環境
- リピートモードの有効化
- 再生中の曲の状態を取得
- バックグラウンド再生に対応する
- UILabelをオートスクロール(動作に問題あり)
- フルコード
- 完成
- 感想
- 参考資料


# **はじめに**
このアプリを開発する上で、役に立った記事を紹介する。
MPMusicPlayerControllerについては、この記事を参考にして欲しい。

↓Swiftの書式が古いので、注意。
[[iOS][Swift]ミュージックライブラリの音楽の再生、情報の表示(MPMusicPlayerController使用)](https://nackpan.net/blog/2016/04/13/ios-swift-musicplayercontroller-2/)

公式リファレンスも載せておく。
[MPMusicPlayerController](https://developer.apple.com/documentation/mediaplayer/mpmusicplayercontroller)


ここでは、上記の記事で書かれていない点を書くことにする。

# **開発環境**
- Xcode9.3
- Swift4.1

# **リピートモードの有効化**
```swift
var player: MPMusicPlayerController!

//systemMusicControllerにする。
player = MPMusicPlayerController.systemMusicPlayer

//リピートの無効化
player.repeatMode = .none

//リピートの有効化(１曲をリピート)
player.repeatMode = .one
```

# **再生中の曲の状態を取得**

```swift
let playStatus = player.playbackState

//停止中
if playstatus == .stopped {
  //処理
}

//一時停止中 
else if playstatus == .paused {
  //処理
} 
//再生中
else if playstatus == .playing {
 //処理
}
```

# **バックグラウンド再生に対応する**
`Capabilities`>`Background Mode`で、`Audio,AirPlay,and Picture in Picture`にチェックを入れる。
僕の環境の場合、これで動いた。

<img width="1173" alt="スクリーンショット 2018-05-07 19.32.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/f45d6265-1eb0-7c37-deee-fa866dbcd03e.png">

# **UILabelをオートスクロール(動作不安定)**
参考資料:[【Swift4.0】テキストのオートスクロールを実装するライブラリ【iOS】](http://daisa-n.com/blog/swift-text-autoscroll/)

Cocoapodsで、CBAutoScrollLabelをインストール。

```
pod `AutoScrollLabel`
```

各種設定はこんな感じ。

```swift
artistLabel.labelSpacing = 50; // 開始と終了の間間隔
artistLabel.pauseInterval = 3; // スクロール前の一時停止時間
artistLabel.scrollSpeed = 50.0; // スクロール速度
artistLabel.fadeLength = 20.0; // 左端と右端のフェードの長さ
```

しかし、実際に導入したものの、**稀にスクロールしない時がある**。
原因はわからず。。。
                        
# **フルコード**
フルコードを載せておく。
再生ボタンなどは、一から画像を作成して、UIButton.setImageでセットしている。

```swift:Viewcontroller.swift
import UIKit
import MediaPlayer
import AutoScrollLabel

let w = UIScreen.main.bounds.size.width
let h = UIScreen.main.bounds.size.height
class ViewController: UIViewController, MPMediaPickerControllerDelegate {
    
    let artistLabel = CBAutoScrollLabel(frame: CGRect(x: 0, y: w + 160, width: w, height: 30))
    let albumLabel = CBAutoScrollLabel(frame: CGRect(x: 0, y: w + 100, width: w, height: 30))
    let songLabel = CBAutoScrollLabel(frame: CGRect(x: 0, y: w + 130, width: w, height: 30))
    let imageView = UIImageView(frame: CGRect(x: 0, y: 100, width: w, height: w))
    var playpause = UIButton(frame: CGRect(x: (w - 60) / 2, y: 580, width: 60, height: 60))
    let reb = UIButton(frame: CGRect(x:(w - 60) / 4, y: 580, width: 60, height: 60))
    var player: MPMusicPlayerController!

    override func viewDidLoad() {
        
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        view.backgroundColor = UIColor.rgb(r: 40, g: 40, b: 40, alpha: 1.0)
        player = MPMusicPlayerController.systemMusicPlayer
        player.repeatMode = .none
        // プレイヤーを止める
        player.stop()
        
        // 再生中のItemが変わった時に通知を受け取る
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(self, selector: #selector(type(of: self).b(notification:)), name: NSNotification.Name.MPMusicPlayerControllerPlaybackStateDidChange, object: player)
        notificationCenter.addObserver(self, selector: #selector(type(of: self).nowPlayingItemChanged(notification:)), name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange, object: player)
        // 通知の有効化
        player.beginGeneratingPlaybackNotifications()
        
        artistLabel.text = ""
        artistLabel.font = UIFont.systemFont(ofSize: 15.0)
        artistLabel.textAlignment = .center
        artistLabel.textColor = .white
        artistLabel.labelSpacing = 50;
        artistLabel.pauseInterval = 3;
        artistLabel.scrollSpeed = 50.0;
        artistLabel.fadeLength = 20.0;
        view.addSubview(artistLabel)
        
        albumLabel.text = ""
        albumLabel.font = UIFont.systemFont(ofSize: 15.0)
        albumLabel.textAlignment = .center
        albumLabel.textColor = .white
        albumLabel.labelSpacing = 50;
        albumLabel.pauseInterval = 3;
        albumLabel.scrollSpeed = 50.0;
        albumLabel.fadeLength = 20.0;
        view.addSubview(albumLabel)
        
        songLabel.text = ""
        songLabel.font = UIFont.boldSystemFont(ofSize: 30.0)
        songLabel.textAlignment = .center
        songLabel.textColor = .white
        songLabel.labelSpacing = 50;
        songLabel.pauseInterval = 3;
        songLabel.scrollSpeed = 50.0;
        songLabel.fadeLength = 20.0;
        view.addSubview(songLabel)
        
        imageView.image = UIImage(named: "noselect.jpg")
        view.addSubview(imageView)
        
        //再生・一時停止ボタン配置
        view.addSubview(playpause)
        
        let b = UIButton(frame: CGRect(x: w - 60, y: 30, width: 50, height: 50))
        b.layer.cornerRadius = 25.0
        b.setTitleColor(UIColor.white, for: UIControlState())
        b.backgroundColor = UIColor.rgb(r: 255, g: 15, b:115, alpha: 1.0)
        b.setTitle("+", for: UIControlState())
        b.titleLabel?.font = UIFont.systemFont(ofSize: 28.0)
        b.contentHorizontalAlignment = .center
        b.addTarget(self, action: #selector(selecter(_:)), for: .touchUpInside)
        view.addSubview(b)
        
        
        let playStatus = player.playbackState
        if playStatus == .stopped {
            playpause.setImage(UIImage(named :"play.png"), for: UIControlState())
            playpause.addTarget(self, action: #selector(play(_:)), for: .touchUpInside)
        }
        else if playStatus == .paused {
            playpause.setImage(UIImage(named :"play.png"), for: UIControlState())
            playpause.addTarget(self, action: #selector(play(_:)), for: .touchUpInside)
        }
        
        let stop = UIButton(frame: CGRect(x: (w - 60) / 4 * 3, y: 580, width: 60, height: 60))
        stop.setImage(UIImage(named :"stop.png"), for: UIControlState())
        stop.addTarget(self, action: #selector(Stop(_:)), for: .touchUpInside)
        view.addSubview(stop)
        
        
        reb.setImage(UIImage(named: "repeat.png"), for: UIControlState())
        reb.addTarget(self, action: #selector(ChangeRepeat(_:)), for: .touchUpInside)
        view.addSubview(reb)
        
        
        
        //現在再生中の音楽を表示
        let playing = player.nowPlayingItem
        player.skipToBeginning()
        
        // 選択した曲から最初の曲の情報を表示
        if let mediaItem = playing {
            let playstatus = player.playbackState
            if playstatus == .stopped {
                updateSongInformationUI(mediaItem : mediaItem)
                player.stop()
                print("stop")
            } else if playstatus == .paused {
                updateSongInformationUI(mediaItem : mediaItem)
                player.stop()
                print("pause")
            } else {
                updateSongInformationUI(mediaItem : mediaItem)
                player.stop()
                print("playing")
            }
        } else {
            //曲が選択されていない場合、全楽曲セットする
            let query = MPMediaQuery.songs()
            player.setQueue(with: query)
            player.play()
        }
        
        
        
        
    }
    
    
    
    @objc func selecter(_ :UIButton){
        // MPMediaPickerControllerのインスタンスを作成
        let picker = MPMediaPickerController()
        // ピッカーのデリゲートを設定
        picker.delegate = self
        // 複数選択にする。（falseにすると、単数選択になる）
        picker.allowsPickingMultipleItems = false
        // ピッカーを表示する
        present(picker, animated: true, completion: nil)
    }
    
    //再生
    @objc func play(_ :UIButton){
        player.play()
        playpause.setImage(UIImage(named :"pause.png"), for: UIControlState())
        playpause.addTarget(self, action: #selector(Pause(_:)), for: .touchUpInside)
    }
    
    //一時停止
    @objc func Pause(_ :UIButton){
        player.pause()
        playpause.setImage(UIImage(named :"play.png"), for: UIControlState())
        playpause.addTarget(self, action: #selector(play(_:)), for: .touchUpInside)
    }
    
    //停止
    @objc func Stop(_ :UIButton){
        player.stop()
        player.skipToBeginning()
        playpause.setImage(UIImage(named :"play.png"), for: UIControlState())
        playpause.addTarget(self, action: #selector(play(_:)), for: .touchUpInside)
    }
    
    //リピート有効
    @objc func ChangeRepeat(_ : UIButton){
        player.repeatMode = .one
        reb.setImage(UIImage(named: "deleterepeat.png"), for: UIControlState())
        reb.addTarget(self, action: #selector(ChangeRepeatStop(_:)), for: .touchUpInside)

    }
    
    //リピート無効
    @objc func ChangeRepeatStop(_ : UIButton){
        player.repeatMode = .none
        reb.setImage(UIImage(named: "repeat.png"), for: UIControlState())
        reb.addTarget(self, action: #selector(ChangeRepeat(_:)), for: .touchUpInside)
    }
    
    
    // メディアアイテムピッカーでアイテムを選択完了したときに呼び出される
    func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection)  {
        
        // プレイヤーを止める
        player.stop()
        
        // 選択した曲情報がmediaItemCollectionに入っているので、これをplayerにセット。
        player.setQueue(with: mediaItemCollection)
        
        
        // 選択した曲から最初の曲の情報を表示
        if let mediaItem = mediaItemCollection.items.first {
            updateSongInformationUI(mediaItem : mediaItem)
            
        }
        
        
        // ピッカーを閉じる
        dismiss(animated: true, completion: nil)
        
    }
    
    
    // 選択がキャンセルされた場合に呼ばれる
    func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
        // ピッカーを閉じる
        dismiss(animated: true, completion: nil)
    }
    
    // 曲情報を表示する
    func updateSongInformationUI(mediaItem: MPMediaItem) {
        
        // 曲情報表示
        artistLabel.text = mediaItem.artist ?? "不明なアーティスト"
        artistLabel.labelSpacing = 50;
        artistLabel.pauseInterval = 3;
        artistLabel.scrollSpeed = 50.0;
        artistLabel.fadeLength = 20.0;
        view.addSubview(artistLabel)
        
        albumLabel.text = mediaItem.albumTitle ?? "不明なアルバム"
        albumLabel.labelSpacing = 50;
        albumLabel.pauseInterval = 3;
        albumLabel.scrollSpeed = 50.0;
        albumLabel.fadeLength = 20.0;
        view.addSubview(albumLabel)
        
        songLabel.text = mediaItem.title ?? "不明な曲"
        songLabel.labelSpacing = 50;
        songLabel.pauseInterval = 3;
        songLabel.scrollSpeed = 50.0;
        songLabel.fadeLength = 20.0;
        view.addSubview(songLabel)
        
        // アートワーク表示
        if let artwork = mediaItem.artwork {
            let image = artwork.image(at: imageView.bounds.size)
            imageView.image = image
        } else {
            // アートワークがないとき
            imageView.image = nil
            imageView.backgroundColor = .gray
        }
        
        player.play()
    }
    
    
    /// 再生中の曲が変更になったときに呼ばれる
    @objc func nowPlayingItemChanged(notification: NSNotification) {
        
        if let mediaItem = player.nowPlayingItem {
            updateSongInformationUI(mediaItem: mediaItem)
            
            playpause.setImage(UIImage(named :"pause.png"), for: UIControlState())
            playpause.addTarget(self, action: #selector(Pause(_:)), for: .touchUpInside)
        }
        
    }
    
    
    //再生中の曲の状態が変わったときに呼ばれる
    @objc func b(notification: NSNotification) {
        let playStatus = player.playbackState
        if playStatus == .stopped {
            playpause.setImage(UIImage(named :"play.png"), for: UIControlState())
            playpause.addTarget(self, action: #selector(play(_:)), for: .touchUpInside)
        }
        
        if playStatus == .paused {
            playpause.setImage(UIImage(named :"play.png"), for: UIControlState())
            playpause.addTarget(self, action: #selector(play(_:)), for: .touchUpInside)
        }
        
        if playStatus == .playing {
            playpause.setImage(UIImage(named :"pause.png"), for: UIControlState())
            playpause.addTarget(self, action: #selector(Pause(_:)), for: .touchUpInside)
        }
        
    }
    
    
    
    deinit {
        // 再生中アイテム変更に対する監視をはずす
        let notificationCenter = NotificationCenter.default
        notificationCenter.removeObserver(self, name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange, object: player)
        notificationCenter.removeObserver(self, name: NSNotification.Name.MPMusicPlayerControllerPlaybackStateDidChange, object: player)
        // ミュージックプレーヤー通知の無効化
        player.endGeneratingPlaybackNotifications()
    }



}

//rgbaカラーコード
extension UIColor {
    class func rgb(r: Int, g: Int, b: Int, alpha: CGFloat) -> UIColor{
        return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: alpha)
    }
}
```
# **完成**
<img width="375" alt="fullsizeoutput_6ce.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/154610/5224b3d7-aa97-15d9-a5c1-69ea0b837b97.jpeg">

<img width="375" alt="fullsizeoutput_6cc.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/154610/9feba620-d79f-e050-3eeb-d4451387e07f.jpeg">

# **感想**
結構楽しかったです。
ただ、オートスクロールが稀な確率で動かないのは気になりますね。
現在、調査中です。。

**AVAudioPlayer**でやる方法もあるので、それを使って、もう少し改造したいですね。