[iOS]動画を再生する(AVPlayerLayer使用)

(Xcode 11, Swift 5.1, iOS 13での実装)

iOSでの、動画を再生する機能を、前回はAVPlayerViewControllerを用いて作成しました。今回は、AVPlayerLayerを使用します。

AVPlayer – AVFoundation | Apple Developer Documentation

AVPlayerクラスを用いることで、映像やオーディオの再生ができます。
AVPlayerには画面に表示する機能がないので、前回はその部分をAVPLayerViewControllerがになう例を記しました。
今回は、AVPlayerLayerを使う例を記しました。

AVPlayerLayer – AVFoundation | Apple Developer Documentation

AVPlayerLayerはplayerプロパティを持ち、そこにAVPlayerを渡すことで映像を表示できます。
実際に使う際には、このAVPlayerLayerのドキュメントに記されているプレイヤー用のUIViewサブクラスを用いる方法が扱いやすいです。

class PlayerView: UIView {
    var player: AVPlayer? {
        get {
            return playerLayer.player
        }
        set {
            playerLayer.player = newValue
        }
    }
    
    var playerLayer: AVPlayerLayer {
        return layer as! AVPlayerLayer
    }
    
    // Override UIView property
    override static var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
}

Appleのドキュメントに乗っているコード例です。layerClassをオーバーライドしてこのUIViewがもつlayerはAVPlayerLayerクラスを返すようにしています。プロパティとしてplayerがあり、そこにAVPlayerをセットします。

この記事では、画面表示にこのPlayerViewを用いて、プロジェクトのリソースに加えた動画ファイルを再生する方法を記します。

動画を再生するサンプル

プロジェクトに追加してある動画ファイルを再生・一時停止するサンプルを作成しました。バックグラウンドへ移行してもオーディオの再生・一時停止ができます。バックグラウンド移行時に動画再生中であった場合、一時停止するものを作成しますが、一時停止せずにオーディオを再生し続ける方法についても記しました。全体のソースコードは記事の最後にまとめてあります。
ポイントになる箇所について、説明をおこないました。
(今回の記事では再生・一時停止のみですが、その後、早戻し・早送り・シークを含むサンプルを作成する記事を書きました。[iOS]動画を再生する(早戻り、早送り、シーク)

動画再生機能作成については、Appleのサンプルが参考になります。
Creating a Movie Player App with Basic Playback Controls | Apple Developer Documentation

PlayerViewクラスを作成

今回は、ViewController.swiftのほかに、PlayerView.swiftを作成しました。
File > New > File > Cocoa Touch Classから、UIViewをスーパークラスとするPlayerViewクラスを作成します。
内容は、前段に記したAppleのドキュメントに載っているPlayerViewコード例の通りです。記事の最後にも載せています。

動画ファイルをプロジェクトに加える

File > Add Files to <プロジェクト名>。
動画ファイルを選び、プロジェクトに加えます。
今回は、clip.mp4という名前の動画ファイルが加えられたとして話を進めました。

UI作成

Main.storyboard > View Controller。
UIViewとボタン二つを貼り付けます。
ボタンのテキストを「Play」「Pause」とします。
(画像ではUIViewのBackgroundはオレンジ色にしてありますが、これはどこに貼り付けたかを見やすくするためで、実際にはSystem Background Colorのままで構いません)

貼り付けたUIViewを選択し、Identity inspector > Custom Class > Classで、PlayerViewとします。

Player ViewとViewController.swiftをOutletで結び、名前をplayerViewとします。

一つのボタンのテキストを「Play」、もう一つのボタンのテキストを「Pause」とします。
PlayボタンとViewController.swiftをActionで結び、名前をplayBtnTappedとします。@IBAction func playBtnTapped(_ sender: Any) {}が生成されます。
PauseボタンとViewController.swiftをActionで結び、名前をpauseBtnTappedとします。@IBAction func pauseBtnTapped(_ sender: Any) {}が生成されます。

Audio Sessionの準備

AVAudioSession – AVFoundation | Apple Developer Documentation

Audio Sessionとはアプリがどのようにオーディオを扱うかを示すオブジェクトです。再生のみなのか、録音再生を行うのか、バックグラウンド再生ありにするのかなどを設定します。用意されたカテゴリ・モード・オプションから適切なものを選んで設定する形式になっています。
今回は、カテゴリをAVAudioSessionCategoryPlayback(オーディオ機能をメインとしたアプリ用カテゴリ。バックグラウンド再生可能)、モードをAVAudioSessionModeMoviePlayback(ムービー再生用)に設定します。

/// Audio sessionを動画再生向けのものに設定し、activeにします
let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setCategory(.playback, mode: .moviePlayback)
        
} catch {
    print("Setting category to AVAudioSessionCategoryPlayback failed.")
}

do {
    try audioSession.setActive(true)
    print("Audio session set active !!")
} catch {
    
}

バックグラウンド再生の準備

今回のサンプルでは、バックグラウンドでのオーディオ再生を行うので、その設定を行います。
TARGET > Signing & Capabilitiesを選択。
「+ Capability」ボタンから-> Background Modes

Audio, AirPlay and Picture in Pictureをチェック。

これにより、info.plistにRequired background modesが加わり、そのitemがApp plays audio or streams audio/video using AirPlayとなります。バックグラウンド再生が可能になります。

動画プレイヤー

urlからAVPlayerItemを作成します。
AVPlayerに作成したAVPlayerItemをセットします。
AVPlayerを動画表示を担当するAVPlayerLayerと結びつけます。
PlayerViewのプロパティであるplayerにAVPlayerをセットします。これによって、PlayerViewの動画表示をになうlayerと結びつけることができます。
今回は、ViewControllerクラスに追加したitemURLプロパティに動画のurlも渡しています。これは、あとで、NowPlayingInfo(現在再生中のアプリの状況)を表示するさいに使用します。

let fileName = "clip"
let fileExtension = "mp4"
guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    print("Url is nil")
    return
}

itemURL = url
let item = AVPlayerItem(url: url)
player = AVPlayer(playerItem: item)

playerView.player = player

リモートコントロール対応

イヤホンからの操作や、バックグラウンド移行後のコントロールセンターやロックスクリーンの再生コントロールからの操作に対応します。
MPRemoteCommandCenterクラスを用います。
また、コントロールセンターやロックスクリーンの再生コントロールに再生中アプリの現在の状況を表示する必要があります。それには、MPNowPlayingInfoCenterクラスを用います。
これらの実装については、Controlling Background Audio | Apple Developer Documentationが参考になります。

リモートコントロール対応部分

