Repete(Android版)開発をふりかえる

2024年12月にリリースしたRepete(Android版)の開発を振り返りたいと思います。

ものすごく時間がかかりました。
2年。

かかりすぎ。

1年程度でできる想定でしたので、かかりすぎです。つらい。

当初は、Flutterの勉強がてら、オーディオプレイヤーをまずは作成していこう。という感じでスタートしました。
RepeteのAndroid版をリリースするかどうかは決めておらず、まずは、馴染みのあるオーディオプレイヤーをクロスプラットフォーム開発してみようという感じです。

2022年10月

Flutterを開始。
リアクティブプログラミングは、馴染みないので、勉強しながら進めます。

こちらのチュートリアルとサンプルプロジェクトで、オーディオプレイヤーを作成。
just_audioパッケージを採用。
端末内のファイルやインターネット上のURLが示すファイルの再生に成功。
バックグラウンド再生に成功。
バックグラウンド再生にあたっては、just_audioの作者が開発したaudio_serviceパッケージを採用。

2022年11月

端末内のファイルを一覧表示して選曲する機能を開発。
そのさい、Android Developerの記事を参考。
Kotlinで、Android側の実装を行いました。

共有ストレージからメディア ファイルにアクセスする | Android デベロッパー

また、MethodChannelなど、FlutterからAndroidの機能へアクセスする機能を学びました。

2022年12月

選曲用画面

選曲用の画面の作成。
デバイス内のファイルを取得、アルバムごとに表示。
階層を持った画面の作成。

データの永続化

Flutterでデバイスにデータを保存するにはどうすれば良いのか調査。
Persist data with SQLite
で、FlutterのドキュメントのCookbookにある記事で、sqfliteを知りました。
データ保存を実験するアプリを試作。

2023年1月

画面遷移

画面遷移について、調べました。

Navigation and routing

Navigator.push()とpop()を知って、それを使用していました。
その後、タブがある画面で、タブAの深い階層のページからタブBのページに移るケースで簡潔に書けるやりかたを調べていました。
そのさい、あらためてこのページを見て、後半に記されている、go_routerについて学びました。

2023年2月

iOSでのオーディオファイル再生でうまくいっていない箇所があったのを修正しました。
原因を見つけるのに時間がかかりました。
iOSでもAndroidでもオーディオファイル再生ができるようになりました。

ダークモード

iOSでのダークモード、Androidでのダークテーマに対応しました

ローカライズ

多言語処理について調べました。言語追加できるように改造しました。

2023年3月

2つの教材を交互に使用している場合に、すばやく切り替えられたらよいなと思っていました。
そこで、現在使用しているアイテムと前回使用したアイテムをすばやく切り替えられる機能を作成。
画面上部にタブを配置。タブ切り替えでアイテム切り替え。タブは、2つではなく、履歴タブのような形としました。

最後にポーズした箇所から再開する機能を実装。

データ構造を変更。

2023年4月

ホーム画面で、ジェスチャー(タップ、タッチ)で再生状況を切りかえる機能の実装開始。
画面にタッチしている間は再生速度0.8で、離すと1.0に戻る機能を実験。
タップではなく、タッチでできればと考えていた機能を実際にやってみた。良いかんじ。

「設定」画面と「設定」データについて、詳細を詰めていきました。
「設定」データの構造を修正。

設定画面では、ユーザーがジェスチャー用の動作割り当てをするための画面を作成。
Flutterで作成するにあたり複数のレンジをもつスライダーがないかと探しました。

flutter_multi_slider

こちらのパッケージを使用。

ジェスチャーの範囲設定にあたり、スライダーの下に範囲を示す枠を設置。

(ただし、この機能は、リリースしたRepete(Android版)には搭載しませんでした)

2023年5月

ここしばらく、Androidでのテストしかしていなかったので、iPadで実機テスト。
起動できずエラー。
ライブラリ、Flutterアップデート。info.plistを修正。
動くようになりました

iOSでのデータベースの処理を修正
ようやく、iOSでも、前回使用したアイテムがあるときは、そこから開始できるようになりました。

ループ処理。
just_audioでのループ処理を調べました。
曲単位で回数指定をしてループできるようにしました。

2023年6月

再生回数機能を実装

AndroidでMediaStoreのアイテムを集める際に、アルバム情報がないなど一部情報がない時にエラーが発生していたのを修正

Androidでは、端末のメディアファイルを収集してライブラリ画面に表示する形でした。
ただ、これでは端末にメディアファイルがない状態だと、ライブラリ画面を開いても、なにも表示されてない状態になります。ユーザーは、何をすれば良いのか戸惑ってしまいます。
そこで、ファイルピッカーとファイルダウンロード機能を実装しました。

