「Repete Plus」をアップデートしました。(version 6.1)

「Repete Plus」をアップデートしました。(version 6.1)

Repete Plusは、語学学習を支援するオーディオプレイヤーです。
無音部分を自動認識し、語学教材をフレーズごとに間隔をあけて再生できます。

App Storeはこちら

使い方を紹介しているサポートサイトはこちら
Repete Plus – nackpan Blog

変更点

  • 選曲画面の各アルバムおよびストレージタブの各フォルダで、再生不要区間数/区間数が表示されるようになりました。(ただし、すでに区間分割済みで再生不要区間が一つ以上ある時に限ります)
アルバムタブでの再生不要区間数/区間数の例
ストレージタブでの再生不要区間数/区間数の例

習得した区間を再生不要にする学習スタイルの場合、学習の進展度が分かりやすくなりました。


語学学習を助ける「Repete Plus」をよろしくおねがいします。

「Repete Plus」をアップデートしました。(version 6.0)

「Repete Plus」をアップデートしました。(version 6.0)

Repete Plusは、語学学習を支援するオーディオプレイヤーです。
無音部分を自動認識し、語学教材をフレーズごとに間隔をあけて再生できます。

App Storeはこちら

使い方を紹介しているサポートサイトはこちら
Repete Plus – nackpan Blog

変更点

* タップジェスチャーに「99回繰り返し」が加わりました。
(設定 > ジェスチャー > タップ)
この項目を選んで指定の範囲でタップすると、区間(もしくはペア)を99回再生することができます。再度タップすると99回再生は解除されます。
区間(もしくはペア)を(ほぼ)無限にリピートしたいと思った際にそれをワンタップで行えるようになりました。

* バグ修正。バージョン5.9でテキストエリアを拡大した際、再生速度表示がぽつんとテキストエリアに表示される不具合がありました。この不具合を修正しました。


語学学習を助ける「Repete Plus」をよろしくおねがいします。

「Repete Plus」をアップデートしました。(version 5.9)

「Repete Plus」をアップデートしました。(version 5.9)

Repete Plusは、語学学習を支援するオーディオプレイヤーです。
語学教材のフレーズごとに自動的に間隔をあけて再生できます。

App Storeはこちら

使い方を紹介しているサポートサイトはこちら
Repete Plus – nackpan Blog

変更点

* 再生速度を表示できるようになりました。
(設定 > 詳細 > 再生速度を表示)

※ バージョン 5.9で、テキストエリアを拡大した際、再生速度表示がぽつんとテキストエリアに表示されたままの不具合があります。お恥ずかしい限りです。早急に修正します。


前回のアップデート(5.8)が6月19日。Repeteで再生速度表示機能を加えたのが6月21日。すぐに再生速度表示機能をRepete Plusにも加えようと考えていましたが遅れてしまいました。
Repete Plusではインポート・エクスポート機能がありそこで再生速度表示機能を表示するか否かの情報も送れるようにデータベースのモデルの変更を行ないました。また、モデルにいくつかのプロパティを加えました。
7月前半にかけて、スムーズに移行できるかの検証をしつこく行いました。この時期、イラスト描きに力を入れていて、アプリ開発作業が停滞気味でした。7月後半にはMacが故障、新規購入で開発機がないという状況になり8月にもつれこんでしまいました。
今回のアップデートでは、新規機能というわけではないですがジェスチャー項目の順番の入れ替えを行なっています。「なし」の項目が表の最後尾にあったものが、最初に移動しました。項目を追加しやすいかたちにしました。


語学学習を助ける「Repete Plus」をよろしくおねがいします。

MacBook Pro(2019)を購入しました

MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)を購入しました。

経緯

7月15日、使用していたMacBook Pro 2017の電源が入らなくなりました。
電源コードを繋いでもさっぱり反応しません。
持ち込み修理に赴きました。

それ以前からキーボードの反応が悪い状態が続いていました。
MacBook、MacBook Air、MacBook Pro キーボード修理プログラム
の対象となっていました。これだと無償修理してもらえます。ただ、バッテリーの状態が「交換修理」が必要になっており、バッテリーの交換が入るなら費用がかかるんじゃないかしらと懸念して、だましだまし使用していました。実際には、キーボード修理の際にバッテリーも交換することになり、それもふくめての無償修理だったので、さっさと修理してもらえば良かったのです。

