Crear una app con videollamadas

Desarrollar app con videollamadas

¿Cómo crear una aplicación con videollamadas?

by I. Fuioaga, App Developer, 21/12/2021

Pide presupuesto ahora, click aquí

Hace no demasiados años se normalizaba el hecho de que las videollamadas fueran un servicio de pago más caro que las llamadas normales. A día de hoy, tanto las llamadas corrientes como las videollamadas son un servicio que están al alcance de cualquiera que tenga conexión a internet sin necesidad de pagar un servicio adicional para poder hacerlas. La creacción de una app con videollamadas es posible con Abalit y te podemos ayudar con su desarrollo.

Gracias a esto, cada vez es más común encontrar aplicaciones gratuitas que tengan entre su funcionalidad realizar videollamadas, como por ejemplo, Whatsapp o Instagram. Por eso, en el artículo de hoy vamos a hablar sobre cómo desarrollar una aplicación con videollamadas en Flutter 2 con WebRTC.

¿Qué es WebRTC?

WebRTC es un proyecto de código abierto de comunicación en tiempo real para web y puede agregar capacidades de comunicación en tiempo real a su aplicación apoyado por Apple, Google, Microsoft y Mozilla, entre otros. WebRTC admite video, voz y datos genéricos que se envían entre pares. Lo mejor de todo esto es que está disponible tanto para los principales navegadores como para Android e iOS (con una biblioteca).

Configuración

Como hemos mencionado un poco más arriba existe una biblioteca para desarrollar para Android e iOS en nativo. Sin embargo, para Flutter existe una librería llamada flutter_webrtc, y podemos utilizarlo tanto para móvil, escritorio o web.

En primer lugar debemos instalar el package, simplemente debemos añadir la siguiente línea de código en nuestro pubspec.yaml:

flutter_webrtc: ^0.6.6

En cuanto a la configuración de Android, habría que añadir las siguientes lineas en el AndroidManifest.xml:

<uses-feature android:name="android.hardware.camera" >
<uses-feature android:name="android.hardware.camera.autofocus" >
<uses-permission android:name="android.permission.CAMERA" >
<uses-permission android:name="android.permission.RECORD_AUDIO" >
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" >
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" >
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" >
<uses-permission android:name="android.permission.BLUETOOTH" >
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" >

Las dos últimas solo las utilizaremos si necesitamos utilizar el servicio de bluetooth del dispositivo donde se instale la aplicación.

Para la configuración en iOS será necesario añadir estas lineas al Info.plist:

<key>NSCameraUsageDescription</key><string>$(PRODUCT_NAME) Camera Usage!</string><key>NSMicrophoneUsageDescription</key><string>$(PRODUCT_NAME) Microphone Usage!</string>

Antes de empezar con el desarrollo has de saber también que es necesaria la conexión con un servidor para poder establecer la conexión entre varios usuarios diferentes. Por ello, en nuestro ejemplo, vamos a utilizar Firebase para gestionar estas conexiones con el servidor.

En Abalit, somos desarrolladores de apps con Flutter. Si quieres desarrollar una aplicación con videollamadas podemos encargarnos de su desarrollo utilizando flutter_webrtc y optimizando su funcionamiento al máximo.

Desarrollo:

En primer lugar, necesitamos instanciar algunas variables:

RTCPeerConnection? peerConnection;
MediaStream? localStream;
MediaStream? remoteStream;
String? roomId;
String? currentRoomText;
StreamStateCallback?
onAddRemoteStream;

Antes de esto debemos instanciar también las urls de nuestra base de datos.

El siguiente paso sería crear funciones para crear rooms, unirse a rooms, acceder al media del dispositivo y salir de la room.

Esta función para crear el room:

Future<String> createRoom(RTCVideoRenderer remoteRenderer) async {
FirebaseFirestore db = FirebaseFirestore.instance;
DocumentReference roomRef = db.collection('rooms').doc();

peerConnection = await createPeerConnection(configuration);

registerPeerConnectionListeners();

localStream?.getTracks().forEach((track) {
peerConnection?.addTrack(track, localStream!);
});

/// Recogemos los candidatos de la base de datos
var callerCandidatesCollection = roomRef.collection('callerCandidates');

peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
callerCandidatesCollection.add(candidate.toMap());
};

///Creamos una room
RTCSessionDescription offer = await peerConnection!.createOffer();
await peerConnection!.setLocalDescription(offer);
Map<String, dynamic> roomWithOffer = {'offer': offer.toMap()};

await roomRef.set(roomWithOffer);
var roomId = roomRef.id;
peerConnection?.onTrack = (RTCTrackEvent event) {
event.streams[0].getTracks().forEach((track) {
remoteStream?.addTrack(track);
});

};

///Creamos un listener para el evento
roomRef.snapshots().listen((snapshot) async {
Map<String, dynamic> data = snapshot.data() as Map<String, dynamic>;
if (peerConnection?.getRemoteDescription() != null &&
data['answer'] != null) {
var answer = RTCSessionDescription(
data['answer']['sdp'],
data['answer']['type'],

);

await peerConnection?.setRemoteDescription(answer);

}

});

///Listener para los candidatos
roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
snapshot.docChanges.forEach((change) {
if (change.type == DocumentChangeType.added) {
Map<String, dynamic> data = change.doc.data() as Map<String, dynamic>;
peerConnection!.addCandidate(
RTCIceCandidate(
data['candidate'],
data['sdpMid'],
data['sdpMLineIndex'],
)

);

}

});

});
return roomId;
}

