feat(videocall): complete channel wrapper + add integration plan doc
Add 11 missing JCMediaChannel methods to VideocallChannelService: getSelfParticipant, subscribeParticipantAudio, startScreenShareVideo, stopScreenShareVideo, enableSelfVideoRatio, getMaxResolution, sendCommandToDelivery, enableVolumeChangeNotify, getCaptureScreen, getScreenRenderId, getScreenUserId. Add VIDEOCALL_INTEGRATION.md with full implementation checklist tracking phases 1-10.
This commit is contained in:
215
VIDEOCALL_INTEGRATION.md
Normal file
215
VIDEOCALL_INTEGRATION.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# Integración Videollamadas — Juphoon jc_sdk
|
||||||
|
|
||||||
|
## Estado general
|
||||||
|
|
||||||
|
| Fase | Estado |
|
||||||
|
|---|---|
|
||||||
|
| 1. SDK wrapper (`videocall_sdk`) | ✅ Completado |
|
||||||
|
| 2. Configuración nativa (permisos) | ✅ Completado |
|
||||||
|
| 3. Configuración por entorno (AppKey) | ✅ Completado |
|
||||||
|
| 4. Feature videocall (UI + lógica) | ⏳ Pendiente |
|
||||||
|
| 5. Pruebas APP↔APP | ⏳ Pendiente |
|
||||||
|
| 6. Integración con backend (señalización) | ⏳ Pendiente |
|
||||||
|
| 7. Pruebas APP↔Reloj | ⏳ Pendiente |
|
||||||
|
| 8. Token auth (producción) | ⏳ Pendiente |
|
||||||
|
| 9. Push/background iOS | ⏳ Pendiente (sin doc del proveedor) |
|
||||||
|
| 10. Chat | ⏳ Pendiente (sin spec del proveedor) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 1: SDK wrapper — ✅
|
||||||
|
|
||||||
|
Paquete `packages/videocall_sdk/` con wrap 100% de `jc_sdk` v2.16.5.
|
||||||
|
|
||||||
|
### Arquitectura (patrón sca_treezor)
|
||||||
|
- Constructor injection (no singletons estáticos)
|
||||||
|
- GetIt module (`videocallSdkModule(config)`)
|
||||||
|
- `VideocallSdkManager` orquestador de inicialización
|
||||||
|
- `VideocallSdkConfig` abstracto para config por entorno
|
||||||
|
- Riverpod providers + StreamProviders para UI reactiva
|
||||||
|
- Callbacks del SDK → Dart Streams
|
||||||
|
|
||||||
|
### Servicios (7 total, cobertura 100%)
|
||||||
|
- `VideocallClient` → JCClient (auth, login, logout, messaging)
|
||||||
|
- `VideocallCallService` → JCCall (llamadas 1-to-1)
|
||||||
|
- `VideocallDeviceService` → JCMediaDevice (cámara, mic, speaker)
|
||||||
|
- `VideocallChannelService` → JCMediaChannel (llamadas grupales)
|
||||||
|
- `VideocallPushService` → JCPush (push notifications)
|
||||||
|
- `VideocallNetService` → JCNet (estado de red)
|
||||||
|
- `VideocallLogService` → JCLog (logging)
|
||||||
|
|
||||||
|
### Estructura
|
||||||
|
```
|
||||||
|
packages/videocall_sdk/lib/src/
|
||||||
|
├── config/videocall_sdk_config.dart
|
||||||
|
├── di/videocall_sdk_module.dart
|
||||||
|
├── manager/videocall_sdk_manager.dart
|
||||||
|
├── models/ (call_state, call_direction, videocall_item, etc.)
|
||||||
|
├── services/ (7 servicios)
|
||||||
|
└── providers/videocall_providers.dart
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 2: Permisos nativos — ✅
|
||||||
|
|
||||||
|
### Android (`AndroidManifest.xml`)
|
||||||
|
- [x] INTERNET (ya existía)
|
||||||
|
- [x] ACCESS_NETWORK_STATE (ya existía)
|
||||||
|
- [x] ACCESS_WIFI_STATE
|
||||||
|
- [x] CAMERA (ya existía)
|
||||||
|
- [x] RECORD_AUDIO
|
||||||
|
- [x] MODIFY_AUDIO_SETTINGS
|
||||||
|
- [x] BLUETOOTH
|
||||||
|
- [x] uses-feature: hardware.camera
|
||||||
|
- [x] uses-feature: hardware.camera.autofocus
|
||||||
|
- [x] uses-feature: hardware.bluetooth (optional)
|
||||||
|
|
||||||
|
### Android (`proguard-rules.pro`)
|
||||||
|
- [x] -keep com.juphoon.**
|
||||||
|
- [x] -keep com.justalk.**
|
||||||
|
- [x] -keepattributes InnerClasses
|
||||||
|
|
||||||
|
### iOS (`Info.plist`)
|
||||||
|
- [x] NSCameraUsageDescription (actualizado: QR + videollamadas)
|
||||||
|
- [x] NSMicrophoneUsageDescription
|
||||||
|
- [x] NSPhotoLibraryUsageDescription
|
||||||
|
|
||||||
|
### iOS (`Podfile`)
|
||||||
|
- [x] PERMISSION_CAMERA=1
|
||||||
|
- [x] PERMISSION_PHOTOS=1
|
||||||
|
- [x] PERMISSION_MICROPHONE=1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 3: Config por entorno — ✅
|
||||||
|
|
||||||
|
- [x] `juphoonAppKey` en development.json, staging.json, production.json
|
||||||
|
- [x] `Environment.juphoonAppKey` via `String.fromEnvironment()`
|
||||||
|
- [x] `SaveFamilyVideocallConfig` implementa `VideocallSdkConfig`
|
||||||
|
- [x] `videocallSdkModule(config)` llamado en `init_app.dart`
|
||||||
|
- [ ] AppKeys separadas por entorno (por ahora las 3 usan la misma clave de dev)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 4: Feature videocall — ⏳ SIGUIENTE
|
||||||
|
|
||||||
|
Feature en `modules/legacy/modules/device_management/lib/src/features/videocall/`
|
||||||
|
|
||||||
|
### Por hacer
|
||||||
|
- [ ] `videocall_builder.dart` — GoRouter builder
|
||||||
|
- [ ] `domain/entities/videocall_entity.dart` — Freezed entity
|
||||||
|
- [ ] `domain/entities/videocall_error.dart` — Error enum con i18n
|
||||||
|
- [ ] `presentation/state/videocall_view_model.dart` — Notifier
|
||||||
|
- [ ] `presentation/state/videocall_view_state.dart` — Freezed state
|
||||||
|
- [ ] `presentation/videocall_screen.dart` — Pantalla de llamada
|
||||||
|
- [ ] `presentation/widgets/local_video_view.dart` — Video local
|
||||||
|
- [ ] `presentation/widgets/remote_video_view.dart` — Video remoto
|
||||||
|
- [ ] `presentation/widgets/call_controls.dart` — Botones (colgar, mute, cámara)
|
||||||
|
- [ ] `presentation/widgets/incoming_call_dialog.dart` — Dialog llamada entrante
|
||||||
|
- [ ] Providers en `core/providers/`
|
||||||
|
- [ ] Ruta en GoRouter (`app_router.dart`)
|
||||||
|
- [ ] Runtime permissions (pedir cámara/mic en runtime)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 5: Pruebas APP↔APP — ⏳
|
||||||
|
|
||||||
|
- [ ] Login con 2 userIDs de prueba (`p_test1`, `p_test2`)
|
||||||
|
- [ ] Llamada de voz APP→APP
|
||||||
|
- [ ] Videollamada APP→APP
|
||||||
|
- [ ] Responder llamada entrante
|
||||||
|
- [ ] Rechazar llamada
|
||||||
|
- [ ] Colgar durante llamada
|
||||||
|
- [ ] Mute/unmute micrófono
|
||||||
|
- [ ] Cambiar cámara frontal/trasera
|
||||||
|
- [ ] Speaker on/off
|
||||||
|
- [ ] Llamada perdida (onMissedCallItem)
|
||||||
|
- [ ] Verificar desfase versión SDK (quickstart 1.0.2 vs pub.dev 2.16.5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 6: Integración backend — ⏳
|
||||||
|
|
||||||
|
- [ ] Obtener documentación API REST del backend SaveFamily para señalización
|
||||||
|
- [ ] Endpoint para iniciar llamada → notificar al reloj
|
||||||
|
- [ ] Endpoint para recibir notificación de llamada entrante del reloj
|
||||||
|
- [ ] Formato userID definido con backend (`p_<cuenta>` vs `w_<IMEI>`)
|
||||||
|
- [ ] Sanitización de emails (@ → _ en userIDs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 7: Pruebas APP↔Reloj — ⏳
|
||||||
|
|
||||||
|
- [ ] Llamada APP → Reloj
|
||||||
|
- [ ] Llamada Reloj → APP
|
||||||
|
- [ ] Videollamada grupal (JCMediaChannel)
|
||||||
|
- [ ] Límite 5 min de llamada
|
||||||
|
- [ ] Registro IMEI (protocolo RYIMEI) — lo hace el backend
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 8: Token auth — ⏳
|
||||||
|
|
||||||
|
- [ ] Backend implementa generación de tokens Juphoon (usa AppSecret)
|
||||||
|
- [ ] App pide token al backend antes del login
|
||||||
|
- [ ] Token se pasa como `password` en `VideocallClient.login()`
|
||||||
|
- [ ] Activar Token鉴权 en consola Juphoon (ya está activo)
|
||||||
|
|
||||||
|
Nota: para dev/testing no es necesario — `autoCreateAccount = true` en LoginParam permite login con cualquier password.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 9: Push/Background iOS — ⏳ RIESGO
|
||||||
|
|
||||||
|
**Problema:** No hay documentación de cómo recibir llamadas con la app cerrada en iOS.
|
||||||
|
|
||||||
|
- [ ] Probar qué pasa cuando la app está cerrada y llega una llamada (fase 5)
|
||||||
|
- [ ] Si no funciona: investigar PushKit + CallKit
|
||||||
|
- [ ] Verificar si `JCPush` del SDK resuelve esto
|
||||||
|
- [ ] Consultar pestaña "消息通知服务" en la consola Juphoon
|
||||||
|
- [ ] Si es deal-breaker: escalar antes del pago ($8,835)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 10: Chat — ⏳ SIN SPEC
|
||||||
|
|
||||||
|
- [ ] Obtener especificación del módulo de chat del proveedor
|
||||||
|
- [ ] Determinar si usa JCMediaChannel (SDK) o API propia
|
||||||
|
- [ ] $2,950 pagados sin lista de features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Credenciales Juphoon Cloud
|
||||||
|
|
||||||
|
| Campo | Valor | Quién lo usa |
|
||||||
|
|---|---|---|
|
||||||
|
| AppKey | `9efcf2d889dc8a0320925096` | App Flutter + Backend |
|
||||||
|
| AppSecret | `ui7pr73ggl5rr0gf01np` | Solo Backend |
|
||||||
|
| AES_KEY (IoT) | `8e3637pG7E9144E0` | Solo Backend |
|
||||||
|
| Consola | juphoon.com (+34 603675786) | Julián |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Naming conventions (protocolo TCP)
|
||||||
|
|
||||||
|
| Tipo | Formato | Ejemplo |
|
||||||
|
|---|---|---|
|
||||||
|
| Watch userID | `w_` + IMEI | `w_000078932675810` |
|
||||||
|
| Mobile userID | `p_` + APP account | `p_abc10086` |
|
||||||
|
| Group room | `did` + `_group` | `0245423235_group` |
|
||||||
|
| Single room | `did` + `_` + APP account | `0245423235_abc10086` |
|
||||||
|
|
||||||
|
Nota: `@` y `.` se reemplazan por `_` en room numbers y userIDs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentación de referencia
|
||||||
|
|
||||||
|
- Quickstart V1.1: `~/Downloads/Video call API_ Juphoon Flutter SDK quickstart V1.1.pdf`
|
||||||
|
- TCP Protocol: `~/Downloads/Juphoon Video Call TCP Protocol.docx`
|
||||||
|
- Connection process: `~/Downloads/video call connection process Rev2.docx`
|
||||||
|
- Mutual dialing: `~/Downloads/video call mutual dialing process.docx`
|
||||||
|
- Schematics: `~/Downloads/schematics _2025.03.26 (2)/`
|
||||||
|
- pub.dev: https://pub.dev/packages/jc_sdk
|
||||||
|
- Consola: https://developer.juphoon.com
|
||||||
@@ -298,6 +298,57 @@ class VideocallChannelService with JCMediaChannelCallback {
|
|||||||
return _channel!.getCdnState();
|
return _channel!.getCdnState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getCaptureScreen() async {
|
||||||
|
if (_channel == null) return '';
|
||||||
|
return _channel!.getCaptureScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getScreenRenderId() async {
|
||||||
|
if (_channel == null) return '';
|
||||||
|
return _channel!.getScreenRenderId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getScreenUserId() async {
|
||||||
|
if (_channel == null) return '';
|
||||||
|
return _channel!.getScreenUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Self participant --
|
||||||
|
|
||||||
|
Future<JCMediaChannelParticipant?> getSelfParticipant() async {
|
||||||
|
return _channel?.getSelfParticipant();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> subscribeParticipantAudio(
|
||||||
|
JCMediaChannelParticipant participant, bool subscribe) async {
|
||||||
|
if (_channel == null) return false;
|
||||||
|
return _channel!.subscribeParticipantAudio(participant, subscribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Screen share video --
|
||||||
|
|
||||||
|
Future<JCMediaDeviceVideoCanvas?> startScreenShareVideo(
|
||||||
|
int renderType, int pictureSize) async {
|
||||||
|
return _channel?.startScreenShareVideo(renderType, pictureSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> stopScreenShareVideo() async {
|
||||||
|
if (_channel == null) return false;
|
||||||
|
return _channel!.stopScreenShareVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Video ratio / resolution --
|
||||||
|
|
||||||
|
Future<bool> enableSelfVideoRatio(bool enable, double ratio) async {
|
||||||
|
if (_channel == null) return false;
|
||||||
|
return _channel!.enableSelfVideoRatio(enable, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> getMaxResolution() async {
|
||||||
|
if (_channel == null) return 0;
|
||||||
|
return _channel!.getMaxResolution();
|
||||||
|
}
|
||||||
|
|
||||||
// -- Messaging --
|
// -- Messaging --
|
||||||
|
|
||||||
Future<bool> sendMessage(
|
Future<bool> sendMessage(
|
||||||
@@ -311,6 +362,11 @@ class VideocallChannelService with JCMediaChannelCallback {
|
|||||||
return _channel!.sendCommand(name, param);
|
return _channel!.sendCommand(name, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> sendCommandToDelivery(String command) async {
|
||||||
|
if (_channel == null) return false;
|
||||||
|
return _channel!.sendCommandToDelivery(command);
|
||||||
|
}
|
||||||
|
|
||||||
// -- Statistics --
|
// -- Statistics --
|
||||||
|
|
||||||
Future<String> getStatistics() async {
|
Future<String> getStatistics() async {
|
||||||
@@ -318,6 +374,18 @@ class VideocallChannelService with JCMediaChannelCallback {
|
|||||||
return _channel!.getStatistics();
|
return _channel!.getStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- Volume change notify --
|
||||||
|
|
||||||
|
Future<bool> enableVolumeChangeNotify(bool value) async {
|
||||||
|
if (_channel == null) return false;
|
||||||
|
return _channel!.enableVolumeChangeNotify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> getVolumeChangeNotify() async {
|
||||||
|
if (_channel == null) return false;
|
||||||
|
return _channel!.getVolumeChangeNotify();
|
||||||
|
}
|
||||||
|
|
||||||
// -- Dispose --
|
// -- Dispose --
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|||||||
Reference in New Issue
Block a user