fix offer/answer handling

This commit is contained in:
kaedwen committed 2024-03-25 20:37:24 +01:00
1 parent 2a70fd9331
commit 555fce9f10
9 files changed
+274 -71

No files matched your search

+3 -3
View File
@@ -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>
+61 -35
View File
@@ -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> {
}
}
+33
View File
@@ -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';
}
+16 -1
View File
@@ -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,
});
}
}