Edited at

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

More than 1 year has passed since last update.


目次


  • はじめに

  • 開発環境

  • リピートモードの有効化

  • 再生中の曲の状態を取得

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

  • 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でやる方法もあるので、それを使って、もう少し改造したいですね。