プレイヤーのライブラリ画面を開いたのち、ファイルアプリやGoogle Driveに移りファイルをダウンロード。それから、ライブラリ画面に戻ってきた際に、追加や削除されたファイルを反映させる機能を実装。

2023年7月

起動時に表示されるアイテムと再生されるアイテムが異なっていることがある、というエラーが発生。
そんな基本的な動作でエラーが発覚したので、おののきました。
playbackStateの変化で、completedの判定が適切ではありませんでした。修正しました。

playbackStateの変化の監視部分の記述が、ごちゃごちゃしてきたので、整理。
簡潔になりました。

2023年8月

Shuffle機能を実装しようとして大苦戦。
もともと参考にしていた基本的なオーディオプレイヤー作成のチュートリアルで、シャッフル機能も組み込まれていたので、するっといけると思っていたものの、おかしな動作となってしまう。

シャッフル部分のソースコードを読み解こうとするも、理解が進まない。
参考にしていた記事を改めて見直すと、シャッフル部分が修正されていた。
ただ、その記事のシャッフル部分の記述は理解しづらかったので、あらためて自分にとって理解しやすい手法に書き直した。

再生時間を閲覧できる「統計」画面を作成中。

2023年9月

再生時間・再生回数をファイルごとに閲覧できる「統計」画面を作成。
すこしずつデータを読み込むことで、画面を開く際に、待ち時間を作らないようにしました。

作成中のオーディオプレイヤーで実装しようと考えていた機能は以下の通りです。
* 手持ち音源の再生(速度変更・シャッフルができるようにする)
* 再生したファイルの再生時間や再生回数を表示する
* 速度変更などの設定変更するさいに、適用対象を全体・フォルダ・ファイルなど階層ごとに指定できる

さっと作ってさっと出す予定でしたが、作り始めてから1年近く経ってしまいました。
すでに作成したオーディオアプリであるRepeteを「英語のハノン」など語学学習用に使っているので、それと分けて朗読などの音源を再生するプレイヤーを作成しようと考えていました。
なかなか完成しないこの一年の間にAudibleを利用するようになり、小説・ノンフィクションなどを聴くようになりました。
そのため、手持ち音源を聴く機会がほとんどなくなってしまいました。
手持ち音源用オーディオプレイヤーの需要はあるのかなと思うようになりました。

アプリ開発の方針に迷いが生じました。
まず、「手持ち音源用オーディオプレイヤー」を出す。そして、それをベースにしてAndroid用のRepeteを出す、という心づもりでいました。
が、まずAndroid用の「Repete」を出す、という方針に変更しました。

「Repete」を出すとなると、「ファイルから波形情報を取得する」機能の実装が必要です。
以前、Android開発に取り掛かった際に、MediaCodecとMediaExtractorを使ってwaveファイルから波形情報を分析する、という実験には成功していました。
しかし、実用にすぐ用いることができるとはいえない段階です。
自ら実装するとなると相当学習が必要なので、Flutterのpackageがあればそちらを採用したいと考えました。

「ファイルから波形情報を引き出す」機能について調べると、以下の2種のpackageを見つけました。

audio_waveforms: ^1.0.4

just_waveform: ^0.0.5

audio_waveformsのほうは、録音時に波形を作成できるなど多機能。
just_waveformのほうは、作成中のオーディオプレイヤーでも使用しているjust_audioの作者によるもの。ファイルから波形を取り出すのに特化。

実験したところ、今回の目的には、just_waveformが良さそう、と判断。
just_waveformを採用しました。

2023年10月

Android版の「Repete」を出すという方針を、先月(9月)に定めました。
波形を分析して、無音箇所を分析し、区間に分割。
それぞれの区間を示すSection itemを作成。
Section itemの再生後、指定した待ち時間を待機した後、次のsection itemを再生する機能を実装。
sectionでのリピートも実装。
出来上がりが見えてきたという感触を得ました。
が、
10月最終週で、オーディオファイルの一部で、波形分析がうまくいかないことが判明。
just_waveformそのままでは、うまくいかないことから、別のパッケージを実験。
いろいろと学ぶことはあったものの、今回の無音箇所検出には適合しないことがわかりました。
そこで、MediaCodecとMedicExtractorを使用して、Android用の波形分析機能を自作。

また、10/25から、ChatGPT 4を入れて、プログラミングの疑問を尋ねることを始めました。
Dartのコードを元に、Kotlinではどう書くのか尋ねたり。

2023年11月

開発環境をAndroid Studioから、VS Codeに変更しました。

