Skip to content

Commit 8f5a2a7

Browse files
authored
Merge pull request #250 from koush/develop
add support for stop/remove tracks.
2 parents b558849 + 41b339b commit 8f5a2a7

File tree

7 files changed

+376
-40
lines changed

7 files changed

+376
-40
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ChildProcess, spawn } from "child_process";
2+
import { createSocket } from "dgram";
3+
import { AcceptFn } from "protoo-server";
4+
import {
5+
RTCPeerConnection,
6+
MediaStreamTrack,
7+
RtpPacket,
8+
randomPort,
9+
} from "../../";
10+
import { peerConfig } from "../../fixture";
11+
12+
export class mediachannel_removetrack_base {
13+
pc!: RTCPeerConnection;
14+
process!: ChildProcess;
15+
udp = createSocket("udp4");
16+
17+
async exec(type: string, payload: any, accept: AcceptFn) {
18+
switch (type) {
19+
case "init":
20+
{
21+
const port = await randomPort();
22+
this.udp.bind(port);
23+
24+
this.pc = new RTCPeerConnection(await peerConfig);
25+
const track = new MediaStreamTrack({ kind: "video" });
26+
this.pc.addTransceiver(track, { direction: "sendonly" });
27+
await this.pc.setLocalDescription(await this.pc.createOffer());
28+
accept(this.pc.localDescription);
29+
30+
this.udp.on("message", (data) => {
31+
const rtp = RtpPacket.deSerialize(data);
32+
track.writeRtp(rtp);
33+
});
34+
35+
const args = [
36+
`videotestsrc`,
37+
"video/x-raw,width=640,height=480,format=I420",
38+
"vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1",
39+
"rtpvp8pay",
40+
`udpsink host=127.0.0.1 port=${port}`,
41+
].join(" ! ");
42+
this.process = spawn("gst-launch-1.0", args.split(" "));
43+
}
44+
break;
45+
case "candidate":
46+
{
47+
await this.pc.addIceCandidate(payload);
48+
accept({});
49+
}
50+
break;
51+
case "answer":
52+
{
53+
await this.pc.setRemoteDescription(payload);
54+
accept({});
55+
}
56+
break;
57+
case "removeTrack":
58+
{
59+
const sender = this.pc.transceivers[payload].sender;
60+
this.pc.removeTrack(sender);
61+
await this.pc.setLocalDescription(await this.pc.createOffer());
62+
accept(this.pc.localDescription);
63+
}
64+
break;
65+
case "addTrack":
66+
{
67+
const track = new MediaStreamTrack({ kind: "video" });
68+
this.pc.addTransceiver(track, { direction: "sendonly" });
69+
await this.pc.setLocalDescription(await this.pc.createOffer());
70+
accept(this.pc.localDescription);
71+
72+
this.udp.on("message", (data) => {
73+
const rtp = RtpPacket.deSerialize(data);
74+
track.writeRtp(rtp);
75+
});
76+
}
77+
break;
78+
case "done":
79+
{
80+
this.udp.close();
81+
this.pc.close();
82+
try {
83+
this.process.kill("SIGINT");
84+
} catch (error) {}
85+
accept({});
86+
}
87+
break;
88+
}
89+
}
90+
}
91+
92+
export class mediachannel_removetrack_addtrack extends mediachannel_removetrack_base {}
93+
94+
export class mediachannel_addtrack_removefirst_addtrack extends mediachannel_removetrack_base {}

e2e/server/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ import {
5252
bundle_max_bundle_answer,
5353
bundle_max_bundle_offer,
5454
} from "./handler/bundle/max-bundle";
55+
import {
56+
mediachannel_addtrack_removefirst_addtrack,
57+
mediachannel_removetrack_addtrack,
58+
} from "./handler/mediachannel/removeTrack";
5559

