flutter_meta_wearables_dat 0.9.1 et app_links remplacent les dépendances « à décommenter quand publiées ». Le SDK natif vit sur GitHub Packages : sans PAT read:packages dans local.properties, le build tombe en 401 — et un token expiré peut passer inaperçu, Gradle servant son cache. Note ce que le SDK ne donne pas (ni micro, ni haut-parleur, ni bouton : le vocal passe par le routage Bluetooth) et pourquoi iOS n'est pas activé. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
13 KiB
Intégration Ray-Ban Meta — mymuseum-visitapp
Vue d'ensemble
L'intégration Ray-Ban Meta transforme mymuseum-visitapp en guide audio mains-libres. Le visiteur porte les lunettes ; son téléphone est en poche. Il peut :
- Poser une question à voix haute à l'assistant sans toucher le téléphone
- Scanner un QR code en appuyant sur le bouton des lunettes → entendre l'explication
- Recevoir automatiquement des informations contextuelles en approchant d'une œuvre (beacon ou géolocalisation)
L'architecture existante (AssistantService, QR scanner, beacons, géoloc) est inchangée. Les lunettes sont une nouvelle surface d'entrée/sortie branchée dessus.
Prérequis
Matériel & apps
- Ray-Ban Meta Gen 2 (~380€, 8h autonomie)
- App Meta AI installée sur le téléphone — bridge Bluetooth obligatoire
- Compte Meta connecté dans l'app Meta AI
Comptes & clés API
| Service | Usage | Où obtenir |
|---|---|---|
| Picovoice Console | Générer le wake word .ppn | https://console.picovoice.ai/ — gratuit jusqu'à 3 keywords |
| ElevenLabs | Synthèse vocale TTS | https://elevenlabs.io — tier gratuit ou payant selon volume |
SDK Flutter (developer preview)
flutter_meta_wearables_dat: ^0.9.1 # Android API 29+ et iOS 17.2+ — SDK DAT 0.9.0
app_links: ^6.4.1 # retour du deep link Meta AI → handleUrl()
Le SDK natif est publié sur GitHub Packages, pas sur Maven Central. Il faut un
PAT GitHub avec le scope read:packages, dans android/local.properties :
github_token=ghp_xxxx
Sans lui, le build échoue en 401 Unauthorized sur maven.pkg.github.com. Un build
peut sembler passer alors que le token est expiré : Gradle sert les artefacts depuis
son cache local. Vérifier le token, pas le build.
iOS n'est pas activé malgré le support du package. Le coût réel :
- deployment target à 17.2 pour toute l'app visiteur (Podfile est à 12.0)
- dictionnaire
MWDATdans Info.plist (MetaAppID,ClientToken,TeamID) — le mode développeur"0"d'Android ne suffit pas - choix d'un transport caméra : Wi-Fi (entitlements Hotspot Configuration + Wi-Fi
Information,
NSLocalNetworkUsageDescription, +10 s à la 1re connexion) ou Bluetooth Classic (UISupportedExternalAccessoryProtocols, bande passante moindre) - la publication publique reste limitée aux partenaires Meta sous developer preview
Le SDK DAT n'expose ni micro, ni haut-parleur, ni bouton des lunettes. Tout le
pipeline vocal passe hors SDK (routing Bluetooth HFP/A2DP via AudioRoutingChannel).
Setup initial
1. Générer les fichiers wake word (.ppn)
- Aller sur https://console.picovoice.ai/
- Créer un compte → Wake Word → Create a keyword
- Keyword :
Hey MyVisit(ou autre phrase de votre choix) - Exporter pour Android → sauvegarder dans
assets/wake_words/hey_myvisit_android.ppn - Exporter pour iOS → sauvegarder dans
assets/wake_words/hey_myvisit_ios.ppn - Copier l'Access Key affiché dans le dashboard
2. Configurer les variables de build (--dart-define)
Toutes les clés et URLs sont injectées via --dart-define au moment du build/run. Aucune clé en dur dans le code.
Référence complète des --dart-define
| Variable | Obligatoire | Exemple | Description |
|---|---|---|---|
API_BASE_URL |
✅ prod | https://api.mymuseum.be |
URL du backend MyInfoMate |
API_KEY |
✅ | abc123 |
Clé API MyInfoMate (bootstrappe la clé depuis le backend au 1er run) |
INSTANCE_ID |
✅ | 63514fd67ed8c735aaa4b8f2 |
ID de l'instance (fixe par flavor) |
WHISPER_API_KEY |
⚡ recommandé | sk-xxxx |
Clé OpenAI Whisper ou n'importe quelle valeur si endpoint auto-hébergé |
WHISPER_ENDPOINT |
⚡ si auto-hébergé | http://192.168.1.x:8000/v1/audio/transcriptions |
Endpoint Whisper compatible OpenAI (défaut: api.openai.com) |
ELEVENLABS_API_KEY |
❌ optionnel | sk_xxxx |
TTS ElevenLabs — si vide, utilise flutter_tts (on-device gratuit) |
ELEVENLABS_VOICE_ID |
❌ optionnel | EXAVITQu4vr4xnSDxMaL |
ID voix ElevenLabs |
PICOVOICE_ACCESS_KEY |
❌ optionnel | xxxx |
Wake word Porcupine — si vide, utilise speech_to_text |
FLAVOR |
✅ build | dev / mdlf / fortsaintheribert |
Détermine les couleurs et l'app name |
Commandes de lancement
Dev minimal (speech_to_text + flutter_tts) :
flutter run --flavor dev \
--dart-define=API_BASE_URL=http://192.168.x.x:5000 \
--dart-define=API_KEY=ta_clé \
--dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2
Dev avec Whisper OpenAI :
flutter run --flavor dev \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=ta_clé \
--dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2 \
--dart-define=WHISPER_API_KEY=sk-xxxx
Dev avec Whisper auto-hébergé (faster-whisper-server Docker) :
flutter run --flavor dev \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=ta_clé \
--dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2 \
--dart-define=WHISPER_API_KEY=anykey \
--dart-define=WHISPER_ENDPOINT=http://192.168.x.x:8000/v1/audio/transcriptions
Production complète (Porcupine + Whisper auto-hébergé + flutter_tts) :
flutter build appbundle --flavor mdlf \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=ta_clé \
--dart-define=INSTANCE_ID=xxx \
--dart-define=FLAVOR=mdlf \
--dart-define=PICOVOICE_ACCESS_KEY=xxx \
--dart-define=WHISPER_API_KEY=anykey \
--dart-define=WHISPER_ENDPOINT=https://whisper.myinfomate.be/v1/audio/transcriptions
Démarrer faster-whisper-server (Docker)
docker run -p 8000:8000 fedirz/faster-whisper-server:latest-cpu
# Avec GPU :
docker run --gpus all -p 8000:8000 fedirz/faster-whisper-server:latest-cuda
3. Activer le mode lunettes dans l'app
Dans VisitAppContext, passer glassesEnabled = true. Pour l'instant c'est à faire dans le code ou via un toggle UI à créer dans les paramètres de la configuration.
4. Lancer l'app sur le device
flutter run --dart-define=ELEVENLABS_API_KEY=xxx --dart-define=PICOVOICE_ACCESS_KEY=xxx
Architecture technique
Lunettes Ray-Ban Meta
│ Bluetooth (HFP mic, A2DP speakers, camera stream, button)
▼
[MetaGlassesService] ← SDK DAT lifecycle + capture photo
│
├── [WakeWordService] ← Porcupine détecte "Hey MyVisit" (on-device)
│ └── speech_to_text ← transcrit la commande après wake word
│ ├── "scanne ce QR" → MetaGlassesService.requestPhotoCapture()
│ ├── "répète" → GlassesTtsService.replay()
│ └── autre → AssistantService.chat() → GlassesTtsService
│
├── [GlassesQrScannerService] ← photo → mobile_scanner → regex URL → AssistantService
│
├── [GeoBeaconTriggerService] ← geolocator GPS + beacon_scanner BLE → AssistantService
│
└── [GlassesTtsService] ← ElevenLabs API → just_audio → AudioRoutingChannel
└── [AudioRoutingPlugin] ← natif iOS (AVAudioSession) / Android (AudioManager)
force sortie A2DP vers lunettes
Flux de données type (question vocale)
- Porcupine détecte "Hey MyVisit"
speech_to_texttranscrit : "Qui a peint ce tableau ?"AssistantService.chat("Qui a peint ce tableau ?")→ backend MyInfoMate- Réponse texte →
GlassesTtsService.speak() - ElevenLabs → MP3 →
just_audio→ AudioRoutingPlugin → lunettes Ray-Ban
Fonctionnalités
1. Wake word + conversation vocale
Déclencheur : "Hey MyVisit"
Commandes reconnues :
| Phrase | Action |
|---|---|
| "Hey MyVisit" + question | Pose la question à l'assistant, entend la réponse |
| "Hey MyVisit, scanne ce QR" | Déclenche la capture photo → pipeline QR |
| "Hey MyVisit, répète" | Rejoue la dernière synthèse vocale |
Le wake word fonctionne en permanence en background (Foreground Service sur Android).
2. Scanner QR via lunettes
Déclencheur : commande vocale "Hey MyVisit, scanne ce QR"
⚠️ Le bouton hardware des lunettes n'est pas un déclencheur : le SDK DAT ne l'expose pas, et rien n'est implémenté pour lui côté app.
Fonctionnement :
- Les lunettes capturent une photo
GlassesQrScannerServicedécode le QR (même regex que le scanner téléphone)- Si la section appartient à la configuration courante, l'assistant la résume en 3 phrases
- La réponse est lue dans les lunettes
Anti-spam : 10s de cooldown entre deux scans du même QR.
3. Géolocalisation → TTS automatique (mode proactif)
Activer VisitAppContext.proactiveModeEnabled = true pour que l'assistant parle spontanément.
GPS : Le service surveille en continu la position GPS. Quand le visiteur entre dans le rayon d'un GeoTriggerPoint (défini par lat/lng + rayon en mètres), l'assistant propose une présentation du lieu.
Beacons BLE : Quand la précision d'un beacon connu passe sous 3m, l'assistant présente l'œuvre ou l'espace associé.
Les deux fonctionnent en parallèle, avec un cooldown de 30s par point pour éviter le spam.
4. Navigation guidée mains-libres
Dans un GuidedPath, chaque étape peut déclencher un TTS automatique à l'entrée de sa zone GPS ou à la détection de son beacon. Le visiteur progresse sans jamais toucher son téléphone.
5. Mode "Quiet"
La sortie audio est forcée sur les lunettes via AudioRoutingPlugin, pas sur le haut-parleur téléphone. Respecte le silence des musées.
Mode proactif — comportement
proactiveModeEnabled |
glassesEnabled |
Comportement |
|---|---|---|
false |
false |
Comportement app normal, pas de lunettes |
false |
true |
Lunettes actives, TTS sur questions vocales et QR, pas de déclenchements auto |
true |
true |
Tout actif : wake word + QR + géoloc/beacon auto |
Recommandation : proposer le toggle du mode proactif à l'écran de démarrage de visite.
Multi-visiteur broadcast (V2)
Plusieurs visiteurs avec des lunettes dans la même instance peuvent recevoir simultanément un message du guide via Firebase.
Comment ça marchera :
- Le guide envoie un message depuis
manager-app→ push notification Firebase PushNotificationServicereçoit la notification- Un handler à ajouter dans
initialize()pipe le corps du message versGlassesTtsService - Tous les visiteurs connectés entendent le message dans leurs lunettes
Le topic Firebase instance_{instanceId} est déjà en place.
Limitations SDK (developer preview, mai 2026)
| Limitation | Détail |
|---|---|
| App Meta AI requise | Doit être installée et active — bridge Bluetooth obligatoire |
| Résolution caméra | Max 720p/30fps via Bluetooth (contrainte protocole, pas hardware) |
| Publication App Store/Play Store | Réservée aux partenaires Meta sélectionnés pour l'instant — distribution interne possible |
Android uniquement pour meta_wearables_dat |
Le plugin Flutter iOS (meta_wearables) est en cours |
| iOS 26.0+ pour le SDK natif | Si implémentation Swift directe (comme OpenGlasses) |
Troubleshooting
Son dans le téléphone au lieu des lunettes
- Vérifier que les lunettes sont bien en Bluetooth actif et appairées
- Sur Android API < 31 : vérifier que
startBluetoothSco()ne génère pas d'erreur de permission - Sur iOS : vérifier que
AVAudioSession.setCategoryest appelé avantjust_audio.play()
Wake word ne répond pas
- Vérifier que
kPicovoiceAccessKeyest bien injecté (non vide) - Vérifier que les fichiers
.ppnsont bien dansassets/wake_words/et déclarés danspubspec.yaml - Les fichiers
.ppnsont spécifiques Android/iOS — ne pas les inverser
Faux positifs wake word fréquents
- Télécharger un modèle plus sensible (paramètre
sensitivitydePorcupineManager) - Choisir une phrase wake word plus longue sur Picovoice Console
Scan QR échoue depuis les lunettes
- Vérifier que le QR code est bien visible (lumière suffisante)
- Le SDK DAT doit être en état
connectedoustreaming— vérifierMetaGlassesService.instance.state - Vérifier que la section scannée appartient bien à la configuration active (
visitContext.sectionIds)
ElevenLabs retourne 401
- Clé API expirée ou quota dépassé — vérifier sur https://elevenlabs.io/speech-synthesis
Fichiers clés
| Fichier | Rôle |
|---|---|
lib/Services/meta_glasses_service.dart |
SDK DAT lifecycle, photo capture |
lib/Services/glasses_tts_service.dart |
ElevenLabs → audio Bluetooth |
lib/Services/wake_word_service.dart |
Porcupine + dispatch commandes vocales |
lib/Services/geo_beacon_trigger_service.dart |
Géofence GPS + beacons → TTS auto |
lib/Services/glasses_qr_scanner_service.dart |
QR decode depuis photo capture lunettes |
lib/PlatformChannels/audio_routing_channel.dart |
Canal Dart → natif pour routage Bluetooth |
android/…/MainActivity.kt |
Plugin Android : AudioManager A2DP |
ios/Runner/AudioRoutingPlugin.swift |
Plugin iOS : AVAudioSession Bluetooth |
assets/wake_words/ |
Fichiers .ppn Porcupine (à générer) |