// MARK: Remote Command Event
func addRemoteCommandEvent() {
    
    let commandCenter = MPRemoteCommandCenter.shared()
    commandCenter.togglePlayPauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
        self.remoteTogglePlayPause(commandEvent)
        return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.playCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
        self.remotePlay(commandEvent)
        return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.pauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
        self.remotePause(commandEvent)
        return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.nextTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
        self.remoteNextTrack(commandEvent)
        return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.previousTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
        self.remotePrevTrack(commandEvent)
        return MPRemoteCommandHandlerStatus.success
    }
}

func remoteTogglePlayPause(_ event: MPRemoteCommandEvent) {
    // イヤホンのセンターボタンを押した時の処理
    // (略)
}

func remotePlay(_ event: MPRemoteCommandEvent) {
    // 再生ボタンが押された時の処理
    // (略)
}

func remotePause(_ event: MPRemoteCommandEvent) {
    // ポーズボタンが押された時の処理
    // (略)
}

func remoteNextTrack(_ event: MPRemoteCommandEvent) {
    // 「次へ」ボタンが押された時の処理
    // (略)
}

func remotePrevTrack(_ event: MPRemoteCommandEvent) {
    // 「前へ」ボタンが押された時の処理
    // (略)
}

NowPlayingInfo部分

この例では、動画ファイルのurlの末尾部分をタイトル表示に使用しています。
MPNowPlayingInfoCenterに示すことができるメタデータはほかにも様々なものがあります。
Now Playing Metadata Properties | MPNowPlayingInfoCenter – Media Player | Apple Developer Documentation

func setupNowPlaying(url: URL) {
    // Define Now Playing Info
    var nowPlayingInfo = [String : Any]()
    
    // ここでは、urlのlastPathComponentをtitleとして表示しています。
    nowPlayingInfo[MPNowPlayingInfoPropertyAssetURL] = url
    nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.video.rawValue
    nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = false
    nowPlayingInfo[MPMediaItemPropertyTitle] = url.lastPathComponent
    nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = ""
    
    // Set the metadata
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}

バックグラウンド移行時の動作について

AVPlayerでは、動画再生中にバックグラウンドへ移行すると、一時停止するのがデフォルトの動作です。
もし、バックグラウンド移行時に再生しているオーディオを鳴らし続けたい場合は、追加の処理が必要になります。
これについては、Playing Audio from a Video Asset in the Background | Apple Developer Documentationのドキュメントに記されています。
バックグラウンド移行時にAVPlayerのインスタンスをplayerLayerから外す必要があります。

// フォアグラウンド移行時に呼び出されます
@objc func willEnterForeground(_ notification: Notification) {
    playerView.player = player
}
     
// バックグラウンド移行時に呼び出されます
@objc func didEnterBackground(_ notification: Notification) {
    playerView.player = nil
}

Playボタンを押すと動画が再生され、Pauseボタンを押すと動画が一時停止するサンプルを作成しました。イヤホン、コントロールセンターなどからの再生・一時停止もできます。

使用したソースコード

PlayerView.swift

import UIKit
import AVFoundation

class PlayerView: UIView {
    
    // The player assigned to this view, if any.
    
    var player: AVPlayer? {
        get { return playerLayer.player }
        set { playerLayer.player = newValue }
    }
    
    // The layer used by the player.
    
    var playerLayer: AVPlayerLayer {
        return layer as! AVPlayerLayer
    }
    
    // Set the class of the layer for this view.
    override static var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
}

ViewController.swift

以下のソースコードでは、バックグラウンド移行時に一時停止する動作になっています。動画再生中のバックグラウンド移行のさいに、続けてオーディオ再生を行う場合は、willEnterForeground(_;)、didEnterBackground(_:)でのコメントアウトを外してください。

import UIKit
import AVFoundation
import MediaPlayer
import AVKit

class ViewController: UIViewController {

    @IBOutlet weak var playerView: PlayerView!
    
    var player = AVPlayer()
    var itemURL: URL?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(.playback, mode: .moviePlayback)
        } catch {
            print("Setting category to AVAudioSessionCategoryPlayback failed.")
        }
        do {
            try audioSession.setActive(true)
            print("audio session set active !!")
        } catch {
            
        }
        
        setupPlayer()
        
        addLifeCycleObserver()
        
        addRemoteCommandEvent()
    }
    
    private func setupPlayer() {
        let fileName = "clip"
        let fileExtension = "mp4"
        guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
            print("Url is nil")
            return
        }
        
        itemURL = url
        let item = AVPlayerItem(url: url)
        player = AVPlayer(playerItem: item)
        
        playerView.player = player
        
    }

    /// Playボタンが押された
    @IBAction func playBtnTapped(_ sender: Any) {
        player.play()
        if let url = itemURL {
            setupNowPlaying(url: url)
        }
    }
        
    @IBAction func pauseBtnTapped(_ sender: Any) {
        player.pause()
    }
    
    // MARK: Life Cycle
    func addLifeCycleObserver() {
        let center = NotificationCenter.default
        center.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
        center.addObserver(self, selector: #selector(didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
    }
    
    // フォアグラウンド移行時に呼び出されます
    @objc func willEnterForeground(_ notification: Notification) {
        /// (動画再生中であったときにそのままオーディオ再生を続ける場合は、このあとのコメントアウトをとりのぞいてください)
        /// (バックグラウンド移行時に、playerLayerからplayerがはずされているので、再度つなげます。)
        //playerView.player = player
    }
         
    // バックグラウンド移行時に呼び出されます
    @objc func didEnterBackground(_ notification: Notification) {
        /// (動画再生中であったときにそのままオーディオ再生を続ける場合は、このあとのコメントアウトをとりのぞいてください)
        //playerView.player = nil
    }
    
    // MARK: Now Playing Info
    func setupNowPlaying(url: URL) {
        // Define Now Playing Info
        var nowPlayingInfo = [String : Any]()
        
        // ここでは、urlのlastPathComponentを表示しています。
        nowPlayingInfo[MPNowPlayingInfoPropertyAssetURL] = url
        nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.video.rawValue
        nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = false
        nowPlayingInfo[MPMediaItemPropertyTitle] = url.lastPathComponent
        nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = ""
        
        // Set the metadata
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }
    
    // MARK: Remote Command Event
    func addRemoteCommandEvent() {
        
        let commandCenter = MPRemoteCommandCenter.shared()
        commandCenter.togglePlayPauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
            self.remoteTogglePlayPause(commandEvent)
            return MPRemoteCommandHandlerStatus.success
        }
        commandCenter.playCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
            self.remotePlay(commandEvent)
            return MPRemoteCommandHandlerStatus.success
        }
        commandCenter.pauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
            self.remotePause(commandEvent)
            return MPRemoteCommandHandlerStatus.success
        }
        commandCenter.nextTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
            self.remoteNextTrack(commandEvent)
            return MPRemoteCommandHandlerStatus.success
        }
        commandCenter.previousTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
            self.remotePrevTrack(commandEvent)
            return MPRemoteCommandHandlerStatus.success
        }
    }
    
    func remoteTogglePlayPause(_ event: MPRemoteCommandEvent) {
        // イヤホンのセンターボタンを押した時の処理
        // (略)
    }
    
    func remotePlay(_ event: MPRemoteCommandEvent) {
        player.play()
    }
    
    func remotePause(_ event: MPRemoteCommandEvent) {
        player.pause()
    }

    func remoteNextTrack(_ event: MPRemoteCommandEvent) {
        // 「次へ」ボタンが押された時の処理
        // (略)
    }
    
    func remotePrevTrack(_ event: MPRemoteCommandEvent) {
        // 「前へ」ボタンが押された時の処理
        // (略)
        
    }

}

