在本文中,将探讨如何使用Angular和Firebase构建一个实时通信应用程序。将使用WebRTC技术实现设备之间的点对点通信。WebRTC是一个开源项目,它允许物联网设备通过一组通用协议进行通信。可以通过了解更多信息。
要开始这个项目,需要确保已经安装了Node.js和AngularCLI。Firebase配置将在environments.ts
文件中设置,可以根据需要轻松更改为自己的Firebase配置。
可以按照上的说明下载并设置示例代码。这个应用程序是使用Angular CLI创建的,所以请确保已经安装了这些工具。
以下是实现实时通信应用程序的步骤:
首先,需要设置WebRTC。将创建一个名为AppComponent
的Angular组件,它将负责处理WebRTC的设置和通信。
接下来,将创建一个Firebase连接,用于与Firebase实时数据库进行通信。将使用AngularFire2库来实现这一点。
为了使WebRTC能够进行点对点通信,需要配置ICE服务器。这些服务器将帮助设备发现彼此并建立连接。
信令是WebRTC通信中的关键部分,它允许设备交换必要的信息以建立连接。将使用Firebase实时数据库作为信令服务器。
将使用getUserMedia
API获取用户的音频和视频流,并将这些流添加到RTCPeerConnection中。
为了建立连接,需要创建一个offer和一个answer。将使用createOffer
和createAnswer
方法来实现这一点,并使用setLocalDescription
和setRemoteDescription
方法来设置这些描述。
一旦有了offer和answer,需要交换ICE候选。这将允许设备找到最佳的通信路径。将监听icecandidate
事件,并使用addIceCandidate
方法将ICE候选添加到RTCPeerConnection中。
最后,将显示本地和远程媒体流。将使用ontrack
事件来处理远程流,并使用srcObject
属性将流设置为视频元素的源。
import { Component, OnInit, ViewChild, Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { RouterModule, Routes, ActivatedRoute, Router } from "@angular/router";
import {
AngularFireDatabase,
FirebaseListObservable
} from "angularfire2/database";
import { AngularFireAuth } from "angularfire2/auth";
import * as firebase from "firebase/app";
const SERVERS: any = {
iceServers: [
{ urls: "stun:stun.services.mozilla.com" },
{ urls: "stun:stun.l.google.com:19302" }
]
};
const DEFAULT_CONSTRAINTS = {
optional: []
};
declare let RTCPeerConnection: any;
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
pc: any;
channel: FirebaseListObservable;
database: firebase.database.Reference;
user: firebase.User;
senderId: string;
@ViewChild("me") me: any;
@ViewChild("remote") remote: any;
constructor(
public afDb: AngularFireDatabase,
public afAuth: AngularFireAuth
) {}
ngOnInit() {
this.setupWebRtc();
}
setupWebRtc() {
this.senderId = this.guid();
var channelName = "/webrtc";
this.channel = this.afDb.list(channelName);
this.database = this.afDb.database.ref(channelName);
this.database.on("child_added", this.readMessage.bind(this));
this.pc = new RTCPeerConnection(SERVERS, DEFAULT_CONSTRAINTS);
this.pc.onicecandidate = event =>
event.candidate
? this.sendMessage(
this.senderId,
JSON.stringify({ ice: event.candidate })
)
: console.log("Sent All Ice");
this.pc.ontrack = event =>
(this.remote.nativeElement.srcObject = event.streams[0]); // use ontrack
this.showMe();
}
sendMessage(senderId, data) {
var msg = this.channel.push({
sender: senderId,
message: data
});
msg.remove();
}
readMessage(data) {
if (!data) return;
var msg = JSON.parse(data.val().message);
var sender = data.val().sender;
if (sender != this.senderId) {
if (msg.ice != undefined)
this.pc.addIceCandidate(new RTCIceCandidate(msg.ice));
else if (msg.sdp.type == "offer")
this.pc
.setRemoteDescription(new RTCSessionDescription(msg.sdp))
.then(() => this.pc.createAnswer())
.then(answer => this.pc.setLocalDescription(answer))
.then(() =>
this.sendMessage(
this.senderId,
JSON.stringify({ sdp: this.pc.localDescription })
)
);
else if (msg.sdp.type == "answer")
this.pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
}
}
showMe() {
navigator.mediaDevices
.getUserMedia({ audio: true, video: true })
.then(stream => (this.me.nativeElement.srcObject = stream))
.then(stream => this.pc.addStream(stream));
}
showRemote() {
this.pc
.createOffer()
.then(offer => this.pc.setLocalDescription(offer))
.then(() =>
this.sendMessage(
this.senderId,
JSON.stringify({ sdp: this.pc.localDescription })
)
);
}
guid() {
return (
this.s4() +
this.s4() +
"-" +
this.s4() +
"-" +
this.s4() +
"-" +
this.s4() +
"-" +
this.s4() +
this.s4() +
this.s4()
);
}
s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
}