{"id":228,"date":"2016-11-23T10:00:48","date_gmt":"2016-11-23T10:00:48","guid":{"rendered":"https:\/\/blog.mozilla.org\/webrtc\/?p=228"},"modified":"2016-11-23T15:55:57","modified_gmt":"2016-11-23T15:55:57","slug":"enhancing-webcam-using-capturestream","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/","title":{"rendered":"Enhancing webcams with canvas.captureStream()"},"content":{"rendered":"<div style=\"position: relative; padding-bottom: 57%;\"><iframe loading=\"lazy\" style=\"position: absolute; top: 0; left: 0;\" src=\"https:\/\/gfycat.com\/ifr\/DeterminedLightFerret\" width=\"100%\" height=\"100%\" frameborder=\"0\" scrolling=\"no\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/div>\n<p class=\"graf graf--p\" style=\"text-align: center;\"><em>A really enhanced webcam stream!<\/em><\/p>\n<p class=\"graf graf--p\">Recently, <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTMLCanvasElement\/captureStream\" target=\"_blank\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTMLCanvasElement\/captureStream\">HTMLCanvasElement.captureStream()<\/a> was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Media_Streams_API\" target=\"_blank\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Media_Streams_API\">MediaStream<\/a> to be consumed by applications. This is the same base MediaStream type that <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MediaDevices\/getUserMedia\" target=\"_blank\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MediaDevices\/getUserMedia\">getUserMedia<\/a> returns, which is what websites use to get access to your webcam.<\/p>\n<p class=\"graf graf--p\">The first question that comes to mind is, of course: <em class=\"markup--em markup--p-em\">\u201cIs it possible to intercept calls to getUserMedia, get a hold of the webcam MediaStream, enhance it by rendering it into a canvas and doing some post-processing, then transparently returning the canvas\u2019 MediaStream?\u201d<\/em><\/p>\n<p class=\"graf graf--p\">As it turns out, the answer is <!--more--> yes.<\/p>\n<p class=\"graf graf--p\">We built a cross-platform <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/Add-ons\/WebExtensions\" target=\"_blank\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/Add-ons\/WebExtensions\">WebExtension<\/a> called <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/www.zombocam.com\" target=\"_blank\" data-href=\"https:\/\/www.zombocam.com\">Zombocam<\/a> that does exactly this. Zombocam injects itself on every webpage and monkey-patches getUserMedia. If a webpage then calls getUserMedia, we transparently enhance the camera and spawn a floating UI in the DOM that lets you control your different filters and settings. This means that any website that uses your webcam will now get your enhanced webcam instead!<\/p>\n<p class=\"graf graf--p\">This blog post is a technical walk-through of the different challenges we ran into while developing Zombocam.<\/p>\n<h2 class=\"graf graf--h2\">Monkey-patching 101<\/h2>\n<p class=\"graf graf--p\">Monkey-patching getUserMedia essentially means replacing the browser\u2019s implementation with our own. We supply our own getUserMedia function that wraps the browser\u2019s implementation and adds an intermediary canvas processing step (and fires up a UI). Of course, since getUserMedia is a web JS API, there are one million different versions that need to be supported. There\u2019s Navigator.getUserMedia and MediaDevices.getUserMedia, and then vendor prefixes on top of that (e.g. Navigator.webkitGetUserMedia and Navigator.mozGetUserMedia), and then there are different signatures (e.g. callbacks vs promises), and then on top of that again they historically support different syntaxes for specifying constraints. Oh, and they have different errors too. To be fair, MediaDevices.getUserMedia, the one true getUserMedia, solves all of these problems, but the web needs to wait for everyone to stop using the old versions first.<\/p>\n<div style=\"width: 50%; margin: 0 auto\">\n<div style=\"position: relative; padding-bottom: 42%; text-align: center;\"><iframe loading=\"lazy\" style=\"position: absolute; top: 0; left: 0;\" src=\"https:\/\/gfycat.com\/ifr\/ColorlessGeneralAmericanwarmblood\" width=\"100%\" height=\"100%\" frameborder=\"0\" scrolling=\"no\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/div>\n<\/div>\n<p class=\"graf graf--p\" style=\"text-align: center; margin-top: 10px;\"><em>One million twisty little getUserMedia functions, all different.<\/em><\/p>\n<p class=\"graf graf--p\">All of this boils down to having to type a lot of code to iron over the inconsistencies between different implementations, but in the happy case we end up with something like:<\/p>\n<h2 class=\"graf graf--h2\"><\/h2>\n<p><script src=\"https:\/\/gist.github.com\/sigvef\/6e5a04c0974e2418f1b10c6b11a79d77.js\"><\/script><\/p>\n<p class=\"graf graf--p\" style=\"text-align: center;\"><em>Monkey-patching on one of the many heads of the Hydra that is getUserMedia.<\/em><\/p>\n<h2 class=\"graf graf--h2\">The rendering pipeline<\/h2>\n<p class=\"graf graf--p\">Most of the effects and filters in Zombocam are implemented as <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebGL_API\" target=\"_blank\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebGL_API\">WebGL<\/a> fullscreen quad shader passes. This is a WebGL rendering technique that essentially lets us generate images on the fly on a per-pixel basis by using a fragment shader. This is elaborated upon in thorough detail in <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/blog.mayflower.de\/4584-Playing-around-with-pixel-shaders-in-WebGL.html\" target=\"_blank\" data-href=\"https:\/\/blog.mayflower.de\/4584-Playing-around-with-pixel-shaders-in-WebGL.html\">this excellent article<\/a> by Alexander Oldemeier. Using this technique means that the image processing can be done on the GPU, which is essential to achieve smooth real-time performance. For each video frame, the frame is uploaded to the GPU and made available to an effect\u2019s fragment shader, which is responsible for implementing the specific transformation for that effect.<\/p>\n<p><script src=\"https:\/\/gist.github.com\/sigvef\/cc5f6997000cf1c9a0aad6b1fd3e0425.js\"><\/script><\/p>\n<p class=\"graf graf--p\" style=\"text-align: center;\"><em>The glsl source for an invert effect fragment shader. This fragment shader inverts the pixel values for each pixel.<\/em><\/p>\n<p class=\"graf graf--p\">Effects in Zombocam are split into three main categories: color filters, distortion effects and overlays. Filters in the first categories are implemented as non-linear per-channel functions with hard-coded mappings of input to output values in each frame. The idea is that a color grading expert creates a nice-looking preset using his or her favorite color grading tool. Then that color grading is applied to three 0\u2013255 gradients, one for each color channel. The color graded outputs then serve as lookup tables for the pixel values in order to create a color graded output. This is a simplified version of the technique elaborated upon in <a class=\"markup--anchor markup--p-anchor\" href=\"http:\/\/www.slickentertainment.com\/tech\/dev-blog-128-color-grading-another-cool-rendering-trick\/\" target=\"_blank\" data-href=\"http:\/\/www.slickentertainment.com\/tech\/dev-blog-128-color-grading-another-cool-rendering-trick\/\">this excellent article<\/a> by Slick Entertainment.<\/p>\n<p class=\"graf graf--p\">Distortion effects are implemented as non-linear pixel coordinate transformation functions on the input image. That is, the pixel at coordinate <em class=\"markup--em markup--p-em\">(x, y)<\/em> in the transformed image is copied from the pixel at coordinate<em class=\"markup--em markup--p-em\"> f(x, y)<\/em> in the original image. As long as you define <em class=\"markup--em markup--p-em\">f<\/em> correctly, you can implement swirls, pinches, magnifications, hazes and all sorts of other distortions.<\/p>\n<p class=\"graf graf--p\">Finally, overlay effects simply overlay new pixels on parts or all of the frame. These new pixels can be sourced from anywhere, including other video sources. This effectively lets us overlay Giphy videos directly in the camera stream! Productivity will never be the same.<\/p>\n<p class=\"graf graf--p\">Since effects can be chained in Zombocam, the output from one effect\u2019s rendering pass is fed directly as input to the next effect\u2019s rendering pass. This opens for a wide array of different possible effect combinations.<\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" loading=\"lazy\" class=\"size-medium\" src=\"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png\" alt=\"A heavily distorted webcam image.\" width=\"749\" height=\"564\" style=\"max-width:100%;height: auto\" \/><\/div>\n<p class=\"graf graf--p\" style=\"text-align: center; margin-top: 10px;\"><em>Zombocam can turn you into a cyclops if you\u2019re not careful when chaining effects!<\/em><\/p>\n<h2 class=\"graf graf--h2\">Works everywhere! (*)<\/h2>\n<p class=\"graf graf--p\">In theory, this approach works everywhere out of the box, so you can use when you\u2019re snapping a profile picture on <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/facebook.com\" target=\"_blank\" data-href=\"https:\/\/facebook.com\">Facebook<\/a>\u00a0, hanging out in video meetings on <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/appear.in\" target=\"_blank\" data-href=\"https:\/\/appear.in\">Appear.in<\/a> or <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/hangouts.google.com\" target=\"_blank\" data-href=\"https:\/\/hangouts.google.com\">Google Hangouts<\/a>. In practice, however, the story is a little more nuanced. Reliably monkey-patching getUserMedia in time in a cross-browser fashion via injection from a WebExtension without going overboard with permissions turns out to be hard in some cases. This means that if an application is really adamant at calling getUserMedia reeeally early in the page\u2019s lifetime, getUserMedia might not be monkey-patched yet. In that case, Zombocam will simply never trigger, and it will be as if it weren\u2019t ever even installed.<\/p>\n<p class=\"graf graf--p\">When attempting to transparently monkey-patch APIs one has to take extreme care to make sure that the monkey-patching actually is transparent. That means properly forwarding all sorts of properties on the Streams and Tracks returned from getUserMedia that applications might expect and depend on.<br \/>\nOne specific example of this that we ran into was with <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/appear.in\/information\/premium\/\" target=\"_blank\" data-href=\"https:\/\/appear.in\/information\/premium\/\">Appear.in\u2019s new premium offering<\/a>, where you can screen-share and show your webcam stream in your meeting room at the same time. The application relied on the name of one of the Tracks to be \u201cScreen\u201d, which we didn\u2019t properly forward to our Tracks that we got from our canvas. Because of this, Appear.in didn\u2019t know which of the tracks was the screen-sharing track, and things stopped working. Properly forwarding the name property solved the issue, and we learned an important lesson in the virtues of actually being transparent when trying to transparently intercept APIs.<\/p>\n<h2 class=\"graf graf--h2\">What\u2019s next: audio\u00a0filters<\/h2>\n<p class=\"graf graf--p\">With the new release of <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/www.zombocam.com\" target=\"_blank\" data-href=\"https:\/\/www.zombocam.com\">Zombocam<\/a> coming up this week we\u2019ve taken it one step further and enhanced getUserMedia audio tracks as well using the <a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Web_Audio_API\" target=\"_blank\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Web_Audio_API\">Web Audio API<\/a>. More on that in a later blog post!<\/p>\n","protected":false},"excerpt":{"rendered":"A really enhanced webcam stream! Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be consumed by applications. This is the same base MediaStream type that getUserMedia returns, which is what websites use to get access to your webcam. The first question that [&hellip;]","protected":false},"author":1402,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[134,301093,297123,299276,290361,6946,301092],"coauthors":[],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Enhancing webcams with canvas.captureStream() - Advancing WebRTC<\/title>\n<meta name=\"description\" content=\"Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be c..\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Enhancing webcams with canvas.captureStream() - Advancing WebRTC\" \/>\n<meta property=\"og:description\" content=\"Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be c..\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/\" \/>\n<meta property=\"og:site_name\" content=\"Advancing WebRTC\" \/>\n<meta property=\"article:published_time\" content=\"2016-11-23T10:00:48+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2016-11-23T15:55:57+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png\" \/>\n<meta name=\"author\" content=\"Sigve Sebastian Farstad\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sigve Sebastian Farstad\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/\",\"url\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/\",\"name\":\"Enhancing webcams with canvas.captureStream() - Advancing WebRTC\",\"isPartOf\":{\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png\",\"datePublished\":\"2016-11-23T10:00:48+00:00\",\"dateModified\":\"2016-11-23T15:55:57+00:00\",\"author\":{\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/#\/schema\/person\/a9588be4abc7e78f36f3da8e5756dbc7\"},\"description\":\"Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be c..\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#primaryimage\",\"url\":\"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png\",\"contentUrl\":\"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.mozilla.org\/webrtc\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Enhancing webcams with canvas.captureStream()\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/#website\",\"url\":\"https:\/\/blog.mozilla.org\/webrtc\/\",\"name\":\"Advancing WebRTC\",\"description\":\"Committed to moving Firefox and WebRTC forward\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.mozilla.org\/webrtc\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/#\/schema\/person\/a9588be4abc7e78f36f3da8e5756dbc7\",\"name\":\"Sigve Sebastian Farstad\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/webrtc\/#\/schema\/person\/image\/7c8f7d408f399507a8e91353ef1b2fd3\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8d61ffb81b43e827e837623f0d07294c?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8d61ffb81b43e827e837623f0d07294c?s=96&d=mm&r=g\",\"caption\":\"Sigve Sebastian Farstad\"},\"description\":\"Software Engineer in the Strategic Engineering team in Telenor Digital.\",\"sameAs\":[\"http:\/\/arkt.is\"],\"url\":\"https:\/\/blog.mozilla.org\/webrtc\/author\/sigvesebtelenordigital-com\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Enhancing webcams with canvas.captureStream() - Advancing WebRTC","description":"Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be c..","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/","og_locale":"en_US","og_type":"article","og_title":"Enhancing webcams with canvas.captureStream() - Advancing WebRTC","og_description":"Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be c..","og_url":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/","og_site_name":"Advancing WebRTC","article_published_time":"2016-11-23T10:00:48+00:00","article_modified_time":"2016-11-23T15:55:57+00:00","og_image":[{"url":"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png"}],"author":"Sigve Sebastian Farstad","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Sigve Sebastian Farstad","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/","url":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/","name":"Enhancing webcams with canvas.captureStream() - Advancing WebRTC","isPartOf":{"@id":"https:\/\/blog.mozilla.org\/webrtc\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#primaryimage"},"image":{"@id":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#primaryimage"},"thumbnailUrl":"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png","datePublished":"2016-11-23T10:00:48+00:00","dateModified":"2016-11-23T15:55:57+00:00","author":{"@id":"https:\/\/blog.mozilla.org\/webrtc\/#\/schema\/person\/a9588be4abc7e78f36f3da8e5756dbc7"},"description":"Recently, HTMLCanvasElement.captureStream() was implemented in browsers. This allows you to expose the contents of a HTML5 canvas as a MediaStream to be c..","breadcrumb":{"@id":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#primaryimage","url":"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png","contentUrl":"https:\/\/cdn-images-1.medium.com\/max\/1000\/1*ycK1gGpS5YwxIAHAzbHZKA.png"},{"@type":"BreadcrumbList","@id":"https:\/\/blog.mozilla.org\/webrtc\/enhancing-webcam-using-capturestream\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.mozilla.org\/webrtc\/"},{"@type":"ListItem","position":2,"name":"Enhancing webcams with canvas.captureStream()"}]},{"@type":"WebSite","@id":"https:\/\/blog.mozilla.org\/webrtc\/#website","url":"https:\/\/blog.mozilla.org\/webrtc\/","name":"Advancing WebRTC","description":"Committed to moving Firefox and WebRTC forward","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.mozilla.org\/webrtc\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.mozilla.org\/webrtc\/#\/schema\/person\/a9588be4abc7e78f36f3da8e5756dbc7","name":"Sigve Sebastian Farstad","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/webrtc\/#\/schema\/person\/image\/7c8f7d408f399507a8e91353ef1b2fd3","url":"https:\/\/secure.gravatar.com\/avatar\/8d61ffb81b43e827e837623f0d07294c?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8d61ffb81b43e827e837623f0d07294c?s=96&d=mm&r=g","caption":"Sigve Sebastian Farstad"},"description":"Software Engineer in the Strategic Engineering team in Telenor Digital.","sameAs":["http:\/\/arkt.is"],"url":"https:\/\/blog.mozilla.org\/webrtc\/author\/sigvesebtelenordigital-com\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/posts\/228"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/users\/1402"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/comments?post=228"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/posts\/228\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/media?parent=228"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/categories?post=228"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/tags?post=228"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mozilla.org\/webrtc\/wp-json\/wp\/v2\/coauthors?post=228"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}