summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/.gitignore2
-rw-r--r--examples/Makefile.am14
-rw-r--r--examples/callee.c62
-rw-r--r--examples/demo.c250
-rw-r--r--examples/demo.html95
-rw-r--r--src/urtc.c10
-rw-r--r--src/urtc.h12
7 files changed, 360 insertions, 85 deletions
diff --git a/examples/.gitignore b/examples/.gitignore
index b04600a..1549b67 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -1 +1 @@
-sandbox
+demo
diff --git a/examples/Makefile.am b/examples/Makefile.am
index f2e28b1..945cc7f 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,11 +1,5 @@
# Builds a minimal example program
-#bin_PROGRAMS = example
-#example_CFLAGS = -I$(top_srcdir)/src/include $(LWS_CFLAGS)
-#example_LDADD = $(top_builddir)/src/liburtc.la $(LWS_LIBS)
-#example_SOURCES = server.c
-
-# Builds a minimal example program
-bin_PROGRAMS = sandbox
-sandbox_CFLAGS = -I$(top_srcdir)/src
-sandbox_LDADD = $(top_builddir)/src/liburtc.la
-sandbox_SOURCES = sandbox.c
+bin_PROGRAMS = demo
+demo_CFLAGS = -I$(top_srcdir)/src
+demo_LDADD = $(top_builddir)/src/liburtc.la
+demo_SOURCES = demo.c
diff --git a/examples/callee.c b/examples/callee.c
deleted file mode 100644
index 4fb7ddf..0000000
--- a/examples/callee.c
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Simple callee example (web browser is caller). Sends video to caller upon
- * incoming calls.
- *
- * Copyright (c) 2019-2021 Chris Hiszpanski. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- */
-
-#include <signal.h>
-#include <unistd.h>
-
-#include "urtc.h"
-
-
-int main() {
- sigset_t ss;
- int signal;
-
- const char *stun[] = {
- "stun.liburtc.org",
- NULL
- };
-
- sigemptyset(&ss);
- sigaddset(&ss, SIGINT);
- sigaddset(&ss, SIGTERM);
- sigaddset(&ss, SIGQUIT);
-
- // create a new peer connection (no actual network communication yet)
- urtc_peerconn_t *pc = urtc_peerconn_create(NULL);
-
- // connect to signaling service
-
- sigwait(&ss, &signal);
-
- urtc_peerconn_destroy(pc);
-
- return 0;
-}
-
-/* vim: set expandtab ts=8 sw=4 tw=0 : */
diff --git a/examples/demo.c b/examples/demo.c
new file mode 100644
index 0000000..34b85de
--- /dev/null
+++ b/examples/demo.c
@@ -0,0 +1,250 @@
+/**
+ * Simple callee example (web browser is caller). Sends video to caller upon
+ * incoming calls.
+ */
+
+#include <errno.h> // errno
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h> // printf
+#include <string.h>
+#include <unistd.h>
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "urtc.h"
+
+// STUN servers
+const char *stun[] = {
+ "stun.liburtc.org",
+ NULL
+};
+
+static int http_connect(const char *hostname) {
+ int err, fd;
+ struct addrinfo *res, *res0;
+
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = IPPROTO_TCP
+ };
+
+ // resolve hostname to IPv4/IPv6 address
+ if (err = getaddrinfo(hostname, "http", &hints, &res0), err) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(errno));
+ return -1;
+ }
+
+ for (res = res0; res; res = res->ai_next) {
+ // attempt to open socket
+ fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (-1 == fd) {
+ continue; // failed. onto the next result.
+ }
+
+ // attempt to connect socket
+ if (-1 == connect(fd, res->ai_addr, res->ai_addrlen)) {
+ close(fd);
+ fd = -1;
+ continue; // failed. onto the next result.
+ }
+
+ break; // success. return.
+ }
+
+ // free resolved addresses
+ freeaddrinfo(res0);
+
+ return fd;
+}
+
+static int http_post(
+ const char *hostname,
+ const char *path,
+ const char *body
+) {
+ const char *tmpl =
+ "POST %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %d\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n";
+
+ int fd, rv = -1;
+ char req[128];
+
+ if (fd = http_connect(hostname), -1 == fd) {
+ return -1;
+ }
+
+ // construct request
+ if (-1 == snprintf(req, sizeof(req), tmpl, path, hostname, strlen(body))) {
+ fprintf(stderr, "bad request template\n");
+ goto _unwind_socket;
+ }
+
+ // send request
+ if (rv = send(fd, req, strlen(req), 0), -1 == rv) {
+ perror("send");
+ goto _unwind_socket;
+ }
+
+ // send request body
+ if (rv = send(fd, body, strlen(body), 0), -1 == rv) {
+ perror("send");
+ goto _unwind_socket;
+ }
+
+_unwind_socket:
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+
+_done:
+ return rv;
+}
+
+/**
+ * HTTP GET request
+ *
+ * Resolves simple signaling server hostname to IPv4/IPv6 address and connects
+ * to its HTTP port.
+ *
+ * \param[out] response Buffer into which response is written.
+ * \param[in] size Max size of response.
+ * \param hostname Server hostname (e.g. "demo.liburtc.org")
+ * \param path HTTP path (e.g. "/offer")
+ *
+ * \return Socket file descriptor, or -1 on error. Caller must close file
+ * descriptor.
+ */
+static int get(
+ char *response,
+ size_t size,
+ const char *hostname,
+ const char *path
+) {
+ const char *tmpl =
+ "GET %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "\r\n";
+
+ int err, fd, rv = -1;
+ char req[128];
+
+ if (fd = http_connect(hostname), -1 == fd) {
+ return -1;
+ }
+
+ // construct request
+ if (-1 == snprintf(req, sizeof(req), tmpl, path, hostname)) {
+ fprintf(stderr, "bad request template\n");
+ goto _unwind_socket;
+ }
+
+ // send request
+ if (rv = send(fd, req, strlen(req), 0), -1 == rv) {
+ perror("send");
+ goto _unwind_socket;
+ }
+
+ // get response
+ if (rv = recv(fd, response, size, 0), -1 == rv) {
+ perror("recv");
+ goto _unwind_socket;
+ }
+
+_unwind_socket:
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+
+_done:
+ return rv;
+}
+
+
+/**
+ * (callback) Handles incoming ICE candidates.
+ *
+ * \param cand[in] ICE candidate.
+ * \param arg[in] User argument.
+ */
+void handle_ice_candidate(const char *cand, void *arg) {
+ urtc_peerconn_t *pc = (urtc_peerconn_t *)arg;
+
+ urtc_add_ice_candidate(pc, cand);
+}
+
+static int longpoll(char *response, size_t size, const char *path) {
+ int n;
+
+ while (true) {
+ n = get(response, size, "demo.liburtc.org", path);
+ if (-1 == n || 0 == n) {
+ perror("get");
+ continue;
+ }
+
+ // blindly strip http headers
+ char *body = strstr(response, "\r\n\r\n");
+ strcpy(response, body + 4);
+
+ // ignore empty responses (i.e. long-poll timeout)
+ if (0 == strlen(response)) {
+ continue;
+ }
+
+ break;
+ }
+
+ return n;
+}
+
+char offer[4096];
+char answer[4096];
+
+int main(int argc, char **argv) {
+ int n;
+
+ // create a new peer connection (no actual network communication yet)
+ urtc_peerconn_t *pc = urtc_peerconn_create(stun);
+
+ // TODO add tracks here
+
+ // get offer
+ n = longpoll(offer, sizeof(offer), "/offer");
+ fprintf(stderr, "%s\n", offer);
+
+ if (urtc_set_remote_description(pc, offer)) {
+ fprintf(stderr, "error: set remote description\n");
+ }
+ if (urtc_create_answer(pc, answer, sizeof(answer))) {
+ fprintf(stderr, "error: create answer\n");
+ }
+
+ http_post("demo.liburtc.org", "/answer", answer);
+ fprintf(stderr, "answer:\n%s\n", answer);
+ //urtc_set_local_description(pc, answer);
+
+
+ // block until signal
+ {
+ int signal;
+ sigset_t ss;
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGINT);
+ sigaddset(&ss, SIGTERM);
+ sigaddset(&ss, SIGQUIT);
+ sigwait(&ss, &signal);
+ }
+
+ // clean up
+ urtc_peerconn_destroy(pc);
+
+ return 0;
+}
+
+/* vim: set expandtab ts=4 sw=4 tw=0 : */
diff --git a/examples/demo.html b/examples/demo.html
index 03e07b9..0be9df8 100644
--- a/examples/demo.html
+++ b/examples/demo.html
@@ -4,12 +4,105 @@
<title>liburtc demo</title>
<script src="//webrtchacks.github.io/adapter/adapter-latest.js"></script>
<script>
- // call liburtc peer
+ // send offer and get answer
+ let sendOffer = offer => {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == XMLHttpRequest.DONE) {
+ resolve(new RTCSessionDescription({
+ type: "offer",
+ sdp: xhr.responseText
+ }));
+ }
+ };
+ xhr.open("POST", "http://localhost/offer");
+ xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.send(JSON.stringify(offer));
+ });
+ };
+
+ function sendCandidate(candidate) {
+ let req = new XMLHttpRequest();
+ req.open("POST", "http://localhost/candidate");
+ req.setRequestHeader("Content-Type", "application/json");
+ req.send(JSON.stringify(candidate));
+ };
+
+ window.onload = function demo() {
+ // create new peer connection
+ let pc = new RTCPeerConnection({
+ iceCandidatePoolSize: 4,
+ iceServers: [
+ { urls: "stun:stun.liburtc.org" }
+ ]
+ });
+
+ // (callback) called upon discovery of new local ICE candidate
+ pc.onicecandidate = function(event) {
+ if (event.candidate) {
+ console.log("local candidate:\n%c%s", "color: brown", event.candidate.candidate);
+ }
+ };
+
+ // (callback) called upon reception of new remote video track
+ pc.ontrack = function(event) {
+ document.getElementById("remoteVideo").srcObject = event.streams[0];
+ };
+
+ // create offer and send to remote
+ pc.createOffer({ offerToReceiveVideo: true }).then(function(offer) {
+ console.log("local offer:\n%c%s", "color: orange", offer.sdp);
+ pc.setLocalDescription(offer);
+ sendOffer(offer).then(function(answer) {
+ console.log("remote answer:\n%c%s", "color: green", answer.sdp);
+ pc.setRemoteDescription(answer)
+ .catch(function(reason) {
+ console.log(reason);
+ });
+ });
+ }).catch(function(reason) {
+ console.log(reason);
+ });
+ }
</script>
+ <style>
+ html, body {
+ font-family: Arial, Helvetica, sans-serif;
+ text-align: center;
+ }
+ main {
+ margin-left: auto;
+ margin-right: auto;
+ width: 80%;
+ }
+ p {
+ text-align: left;
+ }
+ video {
+ background-color: #eee;
+ border: solid 1px #bbb;
+ width: 100%;
+ }
+ </style>
</head>
<body>
<h1>liburtc demo</h1>
+ <main>
+ <p>
+ In this demo, this web browser is the caller and liburtc is the callee.
+ </p>
+
+ <p>
+ This browser creates an offer and relays it to liburtc via a simple
+ signaling server (HTTP/1.0 long-polling). liburtc replies with an
+ answer, again via the signaling service.
+ </p>
+
<video autoplay controls muted playsinline id="remoteVideo"></video>
+ </main>
+
+
</body>
</html>
diff --git a/src/urtc.c b/src/urtc.c
index 8cc6ff5..f3ffa99 100644
--- a/src/urtc.c
+++ b/src/urtc.c
@@ -425,20 +425,20 @@ int urtc_add_ice_candidate(struct peerconn *pc, const char *cand) {
return -URTC_ERR_NOT_IMPLEMENTED;
}
-int urtc_create_answer(struct peerconn *pc, char **answer) {
- return -URTC_ERR_NOT_IMPLEMENTED;
+int urtc_create_answer(struct peerconn *pc, char *answer, size_t size) {
+ return sdp_serialize(answer, size, &pc->ldesc);
}
-int urtc_create_offer(struct peerconn *pc, char **offer) {
+int urtc_create_offer(struct peerconn *pc, char *offer, size_t size) {
return -URTC_ERR_NOT_IMPLEMENTED;
}
int urtc_set_remote_description(struct peerconn *pc, const char *desc) {
- return -URTC_ERR_NOT_IMPLEMENTED;
+ return sdp_parse(&pc->rdesc, desc);
}
int urtc_set_local_description(struct peerconn *pc, const char *desc) {
- return -URTC_ERR_NOT_IMPLEMENTED;
+ return sdp_parse(&pc->ldesc, desc);
}
void urtc_peerconn_destroy(struct peerconn *pc) {
diff --git a/src/urtc.h b/src/urtc.h
index 7760ca8..eae4e6b 100644
--- a/src/urtc.h
+++ b/src/urtc.h
@@ -151,12 +151,12 @@ int urtc_add_ice_candidate(urtc_peerconn_t *pc, const char *cand);
* Akin to `createAnswer` method of `RTCPeerConnection` in WebRTC JS API.
*
* \param pc Peer connection
- * \param answer Pointer to string pointer pointing to generated answer.
- * Memory internally managed (caller need not and must not free).
+ * \param answer Destination pointer for generated answer.
+ * \param size Bytes available at destination.
*
* \return 0 on success, negative on error.
*/
-int urtc_create_answer(urtc_peerconn_t *pc, char **answer);
+int urtc_create_answer(urtc_peerconn_t *pc, char *answer, size_t size);
/**
* Creates a local description for an offering peer connection
@@ -170,12 +170,12 @@ int urtc_create_answer(urtc_peerconn_t *pc, char **answer);
* Akin to `createOffer` method of `RTCPeerConnection` in WebRTC JS API.
*
* \param pc Peer connection
- * \param offer Pointer to string pointer pointing to generated offer.
- * Memory interally managed (caller need no and must not free).
+ * \param offer Destination pointer for generated offer.
+ * \param size Bytes available at destination.
*
* \return 0 on success, negative on error.
*/
-int urtc_create_offer(urtc_peerconn_t *pc, char **offer);
+int urtc_create_offer(urtc_peerconn_t *pc, char *offer, size_t size);
/**
* Sets local description for peer connection