|
| 1 | +# spec-005: Integrate MediaPipe + Flutter (Hand Landmarker) |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Integrate MediaPipe Hand Landmarker into the Teman Isyarat Flutter app |
| 6 | +using a native Android `PlatformView` for real-time camera + hand |
| 7 | +tracking, bridged via `AndroidView` widget in Flutter. |
| 8 | + |
| 9 | +## Architecture |
| 10 | + |
| 11 | +``` |
| 12 | +Flutter (Dart) |
| 13 | + ┌───────────────┐ ┌──────────────────────────┐ |
| 14 | + │ TranslatePage │ │ AndroidView │ |
| 15 | + │ (Dart UI) │──▶│ (PlatformView) │ |
| 16 | + │ + result box │ │ ┌────────────────────┐ │ |
| 17 | + │ + nav │ │ │ MethodChannel │ │ |
| 18 | + └───────────────┘ │ └──────┬─────────────┘ │ |
| 19 | + └────────┼─────────────────┘ |
| 20 | + │ |
| 21 | +Native Android (Kotlin) │ |
| 22 | + ┌────────────────────────────▼──────────────────┐ |
| 23 | + │ HandLandmarkerView (PlatformView) │ |
| 24 | + │ ┌────────────────┐ ┌────────────────────┐ │ |
| 25 | + │ │ CameraX │ │ HandLandmarker │ │ |
| 26 | + │ │ (PreviewView) │ │ OverlayView │ │ |
| 27 | + │ └───────┬────────┘ │ (canvas skeleton) │ │ |
| 28 | + │ │ └────────────────────┘ │ |
| 29 | + │ ┌───────▼──────────────────────────────────┐ │ |
| 30 | + │ │ HandLandmarkerHelper (MediaPipe Tasks) │ │ |
| 31 | + │ │ - init / setup │ │ |
| 32 | + │ │ - detectLiveStream (LIVE_STREAM mode) │ │ |
| 33 | + │ │ - callback -> onResults / onError │ │ |
| 34 | + │ └──────────────────────────────────────────┘ │ |
| 35 | + │ ┌──────────────────────────────────────────┐ │ |
| 36 | + │ │ GestureClassifier (future) │ │ |
| 37 | + │ │ - receives HandLandmarkerResult │ │ |
| 38 | + │ │ - returns gesture label + confidence │ │ |
| 39 | + │ └──────────────────────────────────────────┘ │ |
| 40 | + └───────────────────────────────────────────────┘ |
| 41 | +``` |
| 42 | + |
| 43 | +## Files to Create |
| 44 | + |
| 45 | +| # | File | Purpose | |
| 46 | +| --- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | |
| 47 | +| 1 | `android/app/src/main/java/com/example/android/handlandmarker/HandLandmarkerHelper.kt` | MediaPipe Tasks wrapper - init, config, detectLiveStream, result/error listeners | |
| 48 | +| 2 | `android/app/src/main/java/com/example/android/handlandmarker/HandLandmarkerView.kt` | PlatformView - CameraX + PreviewView + overlay + lifecycle | |
| 49 | +| 3 | `android/app/src/main/java/com/example/android/handlandmarker/HandLandmarkerOverlay.kt` | Custom View - draws hand skeleton (21 landmarks + HAND_CONNECTIONS) | |
| 50 | +| 4 | `android/app/src/main/java/com/example/android/handlandmarker/HandLandmarkerPlugin.kt` | PlatformViewFactory + MethodChannel handler | |
| 51 | +| 5 | `lib/pages/translate_page.dart` | TranslatePage refactored out of main.dart, uses AndroidView | |
| 52 | + |
| 53 | +## Files to Modify |
| 54 | + |
| 55 | +| # | File | Changes | |
| 56 | +|---|------|---------| |
| 57 | +| 1 | `android/app/build.gradle.kts` | Add `com.google.mediapipe:tasks-vision:0.10.29`, CameraX deps, set `minSdk = 24` | |
| 58 | +| 2 | `android/app/src/main/AndroidManifest.xml` | Add `<uses-permission android:name="android.permission.CAMERA" />` | |
| 59 | +| 3 | `lib/main.dart` | Replace TranslatePage placeholder with real AndroidView widget | |
| 60 | + |
| 61 | +## Assets |
| 62 | + |
| 63 | +| # | File | Source | |
| 64 | +|---|------|--------| |
| 65 | +| 1 | `android/app/src/main/assets/hand_landmarker.task` | `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task` | |
| 66 | + |
| 67 | +## MethodChannel API |
| 68 | + |
| 69 | +### Dart -> Native |
| 70 | + |
| 71 | +| Call | Args | Returns | Notes | |
| 72 | +|------|------|---------|-------| |
| 73 | +| `startCamera` | - | `bool` | Initializes CameraX + MediaPipe | |
| 74 | +| `stopCamera` | - | `bool` | Releases resources | |
| 75 | +| `switchCamera` | - | `bool` | Front <-> back | |
| 76 | +| `updateSettings` | `{maxHands: int, detectionConfidence: float, trackingConfidence: float, delegate: int}` | `bool` | 0=CPU, 1=GPU | |
| 77 | + |
| 78 | +### Native -> Dart (via callback channel) |
| 79 | + |
| 80 | +| Event | Data | Description | |
| 81 | +|-------|------|-------------| |
| 82 | +| `onGestureResult` | `{gesture: String, confidence: float}` | Gesture classification (future) | |
| 83 | +| `onError` | `{message: String}` | Error reporting | |
| 84 | +| `onLandmarks` | `{landmarks: [[x,y,z]]}` | Raw landmark positions | |
| 85 | + |
| 86 | +## Gradle Dependencies |
| 87 | + |
| 88 | +```kotlin |
| 89 | +// MediaPipe Tasks Vision |
| 90 | +implementation("com.google.mediapipe:tasks-vision:0.10.29") |
| 91 | + |
| 92 | +// CameraX |
| 93 | +val cameraxVersion = "1.4.2" |
| 94 | +implementation("androidx.camera:camera-core:$cameraxVersion") |
| 95 | +implementation("androidx.camera:camera-camera2:$cameraxVersion") |
| 96 | +implementation("androidx.camera:camera-lifecycle:$cameraxVersion") |
| 97 | +implementation("androidx.camera:camera-view:$cameraxVersion") |
| 98 | +``` |
| 99 | + |
| 100 | +## Implementation Order |
| 101 | + |
| 102 | +1. Add Gradle dependencies + CAMERA permission |
| 103 | +2. Download `hand_landmarker.task` to `android/app/src/main/assets/` |
| 104 | +3. Create `HandLandmarkerHelper.kt` - adapted from Google's example |
| 105 | +4. Create `HandLandmarkerOverlay.kt` - Canvas landmark skeleton |
| 106 | +5. Create `HandLandmarkerView.kt` - PlatformView with CameraX |
| 107 | +6. Create `HandLandmarkerPlugin.kt` - Factory + MethodChannel |
| 108 | +7. Register plugin in MainActivity |
| 109 | +8. Refactor TranslatePage + AndroidView into Dart |
| 110 | +9. Build & verify |
| 111 | + |
| 112 | +## Future: Gesture Classification |
| 113 | + |
| 114 | +`HandLandmarkerHelper` includes a pluggable callback: |
| 115 | + |
| 116 | +```kotlin |
| 117 | +fun interface GestureClassifier { |
| 118 | + fun classify(landmarks: HandLandmarkerResult): Pair<String, Float> |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +When the classifier is set, `onResults` runs it and sends `onGestureResult` to Dart. |
| 123 | +Classifier implementation (e.g. TF Lite model) can be added later without touching CameraX or MediaPipe pipeline. |
0 commit comments