電源が入らないことをAppleジーニアスバーで確認してもらい、修理を依頼。費用が10万かかるようなら、修理は取りやめてもらうことにしました。

およそ一週間後。
MacBook Pro 2017が戻ってきました。
キーボードの交換修理が行われました。
電源が入らない問題に関しては、意向により修理はされていませんでした。

MacBook Pro 2017はあまりもたなかったなと気持ちが沈みました。
購入したのは2017年10月。
MacBook Pro 2017を購入しました – nackpan Blog
がっかりしながら、新たなMacを購入することにしました。

新Mac購入

対象の家電量販店でd払いで購入すれば、7月中は20%ポイント還元というキャンペーンをやっていました。
15万*0.2で3万ポイント還元されます。これ良いね、ということでK’s ケーズデンキでMacBook Pro 2019を購入しました。その後、キャンペーンページで確かめると、今回のポイント還元は、上限が1万ポイントでした。あらら。

前回のMac購入時には、ストレージを512GB、メモリを16GBにカスタマイズしました。その前のMacBook Airの時代に、ストレージがカツカツになり、やりくりで苦労したので余裕を持って臨めるように512GBにしたものです。今回は、せっかくカスタマイズしたMacBook Pro 2017がはやくも使用できなくなってがっくりきてMacのスペックをあげる意欲が減少していたので、あらかじめ用意されているラインナップを購入しました。
256GBのストレージというのは心配でしたが、前代でもストレージの半分も使ってないからいけるだろうとの考えです。

移行処理

バックアップを取っていたハードディスクから移行処理を行いました。
前代のMacではストレージが512GBで、今回のMacは256GB。
容量が足りないので移行できないとの、メッセージ。
心配していた問題が発生しました。
ダウンロードフォルダのチェックを外して再トライ。
まだ容量が足りないとのことで、さらにそのほかのフォルダのチェックを外して再トライ。  

移行はできました。

起動してみると、チェックを外したダウンロードフォルダなども移行されていました。
ちょっと、よくわからないですね…
「このMacについて」 > 「ストレージ」 を見ると、250.69GBのぎりぎりまで使用している状況。
これはいけないと、ファイルを削除・退避を行いました。
巨大な音声ファイルや素材テクスチャファイルなどを退避、Pages, Numbersなどのアプリケーションを削除。  
70GBほど空きを作りました。  

開発環境準備

Xcodeはアプリケーションからなくなっていたので、あらためてダウンロードしました。
Xcodeは「このMacについて」 > 「ストレージ」> 「管理」 > 「アプリケーション」でのサイズで6.39GB
Finder > 「アプリケーション」> Xcode.app でのサイズで11.88GB
ほんとに容量喰いでございます。
でも、これがなければ始まらないので、やっていきましょう。
(というか、開発機にするなら、ストレージ容量は512GBにすべきでした…)
いや、まあ、やっていきましょう。


AVSpeechSynthesizerをバックグラウンド再生ありで使う

(2019/08/09更新。バックグラウンド移行時に音が止まっているならAudioSessionを無効化する方法に書き換えました。)

以前[iOS]電話がかかってきたとき、ヘッドホンジャックが抜かれたときの対応 – nackpan Blogという記事を書きました。

そこでのkkさんのコメントをきっかけにAVSpeechSynthesizerの使い方について調べました。

AVSpeechSynthesizerを用いてテキストを読み上げるアプリ。フォアグラウンドで再生している場合は問題ありません。
バックグラウンド再生を絡めた場合にトラブル。
AVSpeechSynthesizerでspeak中にpauseしたあとアプリを中断、再びアプリを開くと勝手に音が鳴るなど、アプリ再開時の挙動が妙でした。

ログを見るとアプリ再開時にinterruptionの通知が届いていたので、interruptionNotificationについて調査。

InterruptionNotificationについて

Interruptionの通知は、電話がかかってきた時などでAudioSessionに割り込みが発生した時に届くものと考えていました。
今回、アプリの再開時に、interruptionの通知が届きました。

Appleのドキュメントを見直しました。

interruptionNotification – AVAudioSession | Apple Developer Documentation

Noteとして以下の記述があります。

Starting in iOS 10, the system will deactivate the audio session of most apps in response to the app process being suspended. When the app starts running again, it will receive an interruption notification that its audio session has been deactivated by the system. This notification is necessarily delayed in time because it can only be delivered once the app is running again. If your app’s audio session was suspended for this reason, the userInfo dictionary will contain the AVAudioSessionInterruptionWasSuspendedKey key with a value of true.

