diff options
-rw-r--r-- | examples/.gitignore | 2 | ||||
-rw-r--r-- | examples/Makefile.am | 14 | ||||
-rw-r--r-- | examples/callee.c | 62 | ||||
-rw-r--r-- | examples/demo.c | 250 | ||||
-rw-r--r-- | examples/demo.html | 95 | ||||
-rw-r--r-- | src/urtc.c | 10 | ||||
-rw-r--r-- | src/urtc.h | 12 |
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> @@ -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) { @@ -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 |