5660
const app = express();
5761
app.use(express.json() as never);
@@ -107,6 +111,9 @@ server.on("connectionrequest", async (_, accept) => {
107111
bundle_disable_offer: new bundle_disable_offer(),
108112
bundle_max_bundle_answer: new bundle_max_bundle_answer(),
109113
bundle_max_bundle_offer: new bundle_max_bundle_offer(),
114+
mediachannel_removetrack_addtrack: new mediachannel_removetrack_addtrack(),
115+
mediachannel_addtrack_removefirst_addtrack:
116+
new mediachannel_addtrack_removefirst_addtrack(),
110117
};
111118

112119
const transport = accept();
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { waitVideoPlay, peer, sleep } from "../fixture";
2+
3+
const mediachannel_removetrack_addtrack = "mediachannel_removetrack_addtrack";
4+
const mediachannel_addtrack_removefirst_addtrack =
5+
"mediachannel_addtrack_removefirst_addtrack";
6+
7+
describe("mediachannel_removeTrack", () => {
8+
it(
9+
"removeTrack -> addTrack",
10+
async () =>
11+
new Promise<void>(async (done) => {
12+
if (!peer.connected) await new Promise<void>((r) => peer.on("open", r));
13+
await sleep(100);
14+
15+
let offer = await peer.request(mediachannel_removetrack_addtrack, {
16+
type: "init",
17+
});
18+
19+
const pc = new RTCPeerConnection({
20+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
21+
});
22+
pc.onicecandidate = ({ candidate }) => {
23+
peer
24+
.request(mediachannel_removetrack_addtrack, {
25+
type: "candidate",
26+
payload: candidate,
27+
})
28+
.catch(() => {});
29+
};
30+
31+
const answer = async () => {
32+
await pc.setRemoteDescription(offer);
33+
await pc.setLocalDescription(await pc.createAnswer());
34+
peer
35+
.request(mediachannel_removetrack_addtrack, {
36+
type: "answer",
37+
payload: pc.localDescription,
38+
})
39+
.catch(() => {});
40+
};
41+
42+
answer();
43+
let track = await new Promise<MediaStreamTrack>(
44+
(r) => (pc.ontrack = (e) => r(e.track))
45+
);
46+
await waitVideoPlay(track);
47+
48+
offer = await peer.request(mediachannel_removetrack_addtrack, {
49+
type: "removeTrack",
50+
payload: 0,
51+
});
52+
await answer();
53+
54+
offer = await peer.request(mediachannel_removetrack_addtrack, {
55+
type: "addTrack",
56+
});
57+
answer();
58+
track = await new Promise<MediaStreamTrack>(
59+
(r) => (pc.ontrack = (e) => r(e.track))
60+
);
61+
await waitVideoPlay(track);
62+
63+
await peer.request(mediachannel_removetrack_addtrack, {
64+
type: "done",
65+
});
66+
pc.close();
67+
done();
68+
}),
69+
10 * 1000
70+
);
71+
72+
it(
73+
"addTrack -> remove first -> addTrack",
74+
async () =>
75+
new Promise<void>(async (done) => {
76+
if (!peer.connected) await new Promise<void>((r) => peer.on("open", r));
77+
await sleep(100);
78+
79+
let offer = await peer.request(
80+
mediachannel_addtrack_removefirst_addtrack,
81+
{
82+
type: "init",
83+
}
84+
);
85+
86+
const pc = new RTCPeerConnection({
87+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
88+
});
89+
pc.onicecandidate = ({ candidate }) => {
90+
peer
91+
.request(mediachannel_addtrack_removefirst_addtrack, {
92+
type: "candidate",
93+
payload: candidate,
94+
})
95+
.catch(() => {});
96+
};
97+
98+
const answer = async () => {
99+
await pc.setRemoteDescription(offer);
100+
await pc.setLocalDescription(await pc.createAnswer());
101+
peer
102+
.request(mediachannel_addtrack_removefirst_addtrack, {
103+
type: "answer",
104+
payload: pc.localDescription,
105+
})
106+
.catch(() => {});
107+
};
108+
109+
answer();
110+
let track = await new Promise<MediaStreamTrack>(
111+
(r) => (pc.ontrack = (e) => r(e.track))
112+
);
113+
await waitVideoPlay(track);
114+
115+
offer = await peer.request(mediachannel_addtrack_removefirst_addtrack, {
116+
type: "addTrack",
117+
});
118+
answer();
119+
track = await new Promise<MediaStreamTrack>(
120+
(r) => (pc.ontrack = (e) => r(e.track))
121+
);
122+
await waitVideoPlay(track);
123+
124+
offer = await peer.request(mediachannel_addtrack_removefirst_addtrack, {
125+
type: "removeTrack",
126+
payload: 0,
127+
});
128+
await answer();
129+
130+
offer = await peer.request(mediachannel_addtrack_removefirst_addtrack, {
131+
type: "addTrack",
132+
});
133+
answer();
134+
track = await new Promise<MediaStreamTrack>(
135+
(r) => (pc.ontrack = (e) => r(e.track))
136+
);
137+
await waitVideoPlay(track);
138+
139+
await peer.request(mediachannel_addtrack_removefirst_addtrack, {
140+
type: "done",
141+
});
142+
pc.close();
143+
done();
144+
}),
145+
10 * 1000
146+
);
147+
});

packages/dtls/src/server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ export class DtlsServer extends DtlsSocket {
3131
// flight1,3
3232
case HandshakeType.client_hello_1:
3333
{
34+
if (this.connected) {
35+
this.renegotiation();
36+
}
3437
const clientHello = ClientHello.deSerialize(handshake.fragment);
3538

3639
if (clientHello.cookie.length === 0) {

packages/dtls/src/socket.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ export class DtlsSocket {
3838
readonly transport: TransportContext = new TransportContext(
3939
this.options.transport
4040
);
41-
readonly cipher: CipherContext = new CipherContext(
41+
cipher: CipherContext = new CipherContext(
4242
this.sessionType,
4343
this.options.cert,
4444
this.options.key,
4545
this.options.signatureHash
4646
);
47-
readonly dtls: DtlsContext = new DtlsContext(this.options, this.sessionType);
48-
readonly srtp: SrtpContext = new SrtpContext();
47+
dtls: DtlsContext = new DtlsContext(this.options, this.sessionType);
48+
srtp: SrtpContext = new SrtpContext();
4949

5050
connected = false;
5151
extensions: Extension[] = [];
@@ -58,6 +58,21 @@ export class DtlsSocket {
5858
this.transport.socket.onData = this.udpOnMessage;
5959
}
6060

61+
renegotiation() {
62+
log("renegotiation", this.sessionType);
63+
this.connected = false;
64+
this.cipher = new CipherContext(
65+
this.sessionType,
66+
this.options.cert,
67+
this.options.key,
68+
this.options.signatureHash
69+
);
70+
this.dtls = new DtlsContext(this.options, this.sessionType);
71+
this.srtp = new SrtpContext();
72+
this.extensions = [];
73+
this.bufferFragmentedHandshakes = [];
74+
}
75+
6176
private udpOnMessage = (data: Buffer) => {
6277
const packets = parsePacket(data);
6378

0 commit comments

Comments
 (0)