DeployGate経由でのダウンロードをお願い致します。
https://dply.me/lkjcwc
-
マイクを使用した録音
-
「録音時の移動経路」を記録
-
表示
- 録音データの一覧表示
- 再生状態の表示
- Notificationを用いた、リアルタイムでの再生状態通知
- 移動経路の表示
- ダークモードへの対応
-
データの操作
- 録音デーの保存(「録音タイトル」「録音時間」「保存日時」をSQLデータベースに保管)
- 録音データ削除(「一括削除」「指定されたデータの削除」に対応)
-
再生操作
- 任意の音源データ再生(バックグラウンド再生や連続再生にも対応)
- 通知バーでの再生音源データ切り替え
- Bluetooth機器(イヤホンやリモコン等)からの再生操作受付
- ボイスコマンドへの対応("OK google、次の曲を再生して"等)
-
他音声アプリとの協調動作
- 他アプリから音声が流れる場合は、再生をストップ
-
スマートウォッチ(Wear OS)への完全対応
- Wear OS上から再生操作や選曲が可能
- 再生データの切替に応じて、録音データ一覧(RecyclerView)の各行にもリアルタイムで再生状態が反映されます。
メイン画面 | 通知バー | 録音開始~終了 |
---|---|---|
![]() |
![]() |
![]() |
データ削除(ダークモード) | Wear OSへの完全対応 | 移動経路の表示 |
---|---|---|
![]() |
![]() |
![]() |
-
jetpack Library
- Room Database (SQLite)
- Data Biding & Binding Adapter
- DataStore
- Shimmer Recycler View
- Navigation
-
音源再生
- Media Session / Media Browser Serivce / Media Controller / Media Browser
- Exo Player
-
録音
- Media Recorder
-
マルチスレッド・非同期処理
- Kotlin Coroutines & Flows(Flow、SharedFlow、StateFlow)
-
Dependency Injection
- Dagger Hilt
-
ネットワーク通信
-
Retrofit2
-
Okhttp3
-
Gson
- Maps SDK for Android
アプリ内での地図表示に使用 - Roads API Snap to Roads
移動経路情報の補正 (生の位置情報を実在の道路上にスナップ) に使用
経路情報補正イメージ |
---|
Android上でメディアプレーヤーを実装する場合は、再生を担うコンポーネントを、「再生操作を要求する部分」と「要求を受け取り実際に再生操作を行う部分」に分離して、クライアント/サーバー型設計を採用することが推奨されています。
加えて、上記の設計をサポートするクラス(MediaSessionやMediaController等)がGoogleから提供されおり、当アプリでは、これら標準クラスを積極的に活用しています。
これにより、Android端末外部(スマートウォッチやBluetoothイヤホン等)からの操作要求についても、特別な処理を書かずに、対応させることができています。
DIやインターフェースを積極的に活用することで、クラス同士の相互依存・循環依存を解消させ、Model層からServiceクラスに向かった単一方向の依存関係を構築しました。これにより、テスタブル(テスト用オブジェクトに容易に差し替えられる)で、仕様変更に強い(仕様変更による影響範囲が安易に特定できる)アプリ設計を実現しています。
設計の初期段階では、Serviceクラスが依存しているオブジェクトを、"Serviceクラス内部"で生成していたため、クラス同士が密結合状態となっていました。このため、例えば、「Serviceクラスの挙動を確認するために、Serviceクラスの依存先をテスト用のクラスに差し替える」といったテクニックが使えない状態にありました。
そこで、DIパターンを用いて密結合状態の解消を試みました。しかしながら、Serviceクラス内部の実態は、「循環参照・相互参照が存在する依存関係のスパゲッティ状態」となっており、この状態では「依存オブジェクトを外部から注入する」ことができず、Google側が提供しているクラスをそのまま利用するような形では、密結合状態を解消できません。
こういった背景から、このアプリでは、既存の再生コンポーネント群(ExoPlayerやMediaSession等)を一つの集合として捉え、「PlaybackComponentインターフェース」として再定義しました。
加えて、再生コンポーネント群の実行状態(再生中・停止中・何番目の音源を再生中等)を、ComponentStateクラスとして別クラスに抜き出し、Observableとして再定義することで、単一方向の依存関係を構築しました。
これにより、複数存在したServiceクラスの依存先は一つになり、かつ抽象(インターフェース)への依存も実現できる形となりました。
上記の依存関係を構築することにより、 "具象クラス(実際に再生を担うクラス群)の変更が、Serviceクラスに直接影響を与えない" すなわち、テスタブルで(テスト用オブジェクトに容易に差し替えられる)、仕様変更に強い(仕様変更による影響範囲が安易に特定できる)クラス設計を実現させています。
このアプリでは、動的に表示状態が変化するRecyclerViewを実装しています。
具体的な制御の流れは、下記の通りです。
- 再生状態の変化をAdapterが検知(再生音源データが切り替わった・再生が停止した等)
- RecyclerViewが保持しているViewHolder全てに対して、最新の状態を反映
このロジックを適用することにより、例えば、「RecyclerView内に表示されているViewの中で、現在再生中の音源を示すViewに対してのみ、停止ボタンを表示させる」といったことが可能になっています。
しかしながら、このロジックのみでは、画面の上下スクロールに伴う再バインディグによってViewHolderの表示状態が初期化されてしまうため、onBindViewHolder()のタイミングでも、"その時点での再生状態" を反映させる処理を追加しています。
なお、上記処理は、生成されたViewHolderに対してのみ更新処理を行うため、例えば、表示件数が数万件を超える場合でも、ユーザーに見えるViewに対してのみ更新処理をかけることとなります。
これはすなわち、RecyclerViewの特徴である「ViewHolderの使い回し」によるメリット(必要最低限のリソースで大量の表示を実現させる)を失わずに、動的処理を実現できているとうことでもあります。