先月(10月)は、できあがりも間近という感覚がありました。
10月後半に、波形分析部分をやり直すことになったものの、そこを越えれば完成まで間近だ、などと思っていたのです。
しかし、作業を進めていくと、「設定」関連が、まだまだできていないということに気づきました。
ダミー設定を置いて開発を進めていたので、あとはちょっと実際の状況に合わせて手を入れれば完成、などと思っていました。
実際に「設定」部分を見てみると、まだまだやることがありました。
今回は、一般・フォルダ・選曲グループ・個別と4つのスコープで「設定」を置けるようにしています。
それらの管理部分の実装を進めていきました。やってみると大変。

(リリースしたRepete(Android版)では、一般・フォルダ・選曲グループ・個別と4つのスコープ方式は、取りやめました。2024年5月に、複雑すぎてユーザーにわかりずらい、あと、自分でもわかりづらい、ということで取りやめています。)

2023年12月

今月(12月)で、できあがりまで持っていきたかったものの、まだできず。
年越しとなってしまいました。

役割が増えすぎて、触るのが難しくなりかけていたクラスの設計を変更。
オーディオ再生部分と再生アイテム管理部分を同じクラスで扱っていたのを分離。
区間再生部分を別のクラスへ。
など。

ファイルでの再生、区間再生、どちらもきちんと作動するようになりました。
リピート、待ち時間、きちんと動作。
あとすこし。
のはず。

2024年1月

1/14(日)の朝、いつも使っているMacBook Air 2020に電源を入れても反応しませんでした。

あわてて修理受付に持ち込みました。修理には、一週間以上はかかるとのこと。
新たなMacを購入して、開発再開。
UIの実験用に作成したプロジェクトなど、一部のコードはバックアップをとっていなかったので、やり直しが必要となってしまいました。
また、Flutterプロジェクトとは関係ないものの、一部の表計算データや、作成した画像などが消失してしまい、がっくり。
修理に出したMacが、返ってきたのは、10日間ほど経ってから。データは復旧できず。残念。

気を取り直して再開。
新たなMacでは、書類をクラウドにバックアップするよう変更。

2024年2月

いまだ、できあがらず。

区間再生部分を作成。
設定画面のUI。widgetを作成、動作を検証し、カラーリングを調整。
設定について、「全体設定」「フォルダ設定」「選曲グループ設定」「曲設定」と、スコープの広いものからスコープの狭いものを上書きする機構を作成。

2024年3月-8月開発状況

3月

  • 録音機能作成。
  • クイック再生コントロール作成。

4月

  • クイック再生コントロール部分修正。

5月

  • 「設定」のつくりの変更

「設定」について。
リピート回数や再生速度、無音検出時間など「設定」を、各教材ごとに持てるようにしたいと考えていました。
例えば、
「ロシア語単語帳」で、無音検出時間:0.3秒、再生速度:0.9、リピート2。
「英語短文練習」で、無音検出時間:0.76秒、再生速度:1.0, リピート3
といったぐあいです。

それを実現するために、階層ごとにsettingsを持たせる方式をとりました。
全般>フォルダ>選曲グループ(選曲タブ)
と階層ごとに「設定」を持たせ、実際に使用する際には、狭いスコープを優先して合成した設定を用いるというもの。
Webページのスタイルを設定するCSSで用いられている方式と同様のものです。

しかし、「設定」画面のUIが複雑化して、使いづらいものとなってしまいました。
下の図は、開発時の「設定」画面のスクリーンショットです。画面上部に各階層を示すタブがあり、その下に設定項目が並んでいます。


使ってみると、適用される設定値を把握しづらいのをひしひしと感じました。
これでは、困惑を招くと思いながら、どうにかわかりやすくなるように、微調整を重ねていました。

5月になって、方式を変更することとしました。

「ライブラリ」画面を開く操作の際に、タブごとにそれぞれの前回開いた箇所を開くようにしていたのですが、それと同様にすれば、教材ごとの設定、という目的は達成できることに気づいたからです。

各タブごとに設定を持ち、あるタブから「ライブラリ」画面を開き選曲した場合、そのタブの設定を引き継ぐ、という形にしました。
これで、上記で記した
「ロシア語単語帳」で、無音検出時間:0.3秒、再生速度:0.9、リピート2。
「英語短文練習」で、無音検出時間:0.76秒、再生速度:1.0, リピート3
のように、教材ごとに設定を持たせることが実現できました。

こちらの方式にすると、「設定」画面から、
全般>フォルダ>選曲グループ(選曲タブ)
のタブがなくなり、「有効値」かどうかを気にかける必要もなくなり、ぐっとシンプルになりました。

非常に大きな変更となりました。

そのほか。

区間再生部分を作成。

設定画面のUI。widgetを作成、動作を検証し、カラーリングを調整。