「[iOS]動画を再生する(AVPlayerLayer使用)」への14件のフィードバック

  1. nackpan様

    kkです。

    動画のバックグラウンド再生に関する貴重な記事、大変感謝しております。
    ありがとうございます。
    また、お世話になりたい事象があり、コメントさせて頂いております。

    投稿頂いた記事の内容を元に、アプリ開発をしている中で、
    AVPlayerViewControllerとAVPlayerLayerを、一つのプロジェクト内で
    使い分けるアプリを検討しております。

    仕様は、「Play 02]ボタンを追加し、
    ・「Play01」ボタン(元の「Play」ボタン)を押せば、AVPlayerLayerで「sample01.mp4」再生
    ・「Play02」ボタンを押せば、AVPlayerViewControllerで「sample02.mp4」再生というものです。

    問題は、下記の④です。
    ①「Play01」押しで「sample01」再生=> バックグラウンドへ移行しAVPlayerLayerのリモートコマンド操作で「sample01」を操作:OK
    ②「Play02」押しで「sample02」再生=> バックグラウンドへ移行しAVPlayerViewControllerのリモートコマンド操作で「sample02」を操作:OK
    ③「Play01」押しで「sample01」再生後、「Play02」押しで「sample02」再生=> バックグラウンドへ移行しAVPlayerViewControllerのリモートコマンド操作で「sample02」を操作:OK
    ④「Play01」押しで「sample01」再生後、「Play02」 押しで「sample02」再生、その後「Play01」押しで「sample01」を再生 => バックグラウンドへ移行しAVPlayerLayerのリモートコマンド操作での「sample01」の操作ができず、AVPlayerViewControllerの、「sample02」のリモートコマンド操作になってしまう。

    AVPlayerViewControllerを一度使うと、その後、AVPlayerLayerで再生後、バックグラウンド再生へ移行しても、リモートコマンド画面は、AVPlayerViewControllerのままとなってしまい、「sample01」と「sample02」の両方の音声が、再生されてしまいます。

    下記を検討しましたが、効果はありませんでした。
    (1)AVPlayerViewControllerを、閉じた(左上の×ボタン押し)後、「Play01」ボタン押し時に
     ・deleteRemoteCommandEvent()
     を実行後、再度、addRemoteCommandEvent()を実行
    (2)AVplayerViewController終了(左上の×ボタン押し)時に、リモートイベント解除(コード③に記載)
     ・NotificationCenterで、override func viewDidAppearにて、AVPlayerItemDidPlayToEndTime時に、
      deleteRemoteCommandEvent()と、self.dismiss(animated: true)を実施
      (参考:https://ameblo.jp/zexpertz/entry-12528476400.html)

    AVPlayerViewControllerで動画を、再生した後に、AVPlayerViewControllerのリモートイベントを解除(?という表現が正しいかわかっておりませんが)し、次回にAVPlayerLayerでの、リモートイベントを実行できる方法があれば、ご教示頂きたく、お時間のあるときに、どうか、よろしくお願い致します。

    検討したコードは、nackpan様が投稿しておられる、[iOS]動画を再生する(AVPlayerLayer使用)のコードに、
    //■以下、追加コード
    を、追加したものです。追加コードは、
    ご参考にして頂ければ幸いです。

    import UIKit
    import AVFoundation
    import MediaPlayer
    import AVKit

    class ViewController: UIViewController {

    @IBOutlet weak var playerView: PlayerView!

    var player = AVPlayer()
    var itemURL: URL?

    override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    playerView.player = nil

    let audioSession = AVAudioSession.sharedInstance()
    do {
    try audioSession.setCategory(.playback, mode: .moviePlayback)
    } catch {
    print(“Setting category to AVAudioSessionCategoryPlayback failed.”)
    }
    do {
    try audioSession.setActive(true)
    print(“audio session set active !!”)
    } catch {

    }

    setupPlayer()

    addLifeCycleObserver()

    addRemoteCommandEvent()
    }

    private func setupPlayer() {
    let fileName = “sample01”
    let fileExtension = “mp4”
    guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    print(“Url is nil”)
    return
    }

    itemURL = url
    let item = AVPlayerItem(url: url)
    player = AVPlayer(playerItem: item)

    playerView.player = player

    }

    /// Playボタンが押された
    @IBAction func playBtnTapped(_ sender: Any) {

    //test
    deleteRemoteCommandEvent()
    addRemoteCommandEvent()
    //
    player.play()
    if let url = itemURL {
    setupNowPlaying(url: url)
    }
    }

    @IBAction func pauseBtnTapped(_ sender: Any) {
    player.pause()
    }

    // MARK: Life Cycle
    func addLifeCycleObserver() {
    let center = NotificationCenter.default
    center.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
    center.addObserver(self, selector: #selector(didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
    }

    // フォアグラウンド移行時に呼び出されます
    @objc func willEnterForeground(_ notification: Notification) {
    /// (動画再生中であったときにそのままオーディオ再生を続ける場合は、このあとのコメントアウトをとりのぞいてください)
    /// (バックグラウンド移行時に、playerLayerからplayerがはずされているので、再度つなげます。)
    //playerView.player = player
    }

    // バックグラウンド移行時に呼び出されます
    @objc func didEnterBackground(_ notification: Notification) {
    /// (動画再生中であったときにそのままオーディオ再生を続ける場合は、このあとのコメントアウトをとりのぞいてください)
    //playerView.player = nil
    }

    // MARK: Now Playing Info
    func setupNowPlaying(url: URL) {
    // Define Now Playing Info
    var nowPlayingInfo = [String : Any]()

    // ここでは、urlのlastPathComponentを表示しています。
    nowPlayingInfo[MPNowPlayingInfoPropertyAssetURL] = url
    nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.video.rawValue
    nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = false
    nowPlayingInfo[MPMediaItemPropertyTitle] = url.lastPathComponent
    nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = “”

    // Set the metadata
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }

    // MARK: Remote Command Event
    func addRemoteCommandEvent() {

    let commandCenter = MPRemoteCommandCenter.shared()
    commandCenter.togglePlayPauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remoteTogglePlayPause(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.playCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePlay(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.pauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePause(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.nextTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remoteNextTrack(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.previousTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePrevTrack(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    }

    @objc func remoteTogglePlayPause(_ event: MPRemoteCommandEvent) {
    // イヤホンのセンターボタンを押した時の処理
    // (略)
    }

    func remotePlay(_ event: MPRemoteCommandEvent) {
    player.play()
    }

    func remotePause(_ event: MPRemoteCommandEvent) {
    player.pause()
    }

    func remoteNextTrack(_ event: MPRemoteCommandEvent) {
    // 「次へ」ボタンが押された時の処理
    // (略)
    }

    func remotePrevTrack(_ event: MPRemoteCommandEvent) {
    // 「前へ」ボタンが押された時の処理
    // (略)

    }

    //■以下、追加コード
    //①リモコン対応解放の関数を定義:removeTarget
    func deleteRemoteCommandEvent() {//deleRemo
    let commandCenter = MPRemoteCommandCenter.shared()
    commandCenter.togglePlayPauseCommand.removeTarget(self, action: #selector(type(of: self).remoteTogglePlayPause(_:)))
    }//deleRemo

    //========ここからは、nackpan様の記事(:AVPlayerLayer使用)より転記=============
    //②AVPlayerViewControllerで再生
    var playerController = AVPlayerViewController()
    var player02 = AVPlayer()

    /// 動画プレイヤーにアイテムをセットして更新
    private func playMovie(fileName: String, fileExtension: String) {
    guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    print(“Url is nil”)
    return
    }
    // AVPlayerにアイテムをセット
    let item = AVPlayerItem(url: url)
    player02.replaceCurrentItem(with: item)

    // 動画プレイヤーにplayerをセット
    playerController.player = player02

    // 動画プレイヤーを表示して再生
    self.present(playerController, animated: true) {
    self.player02.play()
    }
    }

    /// Play Videoボタンが押されました
    @IBAction func Play02(_ sender: Any) {
    let fileName = “sample02”
    let fileExtension = “mp4”
    playMovie(fileName: fileName, fileExtension: fileExtension)
    }
    //========記事の転記は、ここまで=============

    //③AVplayerViewController終了時、リモートイベント解除
    override func viewDidAppear(_ animated: Bool) {//func vDA
    super.viewDidAppear(animated)
    NotificationCenter.default.addObserver(self,selector: #selector(self.dismissFromAVplayerVC ),name:NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: nil)
    }//vDA
    @objc func dismissFromAVplayerVC(note:NSNotification) {
    deleteRemoteCommandEvent()
    self.dismiss(animated: true)
    }

    }

    以上です。
    失礼致します。

  2. kkさん、こんにちは。

    コメントの事例で、バックグラウンドで音楽が重なって聞こえる状況を確認しました。
    いくつか修正方法を実験してみました。

    コメントのプログラミングリストでは、 AVPlayerViewController用のプレイヤーとしてplayer02を用意されていますが、それをとりやめて、すでに使っているplayerをAVPlayerViewController用のプレイヤーとすると、音楽が二重になるのを避けることができました。
    いちどきに2つの動画を再生するのでないのなら、これが良いのではないかと思います。
    playMovie(fileName:, fileExtension:)を修正

    
    private func playMovie(fileName: String, fileExtension: String) {
    	guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    		print(“Url is nil”)
    		return
    	}
    	// AVPlayerにアイテムをセット
    	let item = AVPlayerItem(url: url)
    	player.replaceCurrentItem(with: item)
    	
    	// 動画プレイヤーにplayerをセット
    	playerController.player = player
    	
    	// 動画プレイヤーを表示して再生
    	self.present(playerController, animated: true) {
    		self.player.play()
    	}
    }
    
    

    play02ボタンを押すと、playerのアイテムが置き換えられ、動画プレイヤーにセットされ再生されます。
    このままですと、playerのアイテムはsample02のままです。
    play01ボタンを押した時には、sample01になるように修正します。

    
    @IBAction func playBtnTapped(_ sender: Any) {
    	let fileName = “sample01”
    	let fileExtension = “mp4”
    	guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    		print(“Url is nil”)
    		return
    	}
    	
    	let item = AVPlayerItem(url: url)
    	player.replaceCurrentItem(with: item)
    	player.play()
    	
    	setupNowPlaying(url: url)
    }
    
    

    play01ボタンを押すと、playerのアイテムをsample01に変更して、再生します。
    従来は、setupPlayer()内でplayerにアイテムセットを行なっていましたが、それは必要なくなったので、setupPlayer()を修正します。

    
    private func setupPlayer() {
    	playerView.player = player
    }
    
    

    これで、バックグラウンドでの音楽が二重になる問題に関しては解消できると思います。

  3. nackpan様

    kkです。
    早速のコメントありがとうございます。
    大変有難く、早速、実験させて頂きました結果を、報告させて頂きます。
    (1)playerを一つにすることによって、sample01.mp4と、sample02.mp4の両方が、同時再生される事は、無くなりました。ありがとうございます。
    (2)上記、対応を導入後、バックグラウンドに移行したところ、下記のようになりました。
      ①AVPlayerLayer再生中=>バックグラウンド移行=>AVPlayerLayerのリモートコマンドセンターで、操作・再生継続可能
      ②AVPlayerViewController再生中=>バックグラウンド移行=>AVPlayerVieControllerのリモートコマンドセンターで、操作・再生継続可能
      ③AVPlayerLayer再生=>AVPlayerViewController再生=>AVPlayerLayer再生中=>バックグラウンド移行=>AVPlayerViewControllerのリモートコマンドセンターで、操作・再生継続可能(AVPlayerLayerのリモートコマンドセンターではない)

    アプリの仕様としては、
     ・AVPlayerLayerでは、短時間(3〜5秒)のmp4ファイルを、順次連続再生
     ・AVPlayerViewControllerでは、長時間(1〜2時間)のmp4ファイルを再生
     ・AVPlayerLayerのファイルとファイルの再生の間には、DispatchQを使用して、待ち時間(1〜10秒くらい)を置くので
      その間、誤動作を避けるために、ボタン表示を、無効化(薄いグレー)
      無効化のコードは、以下を使用、(待ち時間を過ぎれば有効化:それぞれをtrueにしています)
      func setupRemoteCommandCenter() {//func setupRemo
    let commandCenter = MPRemoteCommandCenter.shared();
    commandCenter.playCommand.isEnabled = false
    commandCenter.playCommand.addTarget {event in
    return .success
    }
    commandCenter.pauseCommand.isEnabled = false
    commandCenter.pauseCommand.addTarget {event in
    return .success
    }
    commandCenter.togglePlayPauseCommand.isEnabled = false
    commandCenter.togglePlayPauseCommand.addTarget {event in
    return .success
    }
    commandCenter.nextTrackCommand.isEnabled = false
    commandCenter.togglePlayPauseCommand.addTarget {event in
    return .success
    }
    commandCenter.previousTrackCommand.isEnabled = false
    commandCenter.togglePlayPauseCommand.addTarget {event in
    return .success
    }

    引き続きの質問となってしまい、申し訳ありませんが、
    ・AVPlayerLayerでの再生中のバックグラウンド移行後は、必ず、AVPlayerLayerのリモートコマンドセンター(⏪・▶️・⏩)で操作
    ・AVPlayerViewControllerでの再生中のバックグラウンド移行時は、必ず、AVPlayerViewControllerのリモートコマンドセンター(-15sec・▶️・+15sec)で操作
    をできるようにする方法は、有りますでしょうか。
    これについては、全く的外れかもしれませんが、AVPlayerLayer再生ボタンを押した時に、
    ・deleteRemoteCommandEvent() => addRemoteCommandEvent()
    を実行してみましたが、効果がありませんでした。

    コメントを頂ければ有難く、どうか、よろしくお願い致します。
    失礼致します。

  4. kkさん、こんにちは。

    いくつか実験してみました。
    バックグラウンドでの再生コントロールを、AVPlayerLayerのものとAVPlayerViewControllerのもので切り替えるのはうまくいきませんでした。
    AVPlayerViewControllerには、updatesNowPlayingInfoCenterというプロパティがあります。
    AVPlayerViewControllerがNowPlayingInfoCenter(バックグラウンドでの再生コントロールの情報)を更新するかどうかを示すフラグです。
    このupdatesNowPlayingInfoCenterのtrue/falseを切り替えることで、AVPlayerLayerでの再生コントロールとAVPlayerViewControllerでの再生コントロールを切り替えられるか試しましたが、だめでした。true/falseを切り替えていると、AVPlayerLayerでのバックグラウンド移行後に再生コントロールが表示されない事態になってしまいました。

    そこで、updatesNowPlayingInfoCenterのtrue/falseを切り替えるのではなく、最初にfalseとしてしまう方法をためしてみました。
    AVPlayerViewControllerで自動的に用意されるバックグラウンド用再生コントロールを取りやめて、自前でコントロールを管理します。
    そうなると、
    * 再生が進むと、現在の時刻が更新され、シークバー(プログレスバー)が進む
    * シークバーのボタンを動かすと再生位置が変わる
    * 十数秒の早戻し、早送り
    を実装する必要があります。
    それに関して、記事を書きました。
    [iOS]動画を再生する(早戻り、早送り、シーク) – nackpan Blog
    [iOS]動画を再生する(バックグラウンドでの早戻り、早送り、シーク) – nackpan Blog
    ごらんください。

    play01ボタンを押すとsample01を再生、play02ボタンを押すとsample02を再生する事例に戻ります。

    実装する事柄。
    * play01ボタンを押すとsample01を再生、play02ボタンを押すとsample02を再生
    * sample01再生時には、バックグラウンド用再生コントロールに「再生」「次トラック」「前トラック」ボタンを表示。バー操作は使用不能。
    * sample02再生時には、バックグラウンド用再生コントロールに「再生」「早戻し」「早送り」ボタンを表示。バー操作で時間移動可能。

    [iOS]動画を再生する(バックグラウンドでの早戻り、早送り、シーク) – nackpan Blogの記事にあるプログラミングリストに加筆するかたちで紹介します。

    1. Play02ボタンを配置して、ViewControllerとActionで結びつけてください。
    名前はplay02BtnTappedとします。

    2. ViewControllerのプロパティに以下を加えてください

    
    var playerController = AVPlayerViewController()
    /// playerViewControllerで再生があったことを示すフラグ
    var playerViewControllerPlayedFlag = false
    
    

    3. viewDidLoadあたりで、AVPlayerViewControllerはNowPlayingInfoCenterの更新はしないと示します。

    
    playerController.updatesNowPlayingInfoCenter = false
    
    

    4. playBtnTapped()を修正

    
    @IBAction func playBtnTapped(_ sender: Any) {
            let commandCenter = MPRemoteCommandCenter.shared()
            commandCenter.skipBackwardCommand.isEnabled = false
            commandCenter.skipForwardCommand.isEnabled = false
            commandCenter.changePlaybackPositionCommand.isEnabled = false
            
            if playerViewControllerPlayedFlag {
                // playerViewControllerで再生があったあとは、ファイルを置き換えます
                replacePlayerItem(fileName: "clip", fileExtension: "mp4")
                
                // playerViewControllerで再生したflagをfalseに戻す
                playerViewControllerPlayedFlag = false
            }
    
            player.play()
        }
    
    

    5. Play02ボタン関連の記述

    
    @IBAction func play02BtnTapped(_ sender: Any) {
        playerViewControllerPlayedFlag = true
        
        let commandCenter = MPRemoteCommandCenter.shared()
        commandCenter.skipBackwardCommand.isEnabled = true
        commandCenter.skipForwardCommand.isEnabled = true
        commandCenter.changePlaybackPositionCommand.isEnabled = true
        
        let fileName = "sample02"
        let fileExtension = "mp4"
        playLongVideo(fileName: fileName, fileExtension: fileExtension)
        
    }
    
    private func playLongVideo(fileName: String, fileExtension: String) {
        // Playerのファイル置き換え
        replacePlayerItem(fileName: fileName, fileExtension: fileExtension)
        
        // 動画プレイヤーにplayerをセット
        playerController.player = player
        
        // 動画プレイヤーを表示して再生
        self.present(playerController, animated: true) {
            self.player.play()
        }
    }
    
    

    これで、バックグラウンド用の再生コントロールを意図する表示にできると思います。

  5. nackpan様
    kkです。
    多岐に渡る内容の記事掲載、ありがとうございます。
    豊富な内容のため、一つづつ実装して勉強させて頂きます。

    今後とも、よろしくお願い致します。

  6. kkさん、こんにちは。

    さきほど(2020/05/18朝)、kkさんのコメントを誤判定して削除してしまいました。
    毎朝、ブランドコピー品のセールを謳うspamコメントを削除しているのですが、そのさいにkkさんのコメントが入っているのに気づかず、削除を選んでいました。
    削除中にkkさんのコメントと気付いたのですが、削除取り消しが間に合いませんでした。
    申し訳ありません。

    もし面倒でなければ、コメントの再送をおねがいします。
    あらためて送るほどでなければ放置してください。
    もうしわけありません。

  7. nackpan様

    いつもありがとうございます。
    以下のコメントを、再送させて頂きます。
    お手数をおかけしますが、よろしくお願いいたします。
    ======= 以下が、送付させて頂いたコメントです。======
    kkと申します。以前は、バックグラウンドでの動画再生について
    丁寧なご説明を頂き、大変感謝しております。
    ありがとうございました。

    今回、その続きで、アドバイスを頂きたく、投稿させて頂きます。

    下記の仕様で、動画再生を、継続的に実行していると、突然、停止
    してしまうことがあります。

    【現象】
    ・複数動画の連続再生
     (語学学習用で、台詞毎の、短い動画の連続再生を意図しております。)
    ・動画一つの長さは約5秒〜10秒
    ・一つの動画を、複数回(2〜3回)繰り返した後、次の動画再生へ移行
    ・動画のサイズ:640×340、500kB〜1MB、コーデック:H.264
    ・再生例:動画①1回目再生 => 動画①2回目再生 => 動画②1回目再生 => 動画②2回目再生 =>・・・つづく
    ・全ての動画を、1回ずつ再生する場合は、問題ないが、各動画を複数回ずつ再生した時に発生
    ・再生速度を、上げると発生しやすくなる。
    ・再生したい動画のファイル数が増えると、発生しやすくなる。

    【発生条件の例】
    上記現象は、起こったり、起こらなかったりするが、下記条件にすると、高い確率で発生する。
    ・再生速度アップ:1.8 倍(通常速度でも発生するが、頻度が少ない)
    ・6個目までの動画では、起こらず、7個目以降で、同じ現象が起こりやすくなる。
    ・7個目の動画の再生が始まった直後に、停止する。

    【確認した内容】
    以下は、現象発生後、確認した内容です。
    ・再生ボタンを押すと、再び再生を始めることができるため、ハングではないように見える
    ・よく起こる箇所(繰り返し再生のところ)で、player.play()の後で、
     再生速度、エラーがあるかどうかを”isPlaying”で、チェックしたが、速度は、”=1.8”、エラーは”=nil”となる
    ・player.play()までは、動作しているが、再生が開始直後に停止しているので、再生終了通知が来ないまま停止し
     しており、再生状態の検出が「できない
    ・シミュレーターでは、発生しない

    【確認した実機】
    確認デバイスは、iPhone 6plus、iPad Pro(2nd Gene)で、iPhone 6plusは頻繁に、iPad proは
    稀(今まで約100時間ほど再生して、1回発生)に起こります。

    【お願い】
    下記の内容で、アドバイスを頂けないでしょうか。
    1)起こさなくすることができる回避策、案がありますでしょうか。
    2)もし起こった場合の検出と、再生継続させる方法は、ありますでしょうか。

    【ソースコード】
    使用しているソースコードは、nackpan様の、投稿記事のコードに、追加(1)、追加(2)を
    挿入したものです。

    ====== 以下、実際のコード ======

    import UIKit
    import AVFoundation
    import MediaPlayer
    import AVKit

    class ViewController: UIViewController {

    @IBOutlet weak var playerView: PlayerView!

    var player = AVPlayer()
    var itemURL: URL?

    override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.

    let audioSession = AVAudioSession.sharedInstance()
    do {
    try audioSession.setCategory(.playback, mode: .moviePlayback)
    } catch {
    print(“Setting category to AVAudioSessionCategoryPlayback failed.”)
    }
    do {
    try audioSession.setActive(true)
    print(“audio session set active !!”)
    } catch {

    }

    setupPlayer()

    addLifeCycleObserver()

    addRemoteCommandEvent()

    //追加(1)viewDidLoad内:ここから
    //(1)-1:動画が再生し終わったことを監視する設定
    NotificationCenter.default.addObserver(
    self, selector: #selector(self.endOfMovie),
    name: .AVPlayerItemDidPlayToEndTime, object: nil)

    //(1)-2:playerItemsの作成
    //①pathの設定
    let path1 = Bundle.main.path(forResource: “sample001″, ofType: “mp4″)
    let path2 = Bundle.main.path(forResource: “sample002”, ofType: “mp4”)
    let path3 = Bundle.main.path(forResource: “sample003”, ofType: “mp4″)
    let path4 = Bundle.main.path(forResource: “sample004”, ofType: “mp4”)
    let path5 = Bundle.main.path(forResource: “sample005”, ofType: “mp4”)
    let path6 = Bundle.main.path(forResource: “sample006”, ofType: “mp4”)
    let path7 = Bundle.main.path(forResource: “sample007”, ofType: “mp4”)
    let path8 = Bundle.main.path(forResource: “sample008”, ofType: “mp4”)
    let path9 = Bundle.main.path(forResource: “sample009”, ofType: “mp4”)
    let path10 = Bundle.main.path(forResource: “sample010”, ofType: “mp4″)
    //②urlの設定
    let url1 = URL(fileURLWithPath: path1!)
    let url2 = URL(fileURLWithPath: path2!)
    let url3 = URL(fileURLWithPath: path3!)
    let url4 = URL(fileURLWithPath: path4!)
    let url5 = URL(fileURLWithPath: path5!)
    let url6 = URL(fileURLWithPath: path6!)
    let url7 = URL(fileURLWithPath: path7!)
    let url8 = URL(fileURLWithPath: path8!)
    let url9 = URL(fileURLWithPath: path9!)
    let url10 = URL(fileURLWithPath: path10!)
    //③playerItemの設定
    let playerItem1 = AVPlayerItem(url: url1)
    let playerItem2 = AVPlayerItem(url: url2)
    let playerItem3 = AVPlayerItem(url: url3)
    let playerItem4 = AVPlayerItem(url: url4)
    let playerItem5 = AVPlayerItem(url: url5)
    let playerItem6 = AVPlayerItem(url: url6)
    let playerItem7 = AVPlayerItem(url: url7)
    let playerItem8 = AVPlayerItem(url: url8)
    let playerItem9 = AVPlayerItem(url: url9)
    let playerItem10 = AVPlayerItem(url: url10)
    //④配列playerItemsの作成
    playerItems = [playerItem1,playerItem2,playerItem3,playerItem4,playerItem5,playerItem6,playerItem7,playerItem8,playerItem9,playerItem10]
    //追加(1):ここまで

    }

    private func setupPlayer() {
    let fileName = “sample001”
    let fileExtension = “mp4”
    guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    print(“Url is nil”)
    return
    }

    itemURL = url
    let item = AVPlayerItem(url: url)
    player = AVPlayer(playerItem: item)

    playerView.player = player

    }

    /// Playボタンが押された
    @IBAction func playBtnTapped(_ sender: Any) {
    player.play()
    player.rate = playerRate
    if let url = itemURL {
    setupNowPlaying(url: url)
    }
    }

    //追加(2):ここから
    //①パラメーターの設定
    var currentTrack:Int = 0
    var subCurrentTrack:Int = 0
    var playerItems:[AVPlayerItem] = []
    var playerRate:Float = 1.8
    //var totalCounterNumber:Int = 1

    //全体の繰り返し回数の表示用
    //@IBOutlet weak var totalCounter: UILabel!
    //②endOfMovewの定義
    @objc func endOfMovie() {//objc func(1)
    //ここにしたいことを記載
    //if currentTrack < 5{// <=この時は起こらない
    if currentTrack < 9{
    // if subCurrentTrack < 0{// <=この時は起こらない
    if subCurrentTrack < 2{
    currentTrack += 0
    subCurrentTrack += 1
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate

    //動画が止まった時に検出、ここから
    var isPlaying: Bool {
    return player.rate != 0 && player.error == nil
    }
    print("isPlaying =",isPlaying)

    //動画が止まった時に検出、ここまで

    }else{
    currentTrack += 1
    subCurrentTrack = 0
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate
    }
    }else{
    //if subCurrentTrack < 0{// <=この時は起こらない
    if subCurrentTrack MPRemoteCommandHandlerStatus in
    self.remoteTogglePlayPause(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.playCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePlay(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.pauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePause(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.nextTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remoteNextTrack(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.previousTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePrevTrack(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    }

    func remoteTogglePlayPause(_ event: MPRemoteCommandEvent) {
    // イヤホンのセンターボタンを押した時の処理
    // (略)
    }

    func remotePlay(_ event: MPRemoteCommandEvent) {
    player.play()
    }

    func remotePause(_ event: MPRemoteCommandEvent) {
    player.pause()
    }

    func remoteNextTrack(_ event: MPRemoteCommandEvent) {
    // 「次へ」ボタンが押された時の処理
    // (略)
    }

    func remotePrevTrack(_ event: MPRemoteCommandEvent) {
    // 「前へ」ボタンが押された時の処理
    // (略)

    }

    }

  8. kkさん、こんにちは。
    あらためて、コメントをお送りいただきありがとうございます。
    これから、調べてみたいと思います。

  9. nackpan様

    kkです。投稿の掲載、ありがとうございます。
    掲載頂いた、コードを確認したところ、
    ・追加(2)ここまで
    ・上記と、そこから数行
    が欠落していましたので、念のため、もう一度、送付させて頂きます。
    よろしくお願いいたします。

    ====再々送付====
    kkと申します。以前は、バックグラウンドでの動画再生について
    丁寧なご説明を頂き、大変感謝しております。
    ありがとうございました。

    今回、その続きで、アドバイスを頂きたく、投稿させて頂きます。

    下記の仕様で、動画再生を、継続的に実行していると、突然、停止
    してしまうことがあります。

    【現象】
    ・複数動画の連続再生
     (語学学習用で、台詞毎の、短い動画の連続再生を意図しております。)
    ・動画一つの長さは約5秒〜10秒
    ・一つの動画を、複数回(2〜3回)繰り返した後、次の動画再生へ移行
    ・動画のサイズ:640×340、500kB〜1MB、コーデック:H.264
    ・再生例:動画①1回目再生 => 動画①2回目再生 => 動画②1回目再生 => 動画②2回目再生 =>・・・つづく
    ・全ての動画を、1回ずつ再生する場合は、問題ないが、各動画を複数回ずつ再生した時に発生
    ・再生速度を、上げると発生しやすくなる。
    ・再生したい動画のファイル数が増えると、発生しやすくなる。

    【発生条件の例】
    上記現象は、起こったり、起こらなかったりするが、下記条件にすると、高い確率で発生する。
    ・再生速度アップ:1.8 倍(通常速度でも発生するが、頻度が少ない)
    ・6個目までの動画では、起こらず、7個目以降で、同じ現象が起こりやすくなる。
    ・7個目の動画の再生が始まった直後に、停止する。

    【確認した内容】
    以下は、現象発生後、確認した内容です。
    ・再生ボタンを押すと、再び再生を始めることができるため、ハングではないように見える
    ・よく起こる箇所(繰り返し再生のところ)で、player.play()の後で、
     再生速度、エラーがあるかどうかを”isPlaying”で、チェックしたが、速度は、”=1.8”、エラーは”=nil”となる
    ・player.play()までは、動作しているが、再生が開始直後に停止しているので、再生終了通知が来ないまま停止し
     しており、再生状態の検出が「できない
    ・シミュレーターでは、発生しない

    【確認した実機】
    確認デバイスは、iPhone 6plus、iPad Pro(2nd Gene)で、iPhone 6plusは頻繁に、iPad proは
    稀(今まで約100時間ほど再生して、1回発生)に起こります。

    【お願い】
    下記の内容で、アドバイスを頂けないでしょうか。
    1)起こさなくすることができる回避策、案がありますでしょうか。
    2)もし起こった場合の検出と、再生継続させる方法は、ありますでしょうか。

    【ソースコード】
    使用しているソースコードは、nackpan様の、投稿記事のコードに、追加(1)、追加(2)を挿入したものです。

    ====== 以下、実際のコード ======

    import UIKit
    import AVFoundation
    import MediaPlayer
    import AVKit

    class ViewController: UIViewController {

    @IBOutlet weak var playerView: PlayerView!

    var player = AVPlayer()
    var itemURL: URL?

    override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.

    let audioSession = AVAudioSession.sharedInstance()
    do {
    try audioSession.setCategory(.playback, mode: .moviePlayback)
    } catch {
    print(“Setting category to AVAudioSessionCategoryPlayback failed.”)
    }
    do {
    try audioSession.setActive(true)
    print(“audio session set active !!”)
    } catch {

    }

    setupPlayer()

    addLifeCycleObserver()

    addRemoteCommandEvent()

    //追加(1)viewDidLoad内:ここから
    //(1)-1:動画が再生し終わったことを監視する設定
    NotificationCenter.default.addObserver(
    self, selector: #selector(self.endOfMovie),
    name: .AVPlayerItemDidPlayToEndTime, object: nil)

    //(1)-2:playerItemsの作成
    //①pathの設定
    let path1 = Bundle.main.path(forResource: “sample001″, ofType: “mp4″)
    let path2 = Bundle.main.path(forResource: “sample002”, ofType: “mp4”)
    let path3 = Bundle.main.path(forResource: “sample003”, ofType: “mp4″)
    let path4 = Bundle.main.path(forResource: “sample004”, ofType: “mp4”)
    let path5 = Bundle.main.path(forResource: “sample005”, ofType: “mp4”)
    let path6 = Bundle.main.path(forResource: “sample006”, ofType: “mp4”)
    let path7 = Bundle.main.path(forResource: “sample007”, ofType: “mp4”)
    let path8 = Bundle.main.path(forResource: “sample008”, ofType: “mp4”)
    let path9 = Bundle.main.path(forResource: “sample009”, ofType: “mp4”)
    let path10 = Bundle.main.path(forResource: “sample010”, ofType: “mp4″)
    //②urlの設定
    let url1 = URL(fileURLWithPath: path1!)
    let url2 = URL(fileURLWithPath: path2!)
    let url3 = URL(fileURLWithPath: path3!)
    let url4 = URL(fileURLWithPath: path4!)
    let url5 = URL(fileURLWithPath: path5!)
    let url6 = URL(fileURLWithPath: path6!)
    let url7 = URL(fileURLWithPath: path7!)
    let url8 = URL(fileURLWithPath: path8!)
    let url9 = URL(fileURLWithPath: path9!)
    let url10 = URL(fileURLWithPath: path10!)
    //③playerItemの設定
    let playerItem1 = AVPlayerItem(url: url1)
    let playerItem2 = AVPlayerItem(url: url2)
    let playerItem3 = AVPlayerItem(url: url3)
    let playerItem4 = AVPlayerItem(url: url4)
    let playerItem5 = AVPlayerItem(url: url5)
    let playerItem6 = AVPlayerItem(url: url6)
    let playerItem7 = AVPlayerItem(url: url7)
    let playerItem8 = AVPlayerItem(url: url8)
    let playerItem9 = AVPlayerItem(url: url9)
    let playerItem10 = AVPlayerItem(url: url10)
    //④配列playerItemsの作成
    playerItems = [playerItem1,playerItem2,playerItem3,playerItem4,playerItem5,playerItem6,playerItem7,playerItem8,playerItem9,playerItem10]
    //追加(1):ここまで

    }

    private func setupPlayer() {
    let fileName = “sample001”
    let fileExtension = “mp4”
    guard let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else {
    print(“Url is nil”)
    return
    }

    itemURL = url
    let item = AVPlayerItem(url: url)
    player = AVPlayer(playerItem: item)

    playerView.player = player

    }

    /// Playボタンが押された
    @IBAction func playBtnTapped(_ sender: Any) {
    player.play()
    player.rate = playerRate
    if let url = itemURL {
    setupNowPlaying(url: url)
    }
    }

    //追加(2):ここから
    //①パラメーターの設定
    var currentTrack:Int = 0
    var subCurrentTrack:Int = 0
    var playerItems:[AVPlayerItem] = []
    var playerRate:Float = 1.8
    //var totalCounterNumber:Int = 1

    //全体の繰り返し回数の表示用
    //@IBOutlet weak var totalCounter: UILabel!
    //②endOfMovewの定義
    @objc func endOfMovie() {//objc func(1)
    //ここにしたいことを記載
    //if currentTrack < 5{// <=この時は起こらない
    if currentTrack < 9{
    // if subCurrentTrack < 0{// <=この時は起こらない
    if subCurrentTrack < 2{
    currentTrack += 0
    subCurrentTrack += 1
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate

    //動画が止まった時に検出、ここから
    var isPlaying: Bool {
    return player.rate != 0 && player.error == nil
    }
    print("isPlaying =",isPlaying)

    //動画が止まった時に検出、ここまで

    }else{
    currentTrack += 1
    subCurrentTrack = 0
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate
    }
    }else{
    //if subCurrentTrack < 0{// <=この時は起こらない
    if subCurrentTrack MPRemoteCommandHandlerStatus in
    self.remoteTogglePlayPause(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.playCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePlay(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.pauseCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePause(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.nextTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remoteNextTrack(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    commandCenter.previousTrackCommand.addTarget{ [unowned self] commandEvent -> MPRemoteCommandHandlerStatus in
    self.remotePrevTrack(commandEvent)
    return MPRemoteCommandHandlerStatus.success
    }
    }

    func remoteTogglePlayPause(_ event: MPRemoteCommandEvent) {
    // イヤホンのセンターボタンを押した時の処理
    // (略)
    }

    func remotePlay(_ event: MPRemoteCommandEvent) {
    player.play()
    }

    func remotePause(_ event: MPRemoteCommandEvent) {
    player.pause()
    }

    func remoteNextTrack(_ event: MPRemoteCommandEvent) {
    // 「次へ」ボタンが押された時の処理
    // (略)
    }

    func remotePrevTrack(_ event: MPRemoteCommandEvent) {
    // 「前へ」ボタンが押された時の処理
    // (略)

    }

    }

  10. nackpan様
    kkです。

    再々送付しましたが、やはり欠落しているようなので、
    ・追加(2)の部分とその前後のみ
    を、以下に送付致します。
    ややこしくなり、申し訳ありませんが、よろしくお願いいたします。

    /// Playボタンが押された
    @IBAction func playBtnTapped(_ sender: Any) {
    player.play()
    player.rate = playerRate
    if let url = itemURL {
    setupNowPlaying(url: url)
    }
    }

    //追加(2):ここから
    //①パラメーターの設定
    var currentTrack:Int = 0
    var subCurrentTrack:Int = 0
    var playerItems:[AVPlayerItem] = []
    var playerRate:Float = 1.8
    //var totalCounterNumber:Int = 1
    //全体の繰り返し回数の表示用
    //@IBOutlet weak var totalCounter: UILabel!
    //②endOfMovewの定義
    @objc func endOfMovie() {//objc func(1)
    //ここにしたいことを記載
    //if currentTrack < 5{// <=この時は起こらない
    if currentTrack < 9{
    // if subCurrentTrack < 0{// <=この時は起こらない
    if subCurrentTrack < 2{
    currentTrack += 0
    subCurrentTrack += 1
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate
    //動画が止まった時に検出、ここから
    var isPlaying: Bool {
    return player.rate != 0 && player.error == nil
    }
    print("isPlaying =",isPlaying)
    //動画が止まった時に検出、ここまで
    }else{
    currentTrack += 1
    subCurrentTrack = 0
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate
    }
    }else{
    //if subCurrentTrack < 0{// <=この時は起こらない
    if subCurrentTrack < 2{
    currentTrack += 0
    subCurrentTrack += 1
    }else{
    currentTrack = 0
    subCurrentTrack = 0
    totalCounterNumber += 1
    //全体の繰り返し回数表示用
    totalCounter.text = String(totalCounterNumber)
    }
    player.replaceCurrentItem(with: playerItems[currentTrack])
    playerView.player?.seek(to: CMTime.zero)
    player.play()
    player.rate = playerRate
    }
    }
    //追加(2):ここまで

    @IBAction func pauseBtnTapped(_ sender: Any) {
    print("pauseBtn at L151")
    player.pause()
    }

  11. kkさん、こんにちは。
    手元の環境で実験してみました。
    iPhone 5s (iOS 12.4)、
    iPhone 6 Plus (iOS 12.4)、
    iPhone Xs Max (iOS 13.4)
    iPad Pro(12.9インチ)(第3世代) (iOS 13.4)
    で、各々一時間ほど検証を行いました。
    しかしながら、手元の環境では問題は発生しませんでした。

    そのため、なんとも改善の手立てについては申し上げることができません。
    お力になれず、申し訳ありません。

  12. nackpan様
    kkです。早速ご確認いただきありがとうございます。また、再現のできない提示になってしまい、申し訳ありません。確実に再現できる条件を明確にして、再度相談させて頂きます。お手数をお掛けし、申し訳ありませんでした。

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください