🚧 Kotlin Multiplatform（KMP）でVJソフトを開発しようとして失敗した話　その１

# 🚧 Kotlin Multiplatform（KMP）でVJソフトを開発しようとして失敗した話

## 📌 はじめに

映像と音楽を同期させてリアルタイムで操作できる **VJソフト** を **Kotlin Multiplatform（KMP）** を使って開発しようとしました。しかし、**プラットフォームごとの制約やライブラリの互換性** などの問題に直面し、最終的には開発を断念することになりました。

本記事では、**どのような構成を目指し、どのような課題に直面し、どう解決しようとしたか** を詳しく解説します。

---

## **🎯 なぜKMPを使おうとしたのか？**
VJソフトは音楽と映像をリアルタイムで同期させるアプリのため、**Android/iOS/Desktopで共通のロジックを持たせる** ことが理想でした。KMPを採用することで、以下のメリットを期待しました。

### **✅ 期待したメリット**
- **ビジネスロジック（音楽のBPM解析、映像シンクロ）を共通化**
- **UIは各プラットフォームで独自実装するが、データ処理は統一**
- **1つのコードベースで管理し、メンテナンス性向上**

しかし、実際には以下の課題に直面し、KMPを適用するのが困難になりました。

---

## **🎯 VJソフトに必要な機能とKMPの課題**

### **🔹 必須機能**
- **音楽・映像の再生・停止・シーク**
- **音楽のBPMに合わせた映像エフェクトの適用**
- **複数の映像・音声の同期とミックス**

### **🔸 KMPでの課題**
- **プラットフォームごとにメディア再生APIが異なり、統一が難しい**
- **メタデータ取得方法がOSごとに異なる**
- **JavaFXがAndroid/iOSと競合し、KMPの統合が困難**
- **依存関係の整理が難しく、Clean Architectureを維持しにくい**

---

## **❌ 躓いたポイント**

### **① KMPで音楽・映像の再生を統一するのが難しい**
#### **発生した問題**
- **Android** → ExoPlayerはシークやピッチ調整のカスタマイズが必要
- **iOS** → AVFoundationはObjective-C/Swiftの依存が強く、KMPでの連携が難しい
- **Desktop** → JavaFX Media APIはリアルタイム処理に向かない
- **FFmpeg/GStreamer** → KMPの公式ラッパーが未成熟で、各OSごとにNativeライブラリが必要

➡ **結果：KMPでの完全統一を断念し、各プラットフォームごとの実装に切り替え**

```
📦 project-root
 ┣ 📂 composeApp        # アプリのエントリポイント（UIの管理）
 ┃ ┣ 📂 commonMain    # アプリの初期化処理
 ┃ ┣ 📂 androidMain   # Android用のUIと実装
 ┃ ┣ 📂 iosMain       # iOS用のUIと実装（後から対応予定）
 ┃ ┗ 📂 desktopMain   # Desktop用のUIと実装（JavaFXを使用）
 ┣ 📂 sharedModule      # KMPで共通化するモジュール
 ┃ ┣ 📂 data           # データの管理（ファイルの読み込み、メタデータ取得）
 ┃ ┣ 📂 domain         # ビジネスロジック（UseCaseなど）
 ┃ ┣ 📂 di             # 依存関係の管理
 ┃ ┗ 📂 presenter      # ViewModelの管理
 ┃ ┃ ┣ 📂 commonMain    # Android & iOSで共通のViewModel
 ┃ ┃ ┣ 📂 androidMain   # Android固有のViewModel
 ┃ ┃ ┣ 📂 iosMain       # iOS固有のViewModel
 ┃ ┃ ┗ 📂 desktopMain   # Desktop固有のViewModel
 ┣ 📂 data_desktop      # Desktop向けのファイル処理（JavaFXを利用）
```

### **ポイント**
- **Android/iOS** は **共通UI（Jetpack Compose Multiplatform）** を採用。
- **Desktop（JavaFX）** は **別モジュール（data_desktop）** に分離。
- **ビジネスロジック（domain）** は **OS非依存** にする設計。
- **データ管理（data）** は **OSごとの違いに対応するため、ファイル取得やメタデータ取得をプラットフォーム別に実装**。
- **DI（di）** は **依存関係を管理し、将来的に Desktop の依存関係を切り分けられるよう設計**。

👉 **この構成により、Android/iOS で共通の UI を使いながら、Desktop では JavaFX を使う柔軟な設計を目指しました。**


---

### **② JavaFXがAndroid/iOSと競合する**
#### **発生した問題**
- `Unresolved reference: javafx`
- `The 'java' plugin has been applied, but it is not compatible with the Android plugins.`

#### **試したこと**
- ✅ gradle.properties にJavaFXの設定 → **Gradle Daemon起動エラー**
- ✅ `compose.desktop.applicationDefaultJvmArgs` に `--module-path` を設定 → **プラグイン競合エラー**
- ✅ **Desktop専用の`data_desktop` モジュールを作成** → **KMPでの統一は断念**

➡ **結果：Android/iOSとDesktopを同じサブモジュールで扱うのを断念し、Desktop専用モジュールを作成。**

---

### **③ KMPの依存関係の整理が難しい**
#### **発生した問題**
- **data モジュールが domain のクラスを参照すると依存関係が逆転**
- **repository を data に置くと domain に依存し、クリーンアーキテクチャが崩れる**

#### **具体的な影響**
- **音楽ファイルの取得ロジックが複雑化**
- **BPM解析や映像のシーク処理をdomain層で統一できなくなった**
- **結果としてKMPのメリット（共通化）が減少**

#### **解決策**
✅ **DTOを導入して、data層がdomainに依存しない設計に変更**  
✅ **Repositoryのinterfaceをdomainではなくdata側に配置**  

```kotlin
// data 層の DTO
data class MediaFileDto(val uri: String, val title: String, val duration: Long)

// domain 層のモデル
data class MediaFile(val uri: String, val title: String, val duration: Long)

// data 層の Repository
interface MediaRepository {
    fun getMediaFile(uri: String): MediaFileDto
}

class MediaRepositoryImpl(private val dataSource: MediaFileDataSource) : MediaRepository {
    override fun getMediaFile(uri: String): MediaFileDto {
        return dataSource.loadMediaFile(uri)
    }
}
```
### 但し上述の解決策には下記のデメリットも存在します。
**・リアルタイム性が求められるVJソフト では、音楽のBPMと映像を同期させるために、フレーム単位でデータ変換が必要です**
**・音楽メタデータの取得・変換 も、プラットフォームごとに異なる処理が走るため、毎回DTOを経由するのが負担になります**
**・こうした要素が積み重なり、パフォーマンスのボトルネックになる可能性があります**

## 結論
**・KMPでVJソフトを開発しようとしたが、現時点では難易度が高い**
**・音楽・映像の再生はプラットフォームごとに対応する必要がある**
**・JavaFXの統合が困難で、DesktopはKMPを諦める選択に**
**・依存関係の整理が難しく、開発コストが増加**
👉 KMPは便利だが、VJソフトのようなマルチメディアアプリには課題が多いと実感した。

## 🔍 次回予告：低レイヤーまで考慮したマルチプラットフォーム開発の課題

今回の記事では、KMPを活用したVJソフト開発の課題について解説しました。しかし、そもそも **ソフトウェアが実際にハードウェア上でどのように動作するのか** を理解することが、より適切な開発手法を選択する上で重要です。

次回は、**ソフトウェア開発者が実装したコードがAPI LayerからSystem Library、Kernelを経てハードウェアに実行される仕組み** を踏まえ、マルチプラットフォーム開発におけるアプローチを探ります。