一部のファイルで、区切り位置が0.6秒ほど適切な位置からズレる問題について。
https://github.com/google/ExoPlayer/issues/9408
just_audioの作者も、ExoPlayerについて、mp3ファイルでのseekについて問い合わせてしていました。
このもんだいについては、「設定」画面で、ずれを調整する項目を組み込むこととしました。

6月

Google Driveなどクラウドストレージからのファイル読み込み機能実装

ローカライズの処理変更

just_audioから自前でAndroidでのオーディオプレイヤー実装に切り替え

just_audioから自前でのオーディオプレイヤー実装に切り替え。
有音部分を再生して、区間末尾に達したならpauseするという形式でやってきました。
しかし、それだと、バックグラウンド再生の時に表示される再生用コントロールで、再生・ポーズが頻繁に切り替わってしまう問題がありました。
「有音部でクリップしたオーディオファイル」+「無音」で、ひとつのアイテムとして再生する方式を取れればよいのだが、と思っていました。
Androidでのオーディオ再生について、調べ直しました。

Media3, ExoPlayer 3の調査。
Media 3では、バックグラウンド再生がわかりやすく、やりやすくなっていました。

Jetpack Media3 の概要

ExoPlayer 3で、オーディオファイルのクリップ、無音作成、作成したリソースを繋いでひとつのリソースとしてあつかうことができました。

高度なメディアソースの構成

今回は、「クリップされた有音区間+無音」をひとまとまりのアイテムとする特殊なオーディオプレイヤーなので、just_audioではなく、自前実装でオーディオプレイヤーを作成ということにしました。

Developing packages & plugins

Flutter plugin packageとして作成。

7月

  • 単体アイテムプレイヤーからプレイリストを持つプレイヤーに変更

プレイヤーは、6月の時点では単体のアイテムを持ち、再生終了の際に別のアイテムに切り替える形をとっていたが、プレイリストをもつ形に変更。
ExoPlayerのドキュメントに示される、素直な実装になっていきました。

8月

  • ホーム画面のタブ表示修正
  • クイック再生コントロール修正
  • ダークモード修正
  • 端末のファイルを読み込む処理を改善

端末のファイルを読み込む処理を改善。
この部分は、ごく初期に実装していたもの。
AndroidからFlutterへデータを渡す際、JSON化しないといけないと思っていたため、Kotlin serializationなどのプラグインを使用していたが、見直すと不必要だと分かったので、修正。
プラグインを使用せずに良くなったので、すっきりしました。

  • 早送り・早戻しのクイック再生コントロールへの追加

8月末での状況

「設定」の作りの変更と、オーディオプレイヤー部分の見直しで、大幅に修正を行いました。
ずいぶんとすっきりしたのは良いのですが、いまだ、完成せず。
恐ろしくておののくほどの時間のかかりっぷり。

2024年9月

オーディオプレイヤー、現在位置計算手法を修正。

2024年10月

オーディオプレイヤー、全体リピートでの挙動に少しおかしなところがあり、修正。
バックグラウンド再生を考慮すると、再生時間検出に不備があったので修正。
ローカライズの文言修正。
ExoPlayerの仕様で、ファイルによっては再生位置に0.6秒程度のズレが生じる。
区間アイテムにoffsetを設定できるようにして対応。
設定画面に、「再生ずれ修正」「録音機能On/Off」の追加。
クイック再生コントロールのコマンドスイートの保存におかしなところがあったので修正。
スマホなどのサイズでは、ホーム画面を縦型表示に限定。

2024年11月

待ち時間が秒数指定の時に、不具合があったので修正。

データサイズが小さくなるよう保存用データ構造を見直し。

再生速度の変更のさい、たとえば+0.1とするさいに、複数の再生速度があった場合、そのすべての要素に+0.1していたが、使用してみるとしっくりきませんでした。現在再生中の速度のみを変更、今回なら、現在再生中の速度に+0.1するように仕様を変更。

設定画面に、Webサイトへのリンクや、アプリバージョンの表示などを追加。

端末内にオーディオファイルがない時の案内を追加。

ライブラリ画面の「ダウンロード」ボタンからの、ファイルダウンロードに不具合が生じていたので修正

リリースモードで稀にMediaControllerの初期化が失敗する不具合が判明したので修正。

そのほか、細々とした箇所を修正。

ついに完成。

11月末、スクリーンショットなどGoogle Play登録用の素材作成中。

2024年12月

2024年12月、「Repete」のAndroid版をリリースしました。


とにかく、時間がかかってしまった。という感慨ばかりがあります。

どうにかリリースできたので、「Repete(Android版)」が、皆様にひろく使われるアプリに成長することを願い、また、成長させるための活動を続けていきたいと思います。

コメントする

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