If your audio session is configured to be non-mixable (the default behavior for the playback, playAndRecord, soloAmbient, and multiRoute categories), it’s recommended that you deactivate your audio session if you’re not actively using audio when you go into the background. Doing so will avoid having your audio session deactivated by the system (and receiving this somewhat confusing notification).

interruptionNotification – AVAudioSession | Apple Developer Documentation

 * iOS 10以降では、システムは、アプリプロセスが中断されると、ほとんどのアプリの AudioSessionを無効にする。
*  アプリの再開時に、AudioSessionがシステムによって無効にされたことを示す割り込み(interruption)通知を受け取る。
* この通知でAVAudioSessionInterruptionWasSuspendedKeyの値はtrue
* AudioSessionがミックス不可(playback、playAndRecord、soloAmbient、multiRouteのデフォルトの動作)設定なら、バックグラウンド移行時に、アクティブに使用していないAudioSessionを無効にすることをお勧め
 * そうすることで、この通知を受け取らなくて済む

とあるように、システムによってAudioSessionが無効にされたことを示すinterruptionがアプリの再開時に届く、とのことでした。

InterruptionNotificationの実験

バックグラウンド再生ありのAVSpeechSynthesizerを使ったアプリで、interruptionの通知について実験しました。
当初、実験した際(07/29)には、iOS 12.4で
* AVSpeechSynthesizerでspeak中に、pause。電源ボタンを押してアプリを中断。すると音声が勝手に鳴ってしまう。電源ボタンを押した直後にinterruptionの通知が届きました。(willResignActiveの前に発生)
という症状が発生しました。
しかし、その後あらためて実験すると、まったくこの症状は出ず、interruptionNotification – AVAudioSession のドキュメントにあるように再開後にinterruptionの通知が届きました。

対処法

アプリ再開時に音が勝手になるなど挙動が変になる問題は、システムによるAudioSessionの無効化が関係しているのだろう。AVSpeechSynthesizerでは再開時に上手いことやってくれない。Noteにあるように、明示的にAudioSessionを無効化すれば、通知を受け取らずに済むし挙動も適切なものになると考えました。

interruptionNotificationのNoteでは、アプリのAudioSessionが無効にされたことを示す通知がアプリ再開後に届くとあり、それを避けるには、バックグラウンド移行時にAudioSessionを無効化するとよい、とありました。


当初の実験(07/29)では、iOS 12.4での電源ボタンによるアプリの中断で、電源ボタンを押した直後に通知が届き、バックグラウンド移行時にAudioSessionを無効化する処理を書いても、音声が短いながらも勝手に鳴ってしまう症状が出ました。そのため、AVSpeechSynthesizerでの音声再生が終了したら、その段階でAudioSessionを無効化にするということで対処することにしていました。これにはAVSpeechSynthesizerDelegateを使いAVSpeechSynthesizerのpause, stop, finishの完了を知り、そこでAudioSessionを無効化にしていました。

しかし、その後の実験では「電源ボタンによるアプリの中断で、電源ボタンを押した直後に通知が届き、音声が短いながらも勝手に鳴ってしまう」症状は再現しませんでした。


そこで、interruptionNotificationのNoteが勧める方法に従いました。
バックグラウンド移行時に音が止まっている場合、AudioSessionを無効化。
speakのさいにAudioSessionを有効化しました。

バックグラウンド移行を検知する方法はこちらの記事が参考になります。
NotificationCenterを用いたライフサイクルイベントの検知

実装

今回はViewController.swiftにすべて記述することとしました。
メッセージラベル、Speakボタン、Pauseボタン、Stopボタンを貼り付けます。

アプリ画面

メッセージラベルとViewControllerをOutletで結びます。
@IBOutlet weak var messageLabel: UILabel!
SpeakボタンとViewControllerをActionで結びます。
@IBAction func speakBtnTapped(_ sender: Any) 
PauseボタンとViewControllerをActionで結びます。
@IBAction func pauseBtnTapped(_ sender: Any)
StopボタンとViewControllerをActionで結びます。
@IBAction func stopBtnTapped(_ sender: Any)

バックグラウンド再生用。
Capabilities > Background Modes をON > Audio, AirPlay, and Picture in Pictureにチェック

ViewController.swift

import UIKit
import AVFoundation
import AVKit
import MediaPlayer

