foresthillのブログ

ホームページ・CMS制作からアプリ開発、ゲーム製作となんでもござれ。

【iPhoneアプリ】【Swift】SolPlayer開発記(5)再生機能を作り込む(シークバーと連動させる)

すべての音源をソルフェジオに変える音楽プレイヤー、
「SolPlayer」の開発記、第5弾。(ここまでコピペ)

前回の記事では、「iPhone内にあるファイルを読み込む方法」を解説しました。

foresthill.hatenablog.com


今回は、「再生機能を作り込み、シークバーと連動させる」方法を紹介します。

実装方法

思いのほか、この機能も苦労しました。

redmineの記録を観ると、予定3時間に対し、実際にかかった時間は14時間。
再生機能の実装も含めての時間ですが、「予定よりかなりかかったなぁ」という印象汗

まぁこれを読んでくれているあなたは30ふん以内でry


シークバーと連動させる

さて、それでは実装方法を紹介します。

例のごとく他力本願です。

asmz.hatenablog.jp

※一応、さくっとURLを紹介していますが、
このページにたどり着くまで結構時間かかってます汗

上記ページすべてをコピペ参考にできますが、
特に「任意の再生位置からの音声再生(シーク機能)」の情報が重要です。

一応、コードを提示。

画面側(ViewController)

毎秒ごとの処理
    //シークバー
    @IBOutlet weak var timeSlider: UISlider!
    //現在時刻
    @IBOutlet weak var nowTimeLabel: UILabel!
    //残り時刻
    @IBOutlet weak var endTimeLabel: UILabel!

    //タイマー
    var timer: NSTimer!

(中略)

    /** 
     初期処理
     */
    override func viewDidLoad() {

       //タイマーをセット
       timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ViewController.didEverySecondPassed), userInfo: nil, repeats: true)

    }

(中略)

    /**
     毎秒ごとに行われる処理(timerで管理)
     */
    func didEverySecondPassed(){
        
        let current = solPlayer.currentPlayTime()
        
        //現在時刻と残り時刻を更新
        nowTimeLabel.text = formatTimeString(current)
        endTimeLabel.text = "-" + formatTimeString(Float(solPlayer.duration) - current)
        
        //スライダー値を更新
        timeSlider.value = current
    }


(中略)


    /**
     再生時間のスライダーが変更された時(Action→ValueChanged)
     - parameter sender: UISlider
     */
    @IBAction func timeSliderAction(sender: UISlider) {
        solPlayer.timeShift(timeSlider.value)
    }

処理(ロジック)側(独自クラス)

/** 
 SolffegioPlayer本体(音源再生を管理する)
 */
class SolPlayer {

    //プレイヤー
    var audioPlayerNode: AVAudioPlayerNode! = AVAudioPlayerNode()
    
    //音源ファイル
    var audioFile: AVAudioFile!

    //サンプルレート
    var sampleRate: Double!

    //時間をずらした時の辻褄あわせ
    var offset = 0.0

(中略)

    /**
     現在の再生時刻を返すメソッド
    */
    func currentPlayTime() -> Float {
                   
            //サンプルレートが0の時は再生時間を取得しない(というかできない)
            if(sampleRate == 0){
                return 0
            }

            //便宜上分かりやすく書いてみる
            let nodeTime = audioPlayerNode.lastRenderTime
            let playerTime = audioPlayerNode.playerTimeForNodeTime(nodeTime!)
            let nowPlayTime = (Double(playerTime!.sampleTime) / sampleRate)
            
            return (Float)(currentTime + offset)
                    
    }

    /**
     シークバーを動かした時の処理
     */
    func timeShift(current: Float){
        
        //退避
        offset = Double(current)                        
        
        //シーク位置(AVAudioFramePosition)取得
        let restartPosition = AVAudioFramePosition(Float(sampleRate) * current)
        
        //残り時間取得(sec)
        let remainSeconds = Float(self.duration) - current
        
        //残りフレーム数(AVAudioFrameCount)取得
        let remainFrames = AVAudioFrameCount(Float(sampleRate) * remainSeconds)
        
        audioPlayerNode.stop()
        
        if remainFrames > 100 {
            //指定の位置から再生するようスケジューリング
            audioPlayerNode.scheduleSegment(audioFile, startingFrame: restartPosition, frameCount: remainFrames, atTime: nil, completionHandler: nil)
        }
        
        audioPlayerNode.play()
                
    }


とりあえず、こんな感じです。
今回のテーマに関係ない個所は端折ってますので、
このまま書いても動かない可能性大です汗

なんとなく、書き方の雰囲気だけでも伝われば幸いです。
(全ソースコードは、今後GitHubで公開しようと思っています)

なお処理(ロジック)に関しては、便宜上わけてはいますが
好みによってはすべてViewControllerに記載しても構いません。

後で見返して、説明不足な点については今後追記したいと思います。

以上です。



次回は、「画面が表示されていない(または画面ロック)時にも、音楽を再生する方法」を紹介します。(要はバックグラウンド再生ですね)
お楽しみに。