Xcode
iOS
Swift

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

目次

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

はじめに

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

↓Swiftの書式が古いので、注意。
[iOS][Swift]ミュージックライブラリの音楽の再生、情報の表示(MPMusicPlayerController使用)

公式リファレンスも載せておく。
MPMusicPlayerController

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

開発環境

  • Xcode9.3
  • Swift4.1

リピートモードの有効化

var player: MPMusicPlayerController!

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

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

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

再生中の曲の状態を取得

let playStatus = player.playbackState

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

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

バックグラウンド再生に対応する

Capabilities>Background Modeで、Audio,AirPlay,and Picture in Pictureにチェックを入れる。
僕の環境の場合、これで動いた。

スクリーンショット 2018-05-07 19.32.22.png

UILabelをオートスクロール(動作不安定)

参考資料:【Swift4.0】テキストのオートスクロールを実装するライブラリ【iOS】

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

pod `AutoScrollLabel`

各種設定はこんな感じ。

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

しかし、実際に導入したものの、稀にスクロールしない時がある
原因はわからず。。。

フルコード

フルコードを載せておく。
再生ボタンなどは、一から画像を作成して、UIButton.setImageでセットしている。

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)
    }
}

完成

fullsizeoutput_6ce.jpeg

fullsizeoutput_6cc.jpeg

感想

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

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