class ViewController: UIViewController, AVSpeechSynthesizerDelegate {

    @IBOutlet weak var messageLabel: UILabel!
    var syntherizer = AVSpeechSynthesizer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // AudioSessionカテゴリをbackground再生ができるものに設定
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(AVAudioSession.Category.playback)
        } catch {
            print("Setting category to AVAudioSessionCategoryPlayback failed.")
        }

        addAudioSessionObservers()
        addRemoteCommandEvent()
        addLifeCycleObserver()
    }
    
    // 電話による割り込みと、オーディオルートの変化を監視します
    func addAudioSessionObservers() {
        let center = NotificationCenter.default
        center.addObserver(self, selector: #selector(handleInterruption(_:)), name: AVAudioSession.interruptionNotification, object: nil)
        center.addObserver(self, selector: #selector(audioSessionRouteChanged(_:)), name: AVAudioSession.routeChangeNotification, object: nil)
    }
    
    /// Interruption : 電話などによる割り込み
    @objc func handleInterruption(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
            let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
            let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
                return
        }
        
        if type == .began {
            // interruptionが開始した時(電話がかかってきたなど)
            if let wasSuspendedKeyValue = userInfo[AVAudioSessionInterruptionWasSuspendedKey] as? NSNumber {
                let wasSuspendedKey = wasSuspendedKeyValue.boolValue
                if wasSuspendedKey {
                    // suspeended key : true
                } else {
                    // suspended key : false
                }
            } else {
                // suspended key : nil
            }
        }
        else if type == .ended {
            // interruptionが終了した時の処理
            if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
                let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
                if options.contains(.shouldResume) {
                    // Interruption Ended - playback should resume
                } else {
                    // Interruption Ended - playback should NOT resume
                }
            }
        }
    }
    
    /// Audio Session Route Change : ルートが変化した(ヘッドフォンが抜き差しされた)
    @objc func audioSessionRouteChanged(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
            let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
            let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else {
                return
        }
        
        DispatchQueue.main.async {
            self.messageLabel.text = self.routeChangeReasonDescription(reason: reason)
        }

        switch reason {
        case .newDeviceAvailable:
            let session = AVAudioSession.sharedInstance()
            for output in session.currentRoute.outputs where output.portType == AVAudioSession.Port.headphones {
                // ヘッドフォンがつながった
                
                break
            }
        case .oldDeviceUnavailable:
            if let previousRoute =
                userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
                for output in previousRoute.outputs where output.portType == AVAudioSession.Port.headphones {
                    // ヘッドフォンが外れた
                    
                    // 音声をpauseしています
                    pause()
                    break
                }
            }
        default: ()
        }
        
    }
    
    public func speak() {
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setActive(true)
        } catch {
            
        }
        let voice = AVSpeechSynthesisVoice(language: "ja-JP")
        let utterance = AVSpeechUtterance(string: "おはようございます。今日もいい天気ですね。音声合成で読み上げています。文章をうまくよめていますか?")
        utterance.voice = voice
        utterance.preUtteranceDelay = 5
        
        if syntherizer.isPaused {
            syntherizer.continueSpeaking()
        } else {
            syntherizer.speak(utterance)
        }
    }
    
    public func pause() {
        syntherizer.pauseSpeaking(at: .immediate)
    }
    
    /// Speakボタンが押された
    @IBAction func speakBtnTapped(_ sender: Any) {
        // 音声出力を行う
        speak()
    }
    
    /// pauseボタンが押された
    @IBAction func pauseBtnTapped(_ sender: Any) {
        pause()
    }
    
    @IBAction func stopBtnTapped(_ sender: Any) {
        syntherizer.stopSpeaking(at: .immediate)
    }
    
    /// 監視する必要がなくなった段階で、Observerを取り外します
    private func removeAudioSessionObservers() {
        let center = NotificationCenter.default
        
        // AVAudio Session
        center.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
        center.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: nil)
        
    }
    
    /// route変更の理由表示用
    private func routeChangeReasonDescription(reason: AVAudioSession.RouteChangeReason) -> String {
        switch reason {
        case .unknown:
            return "unknown"
        case .newDeviceAvailable:
            return "newDeviceAvailable"
        case .oldDeviceUnavailable:
            return "oldDeviceUnailable"
        case .categoryChange:
            return "categoryChange"
        case .override:
            return "override"
        case .wakeFromSleep:
            return "wakeFromSleep"
        case .noSuitableRouteForCategory:
            return "noSuitableRouteForCategory"
        case .routeConfigurationChange:
            return "routeConfigurationChange"
        default:
            return "default"
        }
    }
    
    // MARK: Remote Command Event
    func addRemoteCommandEvent() {
        let commandCenter = MPRemoteCommandCenter.shared()
        commandCenter.togglePlayPauseCommand.addTarget(self, action: #selector(type(of: self).remoteTogglePlayPause(_:)))
        commandCenter.playCommand.addTarget(self, action: #selector(type(of: self).remotePlay(_:)))
        commandCenter.pauseCommand.addTarget(self, action: #selector(type(of: self).remotePause(_:)))
        commandCenter.nextTrackCommand.addTarget(self, action: #selector(type(of: self).remoteNextTrack(_:)))
        commandCenter.previousTrackCommand.addTarget(self, action: #selector(type(of: self).remotePrevTrack(_:)))
    }
    
    @objc func remoteTogglePlayPause(_ event: MPRemoteCommandEvent) {
        // イヤホンのセンターボタンを押した時の処理
        // 略

    }
    
    @objc func remotePlay(_ event: MPRemoteCommandEvent) {
        // プレイボタンが押された時の処理
        speak()

    }
    
    @objc func remotePause(_ event: MPRemoteCommandEvent) {
        // ポーズボタンが押された時の処理
        pause()
        
    }
    
    @objc func remoteNextTrack(_ event: MPRemoteCommandEvent) {
        // 「次へ」ボタンが押された時の処理
        // (略)
    }
    
    @objc func remotePrevTrack(_ event: MPRemoteCommandEvent) {
        // 「前へ」ボタンが押された時の処理
        // (略)
        
    }
    
    // MARK: - Life Cycle
    func addLifeCycleObserver() {
        let center = NotificationCenter.default
        // 今回はbackground移行を検知
        center.addObserver(self, selector: #selector(didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
    }
    
    @objc func didEnterBackground(_ notification: Notification) {
        // 音が止まっていたらAudioSessionを無効化
        if !syntherizer.isSpeaking || syntherizer.isPaused {
            deactivateAudioSession()
        }

        let app = UIApplication.shared
        app.beginBackgroundTask(expirationHandler: {
            // このblockはバックグラウンド処理に入って、所定時間後(180秒程度後)に実行される
            // (バックグラウンド再生が継続している間は実行されない)
            // ここでAudioSessionを無効化しておく
            // (この処理がないとバックグラウンド再生中にpauseしたあと、3分すぎにアプリを再開すると
            // いきなり音が鳴る症状が出てしまう)
            self.deactivateAudioSession()
            
        })
    }
    
    func deactivateAudioSession() {
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setActive(false)
        } catch {
            
        }
    }
}

「Repete」をアップデートしました(version 5.0)

iPhone/iPadアプリ「Repete」(レペテ)(旧「語学学習支援プレイヤー」)をアップデートしました。(version 5.0)

「Repete」(「語学学習支援プレイヤー」)は、語学学習の手助けをするオーディオプレイヤーです。ファイルの無音部分を分析し、流れる言葉が一区切りしたところで、あいだをおいて再生します
リピーティングに便利なオーディオプレイヤーです。

変更点

設定(待ち時間、リピート回数、シャッフル、再生速度)をすばやく変更できるようになりました。
1. 設定 > よく使う設定を登録する > 「設定変更ボタンを表示する」をONにする。
2. 「よく使う設定」に使用する設定を登録。
3. 基本画面の左下に「設定変更」ボタンが表示されています。ここから登録した設定に変更できます。


再生速度をすばやく変更したいという要望が幾度か受けていたので、Repete Plusで導入していた機能を、Repeteにも実装しました。

2014年10月のversion 1.4.2で、再生速度の変更機能を導入しました。
このときは、再生速度の変更のたびにAVPlayerItemの再作成が必要なのではと思っていました。AVPlayerItemの再作成には、0.1秒程度かかります。あまり頻繁に再生速度変更されないようにしようと考え、奥まった位置に変更機能を置きました。
その後、AVPlayerItemの再作成はバックグラウンド移行時に行うのみで良いと気づき、すばやく再生速度を変更する機能が導入できることとなりました。


語学学習を助ける「Repete」をよろしくおねがいします。

「Repete」をアップデートしました(version 4.1)

iPhone/iPadアプリ「Repete」(レペテ)(旧「語学学習支援プレイヤー」)をアップデートしました。(version 4.1)

「Repete」(「語学学習支援プレイヤー」)は、語学学習の手助けをするオーディオプレイヤーです。ファイルの無音部分を分析し、流れる言葉が一区切りしたところで、あいだをおいて再生します
リピーティングに便利なオーディオプレイヤーです。

変更点

* リピート回数が表示できるようになりました。
(設定 > 詳細 > リピート回数を表示)
(一回より多い場合表示されます)

* 再生速度を表示できるようになりました。
(設定 > 詳細 > 再生速度を表示)


語学学習を助ける「Repete」をよろしくおねがいします。

「Repete Plus」をアップデートしました。(version 5.8)

「Repete Plus」をアップデートしました。(version 5.8)

Repete Plusは、語学学習を支援するオーディオプレイヤーです。
語学教材のフレーズごとに自動的に間隔をあけて再生できます。

App Storeはこちら

使い方を紹介しているサポートサイトはこちら
Repete Plus – nackpan Blog

変更点

待ち時間を秒数指定で再生している場合は、区切りに到達すると指定した秒数を待って再生を続けます。再生速度を変更しても、指定秒数だけ待つ仕様です。待ち時間3秒ならば、再生速度が0.5でも3秒待ちます。しかし、従来のバージョンでは、バックグラウンド再生の際に秒数指定での待ち時間が適切なものになっていませんでした。待ち時間3秒指定で再生速度が0.5のとき、6秒待つことになっていました。
今回のアップデートで、その点を修正しました。


今回のアップデートでは、審査にずいぶん時間がかかりました。ここ最近は、あっさりと審査が終わっていたのでまいりました。

最初の審査

どうやったらいいかわからない。ボタン押しても何も起こらない。というコメントおよび再生関連ボタンを赤く囲ったスクリーンショットとともにメタデータリジェクト。
メタデータリジェクトとは、アプリの修正を行って再提出する必要はないが、アプリの情報・説明が足りないので、補足説明を提出する必要があるというものです。
RepeteおよびRepete Plusでは、Apple Musicが始まるずっと前のiOS 6時代のミュージックアプリと同じ要領で操作できるようにしようと考えて設計しました。その頃は、開始時点ではなにもない再生画面があり、選曲ボタンを押して選曲画面を開いて内蔵ライブラリから曲を選ぶというかたちは一般的なものでした。
音楽や映像のストリーミングが一般的になった現在、再生アプリの最初の画面では、楽曲あるいは映像のサムネイルが並び、そこから選んで再生するのが一般的です。
Repete Plusでは、初回は再生ボタンを押してもなにも起こりません。昔なら、そこから選曲ボタンを探して押してくれたかもしれません。しかし、いまとなってはそっけなさすぎです。
そこで、選曲せずに再生ボタンを押した場合は、「選曲ボタンを押して、オーディオファイルを選んでください」とメッセージを追加しました。
修正を行ったので再提出。

二度目の審査

しばらく間があって、再び審査が行われました。
どうやったらいいかわからない、そして、動画デモをつけてくださいというコメント。
はい。わかりました。
操作方法を示す動画デモを作成開始しました。
Premiere Elementsを急遽購入して作成。
テロップ作成・位置調整がやりやすそうと考え、奮発しました。
動画作成完了。
しかし、取り込んだiPhone動画部分の前後に乱れが生じました。選曲画面を開いている場面のはずが一瞬閉じていたり、プログレスバーが痙攣していたり。
原因究明は時間がかかりそうなので、iMovieで作成し直し、送信しました。
今回の用途ではiMovieで十二分に間に合いました。

審査のコメントで、他にSiriショートカットについて記述がありました。
Siriショートカットを使用しているようだけども、アプリ内に「Siriに追加」ボタンはないの?とのこと。
以下の回答を送りました。
Siriショートカットは、iPhoneの設定 > Siriと検索 > Repete Plus > ショートカットから登録してもらっています。アプリ内に「Siriに追加」ボタンはありません。
これで通るのか、それともアプリ内に「Siriに追加」ボタンが必須なのかはわからないのですが、ひとまず現状を報告しました。

三度目の審査

あっさりと審査通過しました。
「Repeteと統合しなさい」とか「Siriに追加」ボタン追加しなきゃダメといわれるかとおそれていたので、すっと通ってほっとしました。


語学学習を助ける「Repete Plus」をよろしくおねがいします。