fix offer/answer handling
This commit is contained in:
1 parent
2a70fd9331
commit
555fce9f10
9 files changed
+274
-71
No files matched your search
Vendored
+3
-3
@@ -1,13 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-critters-container>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Webrtc</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<style>html,body{width:100%;height:100%;margin:0}body{background-color:#2e2e2e}</style><link rel="stylesheet" href="styles.89648d27bd81920d.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.89648d27bd81920d.css"></noscript></head>
|
||||
<link rel="stylesheet" href="styles.css"></head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script src="runtime.c87b550f0b12139c.js" type="module"></script><script src="polyfills.c1402358c38e8522.js" type="module"></script><script src="main.74d7f4c379afaac2.js" type="module"></script></body>
|
||||
<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script><script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body>
|
||||
</html>
|
||||
@@ -2,6 +2,7 @@ import { Component, ComponentRef, HostListener, OnInit, ViewChild, ViewContainer
|
||||
import { VideoComponent } from './components/video/video.component';
|
||||
import { AudioComponent } from './components/audio/audio.component';
|
||||
import { SignalingService } from './services/signaling.service';
|
||||
import { IsAnswer, IsIceCandidate, IsOffer } from './model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -9,7 +10,7 @@ import { SignalingService } from './services/signaling.service';
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
@ViewChild("vc", {static: true, read: ViewContainerRef })
|
||||
@ViewChild("vc", { static: true, read: ViewContainerRef })
|
||||
public vcr!: ViewContainerRef;
|
||||
|
||||
private audioList: ComponentRef<AudioComponent>[] = [];
|
||||
@@ -22,7 +23,7 @@ export class AppComponent implements OnInit {
|
||||
if (!this.selfAudioRunning) {
|
||||
await this.startAudio()
|
||||
}
|
||||
|
||||
|
||||
for (const el of this.audioList) {
|
||||
el.instance.toggleMute();
|
||||
}
|
||||
@@ -44,14 +45,14 @@ export class AppComponent implements OnInit {
|
||||
this.pc = new RTCPeerConnection();
|
||||
|
||||
// Once remote track media arrives, show it in remote element.
|
||||
this.pc.ontrack = (event) => {
|
||||
const [remoteStream] = event.streams;
|
||||
if (event.track.kind === 'video') {
|
||||
this.pc.ontrack = (e) => {
|
||||
const [remoteStream] = e.streams;
|
||||
if (e.track.kind === 'video') {
|
||||
const video = this.vcr.createComponent(VideoComponent, {});
|
||||
video.instance.setStream(remoteStream);
|
||||
video.instance.play();
|
||||
console.log(video);
|
||||
} else if (event.track.kind === 'audio') {
|
||||
} else if (e.track.kind === 'audio') {
|
||||
const audio = this.vcr.createComponent(AudioComponent, {});
|
||||
audio.instance.setStream(remoteStream);
|
||||
console.log(audio);
|
||||
@@ -61,52 +62,77 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.pc.oniceconnectionstatechange = (e) => {
|
||||
console.log(e);
|
||||
console.log('ICE: state change', e);
|
||||
};
|
||||
|
||||
this.pc.onicegatheringstatechange = (e) => {
|
||||
switch (this.pc.iceGatheringState) {
|
||||
case "new":
|
||||
console.log('ICE: gathering is either just starting or has been reset', e);
|
||||
break;
|
||||
case "gathering":
|
||||
console.log('ICE: gathering has begun or is ongoing', e);
|
||||
break;
|
||||
case "complete":
|
||||
console.log('ICE: gathering has ended', e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send any ice candidates to the other peer.
|
||||
this.pc.onicecandidate = ({candidate}) => {
|
||||
console.log(candidate);
|
||||
if (candidate == null) {
|
||||
signaling.next(this.pc.localDescription)
|
||||
this.pc.onicecandidate = ({ candidate }) => {
|
||||
console.log('ICE: candidate', candidate);
|
||||
if (candidate !== null) {
|
||||
signaling.SendCandidate(candidate);
|
||||
} else {
|
||||
/* there are no more candidates coming during this negotiation */
|
||||
}
|
||||
};
|
||||
|
||||
// Offer to receive 1 audio, and 1 video track
|
||||
this.pc.addTransceiver('audio', {direction: 'sendrecv'})
|
||||
this.pc.addTransceiver('video', {direction: 'recvonly'})
|
||||
this.pc.createOffer().then(d => this.pc.setLocalDescription(d)).catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
this.pc.addTransceiver('audio', { direction: 'sendrecv' })
|
||||
this.pc.addTransceiver('video', { direction: 'recvonly' })
|
||||
|
||||
// Let the "negotiationneeded" event trigger offer generation.
|
||||
this.pc.onnegotiationneeded = async (e) => {
|
||||
try {
|
||||
await this.pc.setLocalDescription();
|
||||
signaling.next(this.pc.localDescription);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
this.pc.onnegotiationneeded = (e) => {
|
||||
this.pc.createOffer()
|
||||
.then(async (d) => {
|
||||
await this.pc.setLocalDescription(d)
|
||||
return this.pc.localDescription!
|
||||
})
|
||||
.then((d) => {
|
||||
console.log('SDP: sending offer', d);
|
||||
signaling.SendOffer(d)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
};
|
||||
|
||||
this.signaling.subscribe(async (x) => {
|
||||
console.log(x);
|
||||
if (x.type === 'answer') {
|
||||
console.log('Received answer');
|
||||
this.signaling.subscribe((m) => {
|
||||
switch (true) {
|
||||
case IsIceCandidate(m):
|
||||
console.log('ICE: received new candidate', m.data);
|
||||
break;
|
||||
case IsOffer(m):
|
||||
console.log('SDP: received offer', m.data);
|
||||
break;
|
||||
case IsAnswer(m):
|
||||
console.log('SDP: received answer', m.data);
|
||||
|
||||
try {
|
||||
// the answer
|
||||
await this.pc.setRemoteDescription(x);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.pc.setRemoteDescription(m.data).catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
console.log('WARN: received unknown data', m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async ngOnInit(): Promise<void> {
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
export interface SignalingMessage {
|
||||
type: 'new-ice-candidate' | 'offer' | 'answer';
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface AnswerMessage extends SignalingMessage {
|
||||
data: RTCSessionDescriptionInit;
|
||||
}
|
||||
|
||||
export interface OfferMessage extends SignalingMessage {
|
||||
data: RTCSessionDescriptionInit;
|
||||
}
|
||||
|
||||
export interface IceCandidateMessage extends SignalingMessage {
|
||||
data: RTCIceCandidate;
|
||||
}
|
||||
|
||||
export const IsSignalingMessage = (d: any): d is SignalingMessage => {
|
||||
return !!d && typeof(d.type) === 'string';
|
||||
}
|
||||
|
||||
export const IsIceCandidate = (d: any): d is IceCandidateMessage => {
|
||||
return IsSignalingMessage(d) && d.type === 'new-ice-candidate';
|
||||
}
|
||||
|
||||
export const IsAnswer = (d: any): d is AnswerMessage => {
|
||||
return IsSignalingMessage(d) && d.type === 'answer';
|
||||
}
|
||||
|
||||
export const IsOffer = (d: any): d is OfferMessage => {
|
||||
return IsSignalingMessage(d) && d.type === 'offer';
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { WebSocketSubject, WebSocketSubjectConfig } from 'rxjs/webSocket';
|
||||
import { SignalingMessage } from '../model';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SignalingService extends WebSocketSubject<any> {
|
||||
export class SignalingService extends WebSocketSubject<SignalingMessage> {
|
||||
constructor() {
|
||||
const config: WebSocketSubjectConfig<any> = {
|
||||
url: `${location.origin.replace("http", "ws")}/signaling/${v4()}`,
|
||||
@@ -13,4 +14,18 @@ export class SignalingService extends WebSocketSubject<any> {
|
||||
|
||||
super(config);
|
||||
}
|
||||
|
||||
public SendCandidate(candidate: RTCIceCandidate) {
|
||||
this.next({
|
||||
type: 'new-ice-candidate',
|
||||
data: candidate,
|
||||
});
|
||||
}
|
||||
|
||||
public SendOffer(offer: RTCSessionDescriptionInit) {
|
||||
this.next({
|
||||
type: 'offer',
|
||||
data: offer,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user