La siguiente función se utiliza para unirse a una room:

Future<void> joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
FirebaseFirestore db = FirebaseFirestore.instance;
DocumentReference roomRef = db.collection('rooms').doc('$roomId');
var roomSnapshot = await roomRef.get();

if (roomSnapshot.exists) {
peerConnection = await createPeerConnection(configuration);

registerPeerConnectionListeners();

localStream?.getTracks().forEach((track) {
peerConnection?.addTrack(track, localStream!);
});

/// Recogemos los candidatos de la base de datos
var calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection!.onIceCandidate = (RTCIceCandidate candidate) {
if (candidate == null) {
return;
}
calleeCandidatesCollection.add(candidate.toMap());

};

peerConnection?.onTrack = (RTCTrackEvent event) {
event.streams[0].getTracks().forEach((track) {
remoteStream?.addTrack(track);
});

};

///Crear una respuesta
var data = roomSnapshot.data() as Map<String, dynamic>;
var offer = data['offer'];
await peerConnection?.setRemoteDescription(
RTCSessionDescription(offer['sdp'], offer['type']),
);
var answer = await peerConnection!.createAnswer();

await peerConnection!.setLocalDescription(answer);

Map<String, dynamic> roomWithAnswer = {
'answer': {'type': answer.type, 'sdp': answer.sdp}
};

await roomRef.update(roomWithAnswer);

///Listener para los candidatos
roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
snapshot.docChanges.forEach((document) {
var data = document.doc.data() as Map<String, dynamic>;
peerConnection!.addCandidate(
RTCIceCandidate(
data['candidate'],
data['sdpMid'],
data['sdpMLineIndex'],<
),

);

});

});

}

}

Con la siguiente función abrimos los servicios de video y de audio:

Future<void> openUserMedia(RTCVideoRenderer localVideo,RTCVideoRenderer remoteVideo,) async {
var stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': false});

localVideo.srcObject = stream;
localStream = stream;

remoteVideo.srcObject = await createLocalMediaStream('key');

}

La siguiente función sirve para "colgar" la llamada:

Future<void> hangUp(RTCVideoRenderer localVideo) async {
List<MediaStreamTrack> tracks = localVideo.srcObject!.getTracks();
tracks.forEach((track) {
track.stop();
});

if (remoteStream != null) {
remoteStream!.getTracks().forEach((track) => track.stop());
}
if (peerConnection != null) peerConnection!.close();

if (roomId != null) {
var db = FirebaseFirestore.instance;
var roomRef = db.collection('rooms').doc(roomId);
var calleeCandidates = await roomRef.collection('calleeCandidates').get();
calleeCandidates.docs.forEach((document) => document.reference.delete());

var callerCandidates = await roomRef.collection('callerCandidates').get();
callerCandidates.docs.forEach((document) => document.reference.delete());

await roomRef.delete();

}

localStream!.dispose();
remoteStream?.dispose();

}

Con estas tres funciones se pueden gestionar videollamadas de una forma bastante sencilla, pero recuerda que necesitaras crear tener un servidor preparado para ello y gestionarlo con una base de datos.

Si quieres saber más sobre WebRTC puedes informarte en la página oficial que está gestionada por Google. Además, también puedes echar un ojo a la documentación oficial de la librería con la que estamos trabajando, flutter_webrtc. Por otra parte, también hemos hecho uso de Firebase en Flutter, si quieres saber más sobre el tema puede ver nuestro artículo sobre Firebase.

¿Cómo desarrollar una aplicación en Flutter con WebRTC?

Como has visto desarrollar una aplicación capaz de realizar videollamadas no parece muy complicado. Sin embargo, se necesitan conocimientos avanzados para poder desarrollarla, ya que no solo va a tener esta funcionalidad la aplicación que se quiera desarrollar. En Abalit Technologies, contamos con un equipo de desarrollo expertos en desarrollar aplicaciones con videollamadas. Por lo que, si tienes en mente desarrollar una aplicación con esta funcionalidad o, simplemente, alguna duda sobre el tema, no dudes en contactarnos. Puedes hacerlos sin ningún tipo de compromiso y pedir presupuesto de forma totalmente gratuita.

Conclusión

El uso de las videollamadas siempre ha sido una funcionalidad muy popular en nuestra sociedad, utilizado frecuentemente para dar sensación de cercanía aun cuando se está a miles de kilómetros. De hecho, desde hace años, han existido aplicaciones en muchos entornos que han sido diseñadas exclusivamente para este tipo de comunicaciones.
Debido a la situación sanitaria su uso se ha visto incrementado, pues la distancia no ha sido el único factor que ha hecho que se utilicen. Y, personalmente, creo que se van a seguir utilizando porque la cercanía que ofrece frente a una llamada convencional es mucho mayor. Concluyendo, creo que incluir esta funcionalidad en cualquier aplicación en el que haya comunicación entre usuarios es un acierto muy grande y puede ser un distintivo bastante importante entre aplicaciones del mismo estilo.

Ponte en contacto con nosotros para pedir presupuesto para el desarrollo de una aplicación con videollamadas. Integraremos esta función para que tu aplicación sea todo un éxito. ¡Tenemos una gran experiencia en la creación de aplicaciones!

Pide presupuesto ahora, click aquí